diff --git a/lib/addressable/template.rb b/lib/addressable/template.rb
index a6488fd6..7901e43d 100644
--- a/lib/addressable/template.rb
+++ b/lib/addressable/template.rb
@@ -22,15 +22,44 @@
module Addressable
##
# This is an implementation of a URI template based on
- # URI Template draft 03.
+ # RFC 6570 (http://tools.ietf.org/html/rfc6570).
class Template
# Constants used throughout the template code.
anything =
Addressable::URI::CharacterClasses::RESERVED +
Addressable::URI::CharacterClasses::UNRESERVED
- OPERATOR_EXPANSION =
- /\{-([a-zA-Z]+)\|([#{anything}]+)\|([#{anything}]+)\}/
- VARIABLE_EXPANSION = /\{([#{anything}]+?)(?:=([#{anything}]+))?\}/
+
+
+ variable_char_class =
+ Addressable::URI::CharacterClasses::ALPHA +
+ Addressable::URI::CharacterClasses::DIGIT + ?_
+
+ var_char =
+ "(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
+ RESERVED =
+ "(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
+ UNRESERVED =
+ "(?:[#{
+ Addressable::URI::CharacterClasses::UNRESERVED
+ }]|%[a-fA-F0-9][a-fA-F0-9])"
+ variable =
+ "(?:#{var_char}(?:\\.?#{var_char})*)"
+ varspec =
+ "(?:(#{variable})(\\*|:\\d+)?)"
+ VARNAME =
+ /^#{variable}$/
+ VARSPEC =
+ /^#{varspec}$/
+ VARIABLE_LIST =
+ /^#{varspec}(?:,#{varspec})*$/
+ operator =
+ "+#./;?&=,!@|"
+ EXPRESSION =
+ /\{([#{operator}])?(#{varspec}(?:,#{varspec})*)\}/
+
+
+ LEADERS = {?? => ??, ?/ => ?/, ?# => ?#, ?. => ?., ?; => ?;, ?& => ?&}
+ JOINERS = {?? => ?&, ?. => ?., ?; => ?;, ?& => ?&, ?/ => ?/}
##
# Raised if an invalid template value is supplied.
@@ -148,7 +177,7 @@ def inspect
#
# @param [#restore, #match] processor
# A template processor object may optionally be supplied.
- #
+ #
# The object should respond to either the restore or
# match messages or both. The restore method should
# take two parameters: `[String] name` and `[String] value`.
@@ -210,7 +239,7 @@ def extract(uri, processor=nil)
#
# @param [#restore, #match] processor
# A template processor object may optionally be supplied.
- #
+ #
# The object should respond to either the restore or
# match messages or both. The restore method should
# take two parameters: `[String] name` and `[String] value`.
@@ -253,7 +282,7 @@ def extract(uri, processor=nil)
#
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
# match = Addressable::Template.new(
- # "http://example.com/{first}/{second}/"
+ # "http://example.com/{first}/{+second}/"
# ).match(uri, ExampleProcessor)
# match.variables
# #=> ["first", "second"]
@@ -262,7 +291,7 @@ def extract(uri, processor=nil)
#
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
# match = Addressable::Template.new(
- # "http://example.com/{first}/{-list|/|second}/"
+ # "http://example.com/{first}{/second*}/"
# ).match(uri)
# match.variables
# #=> ["first", "second"]
@@ -279,38 +308,56 @@ def match(uri, processor=nil)
if uri.to_str == pattern
return Addressable::Template::MatchData.new(uri, self, mapping)
- elsif expansions.size > 0 && expansions.size == unparsed_values.size
- expansions.each_with_index do |expansion, index|
- unparsed_value = unparsed_values[index]
- if expansion =~ OPERATOR_EXPANSION
- operator, argument, variables =
- parse_template_expansion(expansion)
- extract_method = "extract_#{operator}_operator"
- if ([extract_method, extract_method.to_sym] &
- private_methods).empty?
- raise InvalidTemplateOperatorError,
- "Invalid template operator: #{operator}"
- else
- begin
- send(
- extract_method.to_sym, unparsed_value, processor,
- argument, variables, mapping
- )
- rescue TemplateOperatorAbortedError
- return nil
+ elsif expansions.size > 0
+ index = 0
+ expansions.each do |expansion|
+ _, operator, varlist = *expansion.match(EXPRESSION)
+ varlist.split(',').each do |varspec|
+ _, name, modifier = *varspec.match(VARSPEC)
+ case operator
+ when nil, ?+, ?#, ?/, ?.
+ unparsed_value = unparsed_values[index]
+ name = varspec[VARSPEC, 1]
+ value = unparsed_value
+ value = value.split(JOINERS[operator]) if value && modifier == ?*
+ when ?;, ??, ?&
+ if modifier == ?*
+ value = unparsed_values[index].split(JOINERS[operator])
+ value = value.inject({}) do |acc, v|
+ key, val = v.split('=')
+ val = "" if val.nil?
+ acc[key] = val
+ acc
+ end
+ else
+ if (unparsed_values[index])
+ name, value = unparsed_values[index].split('=')
+ value = "" if value.nil?
+ end
end
end
- else
- name = expansion[VARIABLE_EXPANSION, 1]
- value = unparsed_value
if processor != nil && processor.respond_to?(:restore)
value = processor.restore(name, value)
end
+ if processor == nil
+ if value.is_a?(Hash)
+ value = value.inject({}){|acc, (k, v)|
+ acc[Addressable::URI.unencode_component(k)] =
+ Addressable::URI.unencode_component(v)
+ acc
+ }
+ elsif value.is_a?(Array)
+ value = value.map{|v| Addressable::URI.unencode_component(v) }
+ else
+ value = Addressable::URI.unencode_component(value)
+ end
+ end
if mapping[name] == nil || mapping[name] == value
mapping[name] = value
else
return nil
end
+ index = index + 1
end
end
return Addressable::Template::MatchData.new(uri, self, mapping)
@@ -324,7 +371,7 @@ def match(uri, processor=nil)
#
# @param [Hash] mapping The mapping that corresponds to the pattern.
# @param [#validate, #transform] processor
- # An optional processor object may be supplied.
+ # An optional processor object may be supplied.
#
# The object should respond to either the validate or
# transform messages or both. Both the validate and
@@ -347,50 +394,18 @@ def match(uri, processor=nil)
# #=> "http://example.com/1/{two}/"
#
# Addressable::Template.new(
- # "http://example.com/search/{-list|+|query}/"
- # ).partial_expand(
- # {"query" => "an example search query".split(" ")}
- # ).pattern
- # #=> "http://example.com/search/an+example+search+query/"
- #
- # Addressable::Template.new(
- # "http://example.com/{-join|&|one,two}/"
+ # "http://example.com/{?one,two}/"
# ).partial_expand({"one" => "1"}).pattern
- # #=> "http://example.com/?one=1{-prefix|&two=|two}"
+ # #=> "http://example.com/?one=1{&two}/"
#
# Addressable::Template.new(
- # "http://example.com/{-join|&|one,two,three}/"
+ # "http://example.com/{?one,two,three}/"
# ).partial_expand({"one" => "1", "three" => 3}).pattern
- # #=> "http://example.com/?one=1{-prefix|&two=|two}&three=3"
+ # #=> "http://example.com/?one=1{&two}&three=3"
def partial_expand(mapping, processor=nil)
result = self.pattern.dup
- transformed_mapping = transform_mapping(mapping, processor)
- result.gsub!(
- /#{OPERATOR_EXPANSION}|#{VARIABLE_EXPANSION}/
- ) do |capture|
- if capture =~ OPERATOR_EXPANSION
- operator, argument, variables, default_mapping =
- parse_template_expansion(capture, transformed_mapping)
- expand_method = "expand_#{operator}_operator"
- if ([expand_method, expand_method.to_sym] & private_methods).empty?
- raise InvalidTemplateOperatorError,
- "Invalid template operator: #{operator}"
- else
- send(
- expand_method.to_sym, argument, variables,
- default_mapping, true
- )
- end
- else
- varname, _, vardefault = capture.scan(/^\{(.+?)(=(.*))?\}$/)[0]
- if transformed_mapping[varname]
- transformed_mapping[varname]
- elsif vardefault
- "{#{varname}=#{vardefault}}"
- else
- "{#{varname}}"
- end
- end
+ result.gsub!( EXPRESSION ) do |capture|
+ transform_partial_capture(mapping, capture, processor)
end
return Addressable::Template.new(result)
end
@@ -438,11 +453,11 @@ def partial_expand(mapping, processor=nil)
# #=> "http://example.com/search/an+example+search+query/"
#
# Addressable::Template.new(
- # "http://example.com/search/{-list|+|query}/"
+ # "http://example.com/search/{query}/"
# ).expand(
- # {"query" => "an example search query".split(" ")}
+ # {"query" => "an example search query"}
# ).to_str
- # #=> "http://example.com/search/an+example+search+query/"
+ # #=> "http://example.com/search/an%20example%20search%20query/"
#
# Addressable::Template.new(
# "http://example.com/search/{query}/"
@@ -453,24 +468,9 @@ def partial_expand(mapping, processor=nil)
# #=> Addressable::Template::InvalidTemplateValueError
def expand(mapping, processor=nil)
result = self.pattern.dup
- transformed_mapping = transform_mapping(mapping, processor)
- result.gsub!(
- /#{OPERATOR_EXPANSION}|#{VARIABLE_EXPANSION}/
- ) do |capture|
- if capture =~ OPERATOR_EXPANSION
- operator, argument, variables, default_mapping =
- parse_template_expansion(capture, transformed_mapping)
- expand_method = "expand_#{operator}_operator"
- if ([expand_method, expand_method.to_sym] & private_methods).empty?
- raise InvalidTemplateOperatorError,
- "Invalid template operator: #{operator}"
- else
- send(expand_method.to_sym, argument, variables, default_mapping)
- end
- else
- varname, _, vardefault = capture.scan(/^\{(.+?)(=(.*))?\}$/)[0]
- transformed_mapping[varname] || vardefault
- end
+ mapping = normalize_keys(mapping)
+ result.gsub!( EXPRESSION ) do |capture|
+ transform_capture(mapping, capture, processor)
end
return Addressable::URI.parse(result)
end
@@ -499,27 +499,25 @@ def variable_defaults
private
def ordered_variable_defaults
- @ordered_variable_defaults ||= (begin
+ @ordered_variable_defaults ||= (
expansions, expansion_regexp = parse_template_pattern(pattern)
-
- expansions.inject([]) do |result, expansion|
- case expansion
- when OPERATOR_EXPANSION
- _, _, variables, mapping = parse_template_expansion(expansion)
- result.concat variables.map { |var| [var, mapping[var]] }
- when VARIABLE_EXPANSION
- result << [$1, $2]
+ expansions.map do |capture|
+ _, operator, varlist = *capture.match(EXPRESSION)
+ varlist.split(',').map do |varspec|
+ name = varspec[VARSPEC, 1]
end
- result
- end
- end)
+ end.flatten
+ )
end
+
##
- # Transforms a mapping so that values can be substituted into the
- # template.
+ # Loops through each capture and expands any values available in mapping
#
- # @param [Hash] mapping The mapping of variables to values.
+ # @param [Hash] mapping
+ # Set of keys to expand
+ # @param [String] capture
+ # The expression to expand
# @param [#validate, #transform] processor
# An optional processor object may be supplied.
#
@@ -535,279 +533,227 @@ def ordered_variable_defaults
# automatically. Unicode normalization will be performed both before and
# after sending the value to the transform method.
#
- # @return [Hash] The transformed mapping.
- def transform_mapping(mapping, processor=nil)
- return mapping.inject({}) do |accu, pair|
- name, value = pair
- value = value.to_s if Numeric === value || Symbol === value
-
- unless value.respond_to?(:to_ary) || value.respond_to?(:to_str)
- raise TypeError,
- "Can't convert #{value.class} into String or Array."
- end
-
- if Symbol === name
- name = name.to_s
- elsif name.respond_to?(:to_str)
- name = name.to_str
- else
- raise TypeError,
- "Can't convert #{name.class} into String."
- end
- value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
-
- # Handle unicode normalization
- if value.kind_of?(Array)
- value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
+ # @return [String] The expanded expression
+ def transform_partial_capture(mapping, capture, processor = nil)
+ _, operator, varlist = *capture.match(EXPRESSION)
+ is_first = true
+ varlist.split(',').inject('') do |acc, varspec|
+ _, name, modifier = *varspec.match(VARSPEC)
+ value = mapping[name]
+ if value
+ operator = ?& if !is_first && operator == ??
+ acc << transform_capture(mapping, "{#{operator}#{varspec}}", processor)
else
- value = Addressable::IDNA.unicode_normalize_kc(value)
+ operator = ?& if !is_first && operator == ??
+ acc << "{#{operator}#{varspec}}"
end
-
- if processor == nil || !processor.respond_to?(:transform)
- # Handle percent escaping
- if value.kind_of?(Array)
- transformed_value = value.map do |val|
- Addressable::URI.encode_component(
- val, Addressable::URI::CharacterClasses::UNRESERVED)
- end
- else
- transformed_value = Addressable::URI.encode_component(
- value, Addressable::URI::CharacterClasses::UNRESERVED)
- end
- end
-
- # Process, if we've got a processor
- if processor != nil
- if processor.respond_to?(:validate)
- if !processor.validate(name, value)
- display_value = value.kind_of?(Array) ? value.inspect : value
- raise InvalidTemplateValueError,
- "#{name}=#{display_value} is an invalid template value."
- end
- end
- if processor.respond_to?(:transform)
- transformed_value = processor.transform(name, value)
- if transformed_value.kind_of?(Array)
- transformed_value.map! do |val|
- Addressable::IDNA.unicode_normalize_kc(val)
- end
- else
- transformed_value =
- Addressable::IDNA.unicode_normalize_kc(transformed_value)
- end
- end
- end
-
- accu[name] = transformed_value
- accu
+ is_first = false
+ acc
end
end
##
- # Expands a URI Template opt operator.
- #
- # @param [String] argument The argument to the operator.
- # @param [Array] variables The variables the operator is working on.
- # @param [Hash] mapping The mapping of variables to values.
- #
- # @return [String] The expanded result.
- def expand_opt_operator(argument, variables, mapping, partial=false)
- variables_present = variables.any? do |variable|
- mapping[variable] != [] &&
- mapping[variable]
- end
- if partial && !variables_present
- "{-opt|#{argument}|#{variables.join(",")}}"
- elsif variables_present
- argument
- else
- ""
- end
- end
-
- ##
- # Expands a URI Template neg operator.
- #
- # @param [String] argument The argument to the operator.
- # @param [Array] variables The variables the operator is working on.
- # @param [Hash] mapping The mapping of variables to values.
+ # Transforms a mapped value so that values can be substituted into the
+ # template.
#
- # @return [String] The expanded result.
- def expand_neg_operator(argument, variables, mapping, partial=false)
- variables_present = variables.any? do |variable|
- mapping[variable] != [] &&
- mapping[variable]
- end
- if partial && !variables_present
- "{-neg|#{argument}|#{variables.join(",")}}"
- elsif variables_present
- ""
- else
- argument
- end
- end
-
- ##
- # Expands a URI Template prefix operator.
+ # @param [Hash] mapping The mapping to replace captures
+ # @param [String] capture
+ # The expression to replace
+ # @param [#validate, #transform] processor
+ # An optional processor object may be supplied.
#
- # @param [String] argument The argument to the operator.
- # @param [Array] variables The variables the operator is working on.
- # @param [Hash] mapping The mapping of variables to values.
+ # The object should respond to either the validate or
+ # transform messages or both. Both the validate and
+ # transform methods should take two parameters: name and
+ # value. The validate method should return true
+ # or false; true if the value of the variable is valid,
+ # false otherwise. An InvalidTemplateValueError exception
+ # will be raised if the value is invalid. The transform method
+ # should return the transformed variable value as a String. If a
+ # transform method is used, the value will not be percent encoded
+ # automatically. Unicode normalization will be performed both before and
+ # after sending the value to the transform method.
#
- # @return [String] The expanded result.
- def expand_prefix_operator(argument, variables, mapping, partial=false)
- if variables.size != 1
- raise InvalidTemplateOperatorError,
- "Template operator 'prefix' takes exactly one variable."
- end
- value = mapping[variables.first]
- if !partial || value
- if value.kind_of?(Array)
- (value.map { |list_value| argument + list_value }).join("")
- elsif value
- argument + value.to_s
- end
- else
- "{-prefix|#{argument}|#{variables.first}}"
- end
- end
+ # @return [String] The expanded expression
+ def transform_capture(mapping, capture, processor=nil)
+ _, operator, varlist = *capture.match(EXPRESSION)
+ return_value = varlist.split(',').inject([]) do |acc, varspec|
+ _, name, modifier = *varspec.match(VARSPEC)
+ value = mapping[name]
+ unless value == nil || value == {}
+ allow_reserved = %w(+ #).include?(operator)
+ value = value.to_s if Numeric === value || Symbol === value
+ length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/
+
+ unless (Hash === value) ||
+ value.respond_to?(:to_ary) || value.respond_to?(:to_str)
+ raise TypeError,
+ "Can't convert #{value.class} into String or Array."
+ end
- ##
- # Expands a URI Template suffix operator.
- #
- # @param [String] argument The argument to the operator.
- # @param [Array] variables The variables the operator is working on.
- # @param [Hash] mapping The mapping of variables to values.
- #
- # @return [String] The expanded result.
- def expand_suffix_operator(argument, variables, mapping, partial=false)
- if variables.size != 1
- raise InvalidTemplateOperatorError,
- "Template operator 'suffix' takes exactly one variable."
- end
- value = mapping[variables.first]
- if !partial || value
- if value.kind_of?(Array)
- (value.map { |list_value| list_value + argument }).join("")
- elsif value
- value.to_s + argument
- end
- else
- "{-suffix|#{argument}|#{variables.first}}"
- end
- end
+ value = normalize_value(value)
- ##
- # Expands a URI Template join operator.
- #
- # @param [String] argument The argument to the operator.
- # @param [Array] variables The variables the operator is working on.
- # @param [Hash] mapping The mapping of variables to values.
- #
- # @return [String] The expanded result.
- def expand_join_operator(argument, variables, mapping, partial=false)
- if !partial
- variable_values = variables.inject([]) do |accu, variable|
- if !mapping[variable].kind_of?(Array)
- if mapping[variable]
- accu << variable + "=" + (mapping[variable])
+ if processor == nil || !processor.respond_to?(:transform)
+ # Handle percent escaping
+ if allow_reserved
+ encode_map =
+ Addressable::URI::CharacterClasses::RESERVED +
+ Addressable::URI::CharacterClasses::UNRESERVED
+ else
+ encode_map = Addressable::URI::CharacterClasses::UNRESERVED
end
- else
- raise InvalidTemplateOperatorError,
- "Template operator 'join' does not accept Array values."
- end
- accu
- end
- variable_values.join(argument)
- else
- buffer = ""
- state = :suffix
- variables.each_with_index do |variable, index|
- if !mapping[variable].kind_of?(Array)
- if mapping[variable]
- if buffer.empty? || buffer[-1..-1] == "}"
- buffer << (variable + "=" + (mapping[variable]))
- elsif state == :suffix
- buffer << argument
- buffer << (variable + "=" + (mapping[variable]))
- else
- buffer << (variable + "=" + (mapping[variable]))
+ if value.kind_of?(Array)
+ transformed_value = value.map do |val|
+ if length
+ Addressable::URI.encode_component(val[0...length], encode_map)
+ else
+ Addressable::URI.encode_component(val, encode_map)
+ end
end
- else
- if !buffer.empty? && (buffer[-1..-1] != "}" || state == :prefix)
- buffer << "{-opt|#{argument}|#{variable}}"
- state = :prefix
+ unless modifier == "*"
+ transformed_value = transformed_value.join(',')
+ end
+ elsif value.kind_of?(Hash)
+ transformed_value = value.map do |key, val|
+ if modifier == "*"
+ "#{
+ Addressable::URI.encode_component( key, encode_map)
+ }=#{
+ Addressable::URI.encode_component( val, encode_map)
+ }"
+ else
+ "#{
+ Addressable::URI.encode_component( key, encode_map)
+ },#{
+ Addressable::URI.encode_component( val, encode_map)
+ }"
+ end
end
- if buffer.empty? && variables.size == 1
- # Evaluates back to itself
- buffer << "{-join|#{argument}|#{variable}}"
+ unless modifier == "*"
+ transformed_value = transformed_value.join(',')
+ end
+ else
+ if length
+ transformed_value = Addressable::URI.encode_component(
+ value[0...length], encode_map)
else
- buffer << "{-prefix|#{variable}=|#{variable}}"
+ transformed_value = Addressable::URI.encode_component(
+ value, encode_map)
end
- if (index != (variables.size - 1) && state == :suffix)
- buffer << "{-opt|#{argument}|#{variable}}"
- elsif index != (variables.size - 1) &&
- mapping[variables[index + 1]]
- buffer << argument
- state = :prefix
+ end
+ end
+
+ # Process, if we've got a processor
+ if processor != nil
+ if processor.respond_to?(:validate)
+ if !processor.validate(name, value)
+ display_value = value.kind_of?(Array) ? value.inspect : value
+ raise InvalidTemplateValueError,
+ "#{name}=#{display_value} is an invalid template value."
end
end
- else
- raise InvalidTemplateOperatorError,
- "Template operator 'join' does not accept Array values."
+ if processor.respond_to?(:transform)
+ transformed_value = processor.transform(name, value)
+ transformed_value = normalize_value(transformed_value)
+ end
end
+ acc << [name, transformed_value]
end
- buffer
+ acc
+ end
+ return "" if return_value.empty?
+ join_values(operator, return_value)
+ end
+
+ ##
+ # Takes a set of values, and joins them together based on the
+ # operator.
+ #
+ # @param [String, Nil] operator One of the operators from the set
+ # (?,&,+,#,;,/,.), or nil if there wasn't one.
+ # @param [Array] return_value
+ # The set of return values (as [variable_name, value] tuples) that will
+ # be joined together.
+ #
+ # @return [String] The transformed mapped value
+ def join_values(operator, return_value)
+ leader = LEADERS.fetch(operator, '')
+ joiner = JOINERS.fetch(operator, ',')
+ case operator
+ when ?&, ??
+ leader + return_value.map{|k,v|
+ if v.is_a?(Array) && v.first =~ /=/
+ v.join(joiner)
+ elsif v.is_a?(Array)
+ v.map{|v| "#{k}=#{v}"}.join(joiner)
+ else
+ "#{k}=#{v}"
+ end
+ }.join(joiner)
+ when ?;
+ return_value.map{|k,v|
+ if v.is_a?(Array) && v.first =~ /=/
+ ?; + v.join(";")
+ elsif v.is_a?(Array)
+ ?; + v.map{|v| "#{k}=#{v}"}.join(";")
+ else
+ v && v != '' ? ";#{k}=#{v}" : ";#{k}"
+ end
+ }.join
+ else
+ leader + return_value.map{|k,v| v}.join(joiner)
end
end
##
- # Expands a URI Template list operator.
+ # Takes a set of values, and joins them together based on the
+ # operator.
#
- # @param [String] argument The argument to the operator.
- # @param [Array] variables The variables the operator is working on.
- # @param [Hash] mapping The mapping of variables to values.
+ # @param [Hash, Array, String] value
+ # Normalizes keys and values with IDNA#unicode_normalize_kc
#
- # @return [String] The expanded result.
- def expand_list_operator(argument, variables, mapping, partial=false)
- if variables.size != 1
- raise InvalidTemplateOperatorError,
- "Template operator 'list' takes exactly one variable."
+ # @return [Hash, Array, String] The normalized values
+ def normalize_value(value)
+ unless value.is_a?(Hash)
+ value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
end
- if !partial || mapping[variables.first]
- values = mapping[variables.first]
- if values
- if values.kind_of?(Array)
- values.join(argument)
- else
- raise InvalidTemplateOperatorError,
- "Template operator 'list' only accepts Array values."
- end
- end
+
+ # Handle unicode normalization
+ if value.kind_of?(Array)
+ value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
+ elsif value.kind_of?(Hash)
+ value = value.inject({}) { |acc, (k, v)|
+ acc[Addressable::IDNA.unicode_normalize_kc(k)] =
+ Addressable::IDNA.unicode_normalize_kc(v)
+ acc
+ }
else
- "{-list|#{argument}|#{variables.first}}"
+ value = Addressable::IDNA.unicode_normalize_kc(value)
end
+ value
end
##
- # Parses a URI template expansion String.
+ # Generates a hash with string keys
#
- # @param [String] expansion The operator String.
- # @param [Hash] mapping An optional mapping to merge defaults into.
+ # @param [Hash] mapping A mapping hash to normalize
#
- # @return [Array]
- # A tuple of the operator, argument, variables, and mapping.
- def parse_template_expansion(capture, mapping={})
- operator, argument, variables = capture[1...-1].split("|", -1)
- operator.gsub!(/^\-/, "")
- variables = variables.split(",", -1)
- mapping = (variables.inject({}) do |accu, var|
- varname, _, vardefault = var.scan(/^(.+?)(=(.*))?$/)[0]
- accu[varname] = vardefault
+ # @return [Hash]
+ # A hash with stringified keys
+ def normalize_keys(mapping)
+ return mapping.inject({}) do |accu, pair|
+ name, value = pair
+ if Symbol === name
+ name = name.to_s
+ elsif name.respond_to?(:to_str)
+ name = name.to_str
+ else
+ raise TypeError,
+ "Can't convert #{name.class} into String."
+ end
+ accu[name] = value
accu
- end).merge(mapping)
- variables = variables.map { |var| var.gsub(/=.*$/, "") }
- return operator, argument, variables, mapping
+ end
end
##
@@ -832,216 +778,48 @@ def parse_template_pattern(pattern, processor=nil)
# Create a regular expression that captures the values of the
# variables in the URI.
- regexp_string = escaped_pattern.gsub(
- /#{OPERATOR_EXPANSION}|#{VARIABLE_EXPANSION}/
- ) do |expansion|
+ regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion|
+
expansions << expansion
- if expansion =~ OPERATOR_EXPANSION
- capture_group = "(.*)"
- operator, argument, names, _ =
- parse_template_expansion(expansion)
+ _, operator, varlist = *expansion.match(EXPRESSION)
+ leader = Regexp.escape(LEADERS.fetch(operator, ''))
+ joiner = Regexp.escape(JOINERS.fetch(operator, ','))
+ leader + varlist.split(',').map do |varspec|
+ _, name, modifier = *varspec.match(VARSPEC)
if processor != nil && processor.respond_to?(:match)
- # We can only lookup the match values for single variable
- # operator expansions. Besides, ".*" is usually the only
- # reasonable value for multivariate operators anyways.
- if ["prefix", "suffix", "list"].include?(operator)
- capture_group = "(#{processor.match(names.first)})"
+ "(#{ processor.match(name) })"
+ else
+ group = case operator
+ when ?+
+ "#{ RESERVED }*?"
+ when ?#
+ "#{ RESERVED }*?"
+ when ?/
+ "#{ UNRESERVED }*?"
+ when ?.
+ "#{ UNRESERVED.gsub('\.', '') }*?"
+ when ?;
+ "#{ UNRESERVED }*=?#{ UNRESERVED }*?"
+ when ??
+ "#{ UNRESERVED }*=#{ UNRESERVED }*?"
+ when ?&
+ "#{ UNRESERVED }*=#{ UNRESERVED }*?"
+ else
+ "#{ UNRESERVED }*?"
+ end
+ if modifier == ?*
+ "(#{group}(?:#{joiner}?#{group})*)?"
+ else
+ "(#{group})?"
end
- elsif operator == "prefix"
- capture_group = "(#{Regexp.escape(argument)}.*?)"
- elsif operator == "suffix"
- capture_group = "(.*?#{Regexp.escape(argument)})"
- end
- capture_group
- else
- capture_group = "(.*?)"
- if processor != nil && processor.respond_to?(:match)
- name = expansion[/\{([^\}=]+)(=[^\}]+)?\}/, 1]
- capture_group = "(#{processor.match(name)})"
end
- capture_group
- end
+ end.join("#{joiner}?")
end
# Ensure that the regular expression matches the whole URI.
regexp_string = "^#{regexp_string}$"
-
return expansions, Regexp.new(regexp_string)
end
- ##
- # Extracts a URI Template opt operator.
- #
- # @param [String] value The unparsed value to extract from.
- # @param [#restore] processor The processor object.
- # @param [String] argument The argument to the operator.
- # @param [Array] variables The variables the operator is working on.
- # @param [Hash] mapping The mapping of variables to values.
- #
- # @return [String] The extracted result.
- def extract_opt_operator(
- value, processor, argument, variables, mapping)
- if value != "" && value != argument
- raise TemplateOperatorAbortedError,
- "Value for template operator 'opt' was unexpected."
- end
- end
-
- ##
- # Extracts a URI Template neg operator.
- #
- # @param [String] value The unparsed value to extract from.
- # @param [#restore] processor The processor object.
- # @param [String] argument The argument to the operator.
- # @param [Array] variables The variables the operator is working on.
- # @param [Hash] mapping The mapping of variables to values.
- #
- # @return [String] The extracted result.
- def extract_neg_operator(
- value, processor, argument, variables, mapping)
- if value != "" && value != argument
- raise TemplateOperatorAbortedError,
- "Value for template operator 'neg' was unexpected."
- end
- end
-
- ##
- # Extracts a URI Template prefix operator.
- #
- # @param [String] value The unparsed value to extract from.
- # @param [#restore] processor The processor object.
- # @param [String] argument The argument to the operator.
- # @param [Array] variables The variables the operator is working on.
- # @param [Hash] mapping The mapping of variables to values.
- #
- # @return [String] The extracted result.
- def extract_prefix_operator(
- value, processor, argument, variables, mapping)
- if variables.size != 1
- raise InvalidTemplateOperatorError,
- "Template operator 'prefix' takes exactly one variable."
- end
- if value[0...argument.size] != argument
- raise TemplateOperatorAbortedError,
- "Value for template operator 'prefix' missing expected prefix."
- end
- values = value.split(argument, -1)
- values << "" if value[-argument.size..-1] == argument
- values.shift if values[0] == ""
- values.pop if values[-1] == ""
-
- if processor && processor.respond_to?(:restore)
- values.map! { |val| processor.restore(variables.first, val) }
- end
- values = values.first if values.size == 1
- if mapping[variables.first] == nil || mapping[variables.first] == values
- mapping[variables.first] = values
- else
- raise TemplateOperatorAbortedError,
- "Value mismatch for repeated variable."
- end
- end
-
- ##
- # Extracts a URI Template suffix operator.
- #
- # @param [String] value The unparsed value to extract from.
- # @param [#restore] processor The processor object.
- # @param [String] argument The argument to the operator.
- # @param [Array] variables The variables the operator is working on.
- # @param [Hash] mapping The mapping of variables to values.
- #
- # @return [String] The extracted result.
- def extract_suffix_operator(
- value, processor, argument, variables, mapping)
- if variables.size != 1
- raise InvalidTemplateOperatorError,
- "Template operator 'suffix' takes exactly one variable."
- end
- if value[-argument.size..-1] != argument
- raise TemplateOperatorAbortedError,
- "Value for template operator 'suffix' missing expected suffix."
- end
- values = value.split(argument, -1)
- values.pop if values[-1] == ""
- if processor && processor.respond_to?(:restore)
- values.map! { |val| processor.restore(variables.first, val) }
- end
- values = values.first if values.size == 1
- if mapping[variables.first] == nil || mapping[variables.first] == values
- mapping[variables.first] = values
- else
- raise TemplateOperatorAbortedError,
- "Value mismatch for repeated variable."
- end
- end
-
- ##
- # Extracts a URI Template join operator.
- #
- # @param [String] value The unparsed value to extract from.
- # @param [#restore] processor The processor object.
- # @param [String] argument The argument to the operator.
- # @param [Array] variables The variables the operator is working on.
- # @param [Hash] mapping The mapping of variables to values.
- #
- # @return [String] The extracted result.
- def extract_join_operator(value, processor, argument, variables, mapping)
- unparsed_values = value.split(argument)
- parsed_variables = []
- for unparsed_value in unparsed_values
- name = unparsed_value[/^(.+?)=(.+)$/, 1]
- parsed_variables << name
- parsed_value = unparsed_value[/^(.+?)=(.+)$/, 2]
- if processor && processor.respond_to?(:restore)
- parsed_value = processor.restore(name, parsed_value)
- end
- if mapping[name] == nil || mapping[name] == parsed_value
- mapping[name] = parsed_value
- else
- raise TemplateOperatorAbortedError,
- "Value mismatch for repeated variable."
- end
- end
- for variable in variables
- if !parsed_variables.include?(variable) && mapping[variable] != nil
- raise TemplateOperatorAbortedError,
- "Value mismatch for repeated variable."
- end
- end
- if (parsed_variables & variables) != parsed_variables
- raise TemplateOperatorAbortedError,
- "Template operator 'join' variable mismatch: " +
- "#{parsed_variables.inspect}, #{variables.inspect}"
- end
- end
-
- ##
- # Extracts a URI Template list operator.
- #
- # @param [String] value The unparsed value to extract from.
- # @param [#restore] processor The processor object.
- # @param [String] argument The argument to the operator.
- # @param [Array] variables The variables the operator is working on.
- # @param [Hash] mapping The mapping of variables to values.
- #
- # @return [String] The extracted result.
- def extract_list_operator(value, processor, argument, variables, mapping)
- if variables.size != 1
- raise InvalidTemplateOperatorError,
- "Template operator 'list' takes exactly one variable."
- end
- values = value.split(argument, -1)
- values.pop if values[-1] == ""
- if processor && processor.respond_to?(:restore)
- values.map! { |val| processor.restore(variables.first, val) }
- end
- if mapping[variables.first] == nil || mapping[variables.first] == values
- mapping[variables.first] = values
- else
- raise TemplateOperatorAbortedError,
- "Value mismatch for repeated variable."
- end
- end
end
end
diff --git a/lib/addressable/uri_template.rb b/lib/addressable/uri_template.rb
deleted file mode 100644
index 69f8bef8..00000000
--- a/lib/addressable/uri_template.rb
+++ /dev/null
@@ -1,636 +0,0 @@
-# encoding:utf-8
-#--
-# Copyright (C) 2006-2011 Bob Aman
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#++
-
-
-require "addressable/version"
-require "addressable/uri"
-require "addressable/template"
-
-module Addressable
- ##
- # This is an implementation of a URI template based on
- # RFC 6570 (http://tools.ietf.org/html/rfc6570).
- class UriTemplate < Template
- # Constants used throughout the template code.
- anything =
- Addressable::URI::CharacterClasses::RESERVED +
- Addressable::URI::CharacterClasses::UNRESERVED
-
-
- variable_char_class =
- Addressable::URI::CharacterClasses::ALPHA +
- Addressable::URI::CharacterClasses::DIGIT + ?_
-
- var_char =
- "(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
- RESERVED =
- "(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
- UNRESERVED =
- "(?:[#{
- Addressable::URI::CharacterClasses::UNRESERVED
- }]|%[a-fA-F0-9][a-fA-F0-9])"
- variable =
- "(?:#{var_char}(?:\\.?#{var_char})*)"
- varspec =
- "(?:(#{variable})(\\*|:\\d+)?)"
- VARNAME =
- /^#{variable}$/
- VARSPEC =
- /^#{varspec}$/
- VARIABLE_LIST =
- /^#{varspec}(?:,#{varspec})*$/
- operator =
- "+#./;?&=,!@|"
- EXPRESSION =
- /\{([#{operator}])?(#{varspec}(?:,#{varspec})*)\}/
-
-
- LEADERS = {?? => ??, ?/ => ?/, ?# => ?#, ?. => ?., ?; => ?;, ?& => ?&}
- JOINERS = {?? => ?&, ?. => ?., ?; => ?;, ?& => ?&, ?/ => ?/}
-
-
- ##
- # Extracts match data from the URI using a URI Template pattern.
- #
- # @param [Addressable::URI, #to_str] uri
- # The URI to extract from.
- #
- # @param [#restore, #match] processor
- # A template processor object may optionally be supplied.
- #
- # The object should respond to either the restore or
- # match messages or both. The restore method should
- # take two parameters: `[String] name` and `[String] value`.
- # The restore method should reverse any transformations that
- # have been performed on the value to ensure a valid URI.
- # The match method should take a single
- # parameter: `[String] name`. The match method should return
- # a String containing a regular expression capture group for
- # matching on that particular variable. The default value is `".*?"`.
- # The match method has no effect on multivariate operator
- # expansions.
- #
- # @return [Hash, NilClass]
- # The Hash mapping that was extracted from the URI, or
- # nil if the URI didn't match the template.
- #
- # @example
- # class ExampleProcessor
- # def self.restore(name, value)
- # return value.gsub(/\+/, " ") if name == "query"
- # return value
- # end
- #
- # def self.match(name)
- # return ".*?" if name == "first"
- # return ".*"
- # end
- # end
- #
- # uri = Addressable::URI.parse(
- # "http://example.com/search/an+example+search+query/"
- # )
- # match = Addressable::UriTemplate.new(
- # "http://example.com/search/{query}/"
- # ).match(uri, ExampleProcessor)
- # match.variables
- # #=> ["query"]
- # match.captures
- # #=> ["an example search query"]
- #
- # uri = Addressable::URI.parse("http://example.com/a/b/c/")
- # match = Addressable::UriTemplate.new(
- # "http://example.com/{first}/{+second}/"
- # ).match(uri, ExampleProcessor)
- # match.variables
- # #=> ["first", "second"]
- # match.captures
- # #=> ["a", "b/c"]
- #
- # uri = Addressable::URI.parse("http://example.com/a/b/c/")
- # match = Addressable::UriTemplate.new(
- # "http://example.com/{first}{/second*}/"
- # ).match(uri)
- # match.variables
- # #=> ["first", "second"]
- # match.captures
- # #=> ["a", ["b", "c"]]
- def match(uri, processor=nil)
- uri = Addressable::URI.parse(uri)
- mapping = {}
-
- # First, we need to process the pattern, and extract the values.
- expansions, expansion_regexp =
- parse_template_pattern(pattern, processor)
- unparsed_values = uri.to_str.scan(expansion_regexp).flatten
-
- if uri.to_str == pattern
- return Addressable::Template::MatchData.new(uri, self, mapping)
- elsif expansions.size > 0
- index = 0
- expansions.each do |expansion|
- _, operator, varlist = *expansion.match(EXPRESSION)
- varlist.split(',').each do |varspec|
- _, name, modifier = *varspec.match(VARSPEC)
- case operator
- when nil, ?+, ?#, ?/, ?.
- unparsed_value = unparsed_values[index]
- name = varspec[VARSPEC, 1]
- value = unparsed_value
- value = value.split(JOINERS[operator]) if value && modifier == ?*
- when ?;, ??, ?&
- if modifier == ?*
- value = unparsed_values[index].split(JOINERS[operator])
- value = value.inject({}) do |acc, v|
- key, val = v.split('=')
- val = "" if val.nil?
- acc[key] = val
- acc
- end
- else
- if (unparsed_values[index])
- name, value = unparsed_values[index].split('=')
- value = "" if value.nil?
- end
- end
- end
- if processor != nil && processor.respond_to?(:restore)
- value = processor.restore(name, value)
- end
- if processor == nil
- if value.is_a?(Hash)
- value = value.inject({}){|acc, (k, v)|
- acc[Addressable::URI.unencode_component(k)] =
- Addressable::URI.unencode_component(v)
- acc
- }
- elsif value.is_a?(Array)
- value = value.map{|v| Addressable::URI.unencode_component(v) }
- else
- value = Addressable::URI.unencode_component(value)
- end
- end
- if mapping[name] == nil || mapping[name] == value
- mapping[name] = value
- else
- return nil
- end
- index = index + 1
- end
- end
- return Addressable::Template::MatchData.new(uri, self, mapping)
- else
- return nil
- end
- end
-
- ##
- # Expands a URI template into another URI template.
- #
- # @param [Hash] mapping The mapping that corresponds to the pattern.
- # @param [#validate, #transform] processor
- # An optional processor object may be supplied.
- #
- # The object should respond to either the validate or
- # transform messages or both. Both the validate and
- # transform methods should take two parameters: name and
- # value. The validate method should return true
- # or false; true if the value of the variable is valid,
- # false otherwise. An InvalidTemplateValueError
- # exception will be raised if the value is invalid. The transform
- # method should return the transformed variable value as a String.
- # If a transform method is used, the value will not be percent
- # encoded automatically. Unicode normalization will be performed both
- # before and after sending the value to the transform method.
- #
- # @return [Addressable::UriTemplate] The partially expanded URI template.
- #
- # @example
- # Addressable::UriTemplate.new(
- # "http://example.com/{one}/{two}/"
- # ).partial_expand({"one" => "1"}).pattern
- # #=> "http://example.com/1/{two}/"
- #
- # Addressable::UriTemplate.new(
- # "http://example.com/{?one,two}/"
- # ).partial_expand({"one" => "1"}).pattern
- # #=> "http://example.com/?one=1{&two}/"
- #
- # Addressable::UriTemplate.new(
- # "http://example.com/{?one,two,three}/"
- # ).partial_expand({"one" => "1", "three" => 3}).pattern
- # #=> "http://example.com/?one=1{&two}&three=3"
- def partial_expand(mapping, processor=nil)
- result = self.pattern.dup
- result.gsub!( EXPRESSION ) do |capture|
- transform_partial_capture(mapping, capture, processor)
- end
- return Addressable::UriTemplate.new(result)
- end
-
- ##
- # Expands a URI template into a full URI.
- #
- # @param [Hash] mapping The mapping that corresponds to the pattern.
- # @param [#validate, #transform] processor
- # An optional processor object may be supplied.
- #
- # The object should respond to either the validate or
- # transform messages or both. Both the validate and
- # transform methods should take two parameters: name and
- # value. The validate method should return true
- # or false; true if the value of the variable is valid,
- # false otherwise. An InvalidTemplateValueError
- # exception will be raised if the value is invalid. The transform
- # method should return the transformed variable value as a String.
- # If a transform method is used, the value will not be percent
- # encoded automatically. Unicode normalization will be performed both
- # before and after sending the value to the transform method.
- #
- # @return [Addressable::URI] The expanded URI template.
- #
- # @example
- # class ExampleProcessor
- # def self.validate(name, value)
- # return !!(value =~ /^[\w ]+$/) if name == "query"
- # return true
- # end
- #
- # def self.transform(name, value)
- # return value.gsub(/ /, "+") if name == "query"
- # return value
- # end
- # end
- #
- # Addressable::UriTemplate.new(
- # "http://example.com/search/{query}/"
- # ).expand(
- # {"query" => "an example search query"},
- # ExampleProcessor
- # ).to_str
- # #=> "http://example.com/search/an+example+search+query/"
- #
- # Addressable::UriTemplate.new(
- # "http://example.com/search/{query}/"
- # ).expand(
- # {"query" => "an example search query"}
- # ).to_str
- # #=> "http://example.com/search/an%20example%20search%20query/"
- #
- # Addressable::UriTemplate.new(
- # "http://example.com/search/{query}/"
- # ).expand(
- # {"query" => "bogus!"},
- # ExampleProcessor
- # ).to_str
- # #=> Addressable::Template::InvalidTemplateValueError
- def expand(mapping, processor=nil)
- result = self.pattern.dup
- mapping = normalize_keys(mapping)
- result.gsub!( EXPRESSION ) do |capture|
- transform_capture(mapping, capture, processor)
- end
- return Addressable::URI.parse(result)
- end
-
-
- private
- def ordered_variable_defaults
- @ordered_variable_defaults ||= (
- expansions, expansion_regexp = parse_template_pattern(pattern)
- expansions.map do |capture|
- _, operator, varlist = *capture.match(EXPRESSION)
- varlist.split(',').map do |varspec|
- name = varspec[VARSPEC, 1]
- end
- end.flatten
- )
- end
-
-
- ##
- # Loops through each capture and expands any values available in mapping
- #
- # @param [Hash] mapping
- # Set of keys to expand
- # @param [String] capture
- # The expression to expand
- # @param [#validate, #transform] processor
- # An optional processor object may be supplied.
- #
- # The object should respond to either the validate or
- # transform messages or both. Both the validate and
- # transform methods should take two parameters: name and
- # value. The validate method should return true
- # or false; true if the value of the variable is valid,
- # false otherwise. An InvalidTemplateValueError exception
- # will be raised if the value is invalid. The transform method
- # should return the transformed variable value as a String. If a
- # transform method is used, the value will not be percent encoded
- # automatically. Unicode normalization will be performed both before and
- # after sending the value to the transform method.
- #
- # @return [String] The expanded expression
- def transform_partial_capture(mapping, capture, processor = nil)
- _, operator, varlist = *capture.match(EXPRESSION)
- is_first = true
- varlist.split(',').inject('') do |acc, varspec|
- _, name, modifier = *varspec.match(VARSPEC)
- value = mapping[name]
- if value
- operator = ?& if !is_first && operator == ??
- acc << transform_capture(mapping, "{#{operator}#{varspec}}", processor)
- else
- operator = ?& if !is_first && operator == ??
- acc << "{#{operator}#{varspec}}"
- end
- is_first = false
- acc
- end
- end
-
- ##
- # Transforms a mapped value so that values can be substituted into the
- # template.
- #
- # @param [Hash] mapping The mapping to replace captures
- # @param [String] capture
- # The expression to replace
- # @param [#validate, #transform] processor
- # An optional processor object may be supplied.
- #
- # The object should respond to either the validate or
- # transform messages or both. Both the validate and
- # transform methods should take two parameters: name and
- # value. The validate method should return true
- # or false; true if the value of the variable is valid,
- # false otherwise. An InvalidTemplateValueError exception
- # will be raised if the value is invalid. The transform method
- # should return the transformed variable value as a String. If a
- # transform method is used, the value will not be percent encoded
- # automatically. Unicode normalization will be performed both before and
- # after sending the value to the transform method.
- #
- # @return [String] The expanded expression
- def transform_capture(mapping, capture, processor=nil)
- _, operator, varlist = *capture.match(EXPRESSION)
- return_value = varlist.split(',').inject([]) do |acc, varspec|
- _, name, modifier = *varspec.match(VARSPEC)
- value = mapping[name]
- unless value == nil || value == {}
- allow_reserved = %w(+ #).include?(operator)
- value = value.to_s if Numeric === value || Symbol === value
- length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/
-
- unless (Hash === value) ||
- value.respond_to?(:to_ary) || value.respond_to?(:to_str)
- raise TypeError,
- "Can't convert #{value.class} into String or Array."
- end
-
- value = normalize_value(value)
-
- if processor == nil || !processor.respond_to?(:transform)
- # Handle percent escaping
- if allow_reserved
- encode_map =
- Addressable::URI::CharacterClasses::RESERVED +
- Addressable::URI::CharacterClasses::UNRESERVED
- else
- encode_map = Addressable::URI::CharacterClasses::UNRESERVED
- end
- if value.kind_of?(Array)
- transformed_value = value.map do |val|
- if length
- Addressable::URI.encode_component(val[0...length], encode_map)
- else
- Addressable::URI.encode_component(val, encode_map)
- end
- end
- unless modifier == "*"
- transformed_value = transformed_value.join(',')
- end
- elsif value.kind_of?(Hash)
- transformed_value = value.map do |key, val|
- if modifier == "*"
- "#{
- Addressable::URI.encode_component( key, encode_map)
- }=#{
- Addressable::URI.encode_component( val, encode_map)
- }"
- else
- "#{
- Addressable::URI.encode_component( key, encode_map)
- },#{
- Addressable::URI.encode_component( val, encode_map)
- }"
- end
- end
- unless modifier == "*"
- transformed_value = transformed_value.join(',')
- end
- else
- if length
- transformed_value = Addressable::URI.encode_component(
- value[0...length], encode_map)
- else
- transformed_value = Addressable::URI.encode_component(
- value, encode_map)
- end
- end
- end
-
- # Process, if we've got a processor
- if processor != nil
- if processor.respond_to?(:validate)
- if !processor.validate(name, value)
- display_value = value.kind_of?(Array) ? value.inspect : value
- raise InvalidTemplateValueError,
- "#{name}=#{display_value} is an invalid template value."
- end
- end
- if processor.respond_to?(:transform)
- transformed_value = processor.transform(name, value)
- transformed_value = normalize_value(transformed_value)
- end
- end
- acc << [name, transformed_value]
- end
- acc
- end
- return "" if return_value.empty?
- join_values(operator, return_value)
- end
-
- ##
- # Takes a set of values, and joins them together based on the
- # operator.
- #
- # @param [String, Nil] operator One of the operators from the set
- # (?,&,+,#,;,/,.), or nil if there wasn't one.
- # @param [Array] return_value
- # The set of return values (as [variable_name, value] tuples) that will
- # be joined together.
- #
- # @return [String] The transformed mapped value
- def join_values(operator, return_value)
- leader = LEADERS.fetch(operator, '')
- joiner = JOINERS.fetch(operator, ',')
- case operator
- when ?&, ??
- leader + return_value.map{|k,v|
- if v.is_a?(Array) && v.first =~ /=/
- v.join(joiner)
- elsif v.is_a?(Array)
- v.map{|v| "#{k}=#{v}"}.join(joiner)
- else
- "#{k}=#{v}"
- end
- }.join(joiner)
- when ?;
- return_value.map{|k,v|
- if v.is_a?(Array) && v.first =~ /=/
- ?; + v.join(";")
- elsif v.is_a?(Array)
- ?; + v.map{|v| "#{k}=#{v}"}.join(";")
- else
- v && v != '' ? ";#{k}=#{v}" : ";#{k}"
- end
- }.join
- else
- leader + return_value.map{|k,v| v}.join(joiner)
- end
- end
-
- ##
- # Takes a set of values, and joins them together based on the
- # operator.
- #
- # @param [Hash, Array, String] value
- # Normalizes keys and values with IDNA#unicode_normalize_kc
- #
- # @return [Hash, Array, String] The normalized values
- def normalize_value(value)
- unless value.is_a?(Hash)
- value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
- end
-
- # Handle unicode normalization
- if value.kind_of?(Array)
- value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
- elsif value.kind_of?(Hash)
- value = value.inject({}) { |acc, (k, v)|
- acc[Addressable::IDNA.unicode_normalize_kc(k)] =
- Addressable::IDNA.unicode_normalize_kc(v)
- acc
- }
- else
- value = Addressable::IDNA.unicode_normalize_kc(value)
- end
- value
- end
-
- ##
- # Generates a hash with string keys
- #
- # @param [Hash] mapping A mapping hash to normalize
- #
- # @return [Hash]
- # A hash with stringified keys
- def normalize_keys(mapping)
- return mapping.inject({}) do |accu, pair|
- name, value = pair
- if Symbol === name
- name = name.to_s
- elsif name.respond_to?(:to_str)
- name = name.to_str
- else
- raise TypeError,
- "Can't convert #{name.class} into String."
- end
- accu[name] = value
- accu
- end
- end
-
- ##
- # Generates the Regexp that parses a template pattern.
- #
- # @param [String] pattern The URI template pattern.
- # @param [#match] processor The template processor to use.
- #
- # @return [Regexp]
- # A regular expression which may be used to parse a template pattern.
- def parse_template_pattern(pattern, processor=nil)
- # Escape the pattern. The two gsubs restore the escaped curly braces
- # back to their original form. Basically, escape everything that isn't
- # within an expansion.
- escaped_pattern = Regexp.escape(
- pattern
- ).gsub(/\\\{(.*?)\\\}/) do |escaped|
- escaped.gsub(/\\(.)/, "\\1")
- end
-
- expansions = []
-
- # Create a regular expression that captures the values of the
- # variables in the URI.
- regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion|
-
- expansions << expansion
- _, operator, varlist = *expansion.match(EXPRESSION)
- leader = Regexp.escape(LEADERS.fetch(operator, ''))
- joiner = Regexp.escape(JOINERS.fetch(operator, ','))
- leader + varlist.split(',').map do |varspec|
- _, name, modifier = *varspec.match(VARSPEC)
- if processor != nil && processor.respond_to?(:match)
- "(#{ processor.match(name) })"
- else
- group = case operator
- when ?+
- "#{ RESERVED }*?"
- when ?#
- "#{ RESERVED }*?"
- when ?/
- "#{ UNRESERVED }*?"
- when ?.
- "#{ UNRESERVED.gsub('\.', '') }*?"
- when ?;
- "#{ UNRESERVED }*=?#{ UNRESERVED }*?"
- when ??
- "#{ UNRESERVED }*=#{ UNRESERVED }*?"
- when ?&
- "#{ UNRESERVED }*=#{ UNRESERVED }*?"
- else
- "#{ UNRESERVED }*?"
- end
- if modifier == ?*
- "(#{group}(?:#{joiner}?#{group})*)?"
- else
- "(#{group})?"
- end
- end
- end.join("#{joiner}?")
- end
-
- # Ensure that the regular expression matches the whole URI.
- regexp_string = "^#{regexp_string}$"
- return expansions, Regexp.new(regexp_string)
- end
-
- end
-end
diff --git a/spec/addressable/template_spec.rb b/spec/addressable/template_spec.rb
index 1fb41b58..2a5195fb 100644
--- a/spec/addressable/template_spec.rb
+++ b/spec/addressable/template_spec.rb
@@ -1,2144 +1,758 @@
-# coding: utf-8
-# Copyright (C) 2006-2011 Bob Aman
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
require "addressable/template"
-if !"".respond_to?("force_encoding")
- class String
- def force_encoding(encoding)
- @encoding = encoding
- end
-
- def encoding
- @encoding ||= Encoding::ASCII_8BIT
- end
- end
-
- class Encoding
- def initialize(name)
- @name = name
- end
-
- def to_s
- return @name
+shared_examples_for 'expands' do |tests|
+ tests.each do |template, expansion|
+ it "#{template} to #{expansion}" do
+ tmpl = Addressable::Template.new(template).expand(subject)
+ tmpl.to_str.should == expansion
end
-
- UTF_8 = Encoding.new("UTF-8")
- ASCII_8BIT = Encoding.new("US-ASCII")
- end
-end
-
-class ExampleProcessor
- def self.validate(name, value)
- return !!(value =~ /^[\w ]+$/) if name == "query"
- return true
- end
-
- def self.transform(name, value)
- return value.gsub(/ /, "+") if name == "query"
- return value
- end
-
- def self.restore(name, value)
- return value.gsub(/\+/, " ") if name == "query"
- return value.tr("A-Za-z", "N-ZA-Mn-za-m") if name == "rot13"
- return value
- end
-
- def self.match(name)
- return ".*?" if name == "first"
- return ".*"
- end
-end
-
-class SlashlessProcessor
- def self.match(name)
- return "[^/\\n]*"
- end
-end
-
-class NoOpProcessor
- def self.transform(name, value)
- value
- end
-end
-
-describe Addressable::Template do
- it "should raise a TypeError for invalid patterns" do
- (lambda do
- Addressable::Template.new(42)
- end).should raise_error(TypeError, "Can't convert Fixnum into String.")
- end
-end
-
-describe Addressable::Template, "created with the pattern '/'" do
- before do
- @template = Addressable::Template.new("/")
- end
-
- it "should have no variables" do
- @template.variables.should be_empty
- end
-
- it "should have the correct mapping when extracting from '/'" do
- @template.extract("/").should == {}
- end
-end
-
-describe Addressable::URI, "when parsed from '/one/'" do
- before do
- @uri = Addressable::URI.parse("/one/")
- end
-
- it "should not match the pattern '/two/'" do
- Addressable::Template.new("/two/").extract(@uri).should == nil
- end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern '/{number}/'" do
- Addressable::Template.new(
- "/{number}/"
- ).extract(@uri).should == {"number" => "one"}
- end
-end
-
-describe Addressable::Template, "created with the pattern '/{number}/'" do
- before do
- @template = Addressable::Template.new("/{number}/")
- end
-
- it "should have the variables ['number']" do
- @template.variables.should == ["number"]
- end
-
- it "should not match the pattern '/'" do
- @template.match("/").should == nil
- end
-
- it "should match the pattern '/two/'" do
- @template.match("/two/").mapping.should == {"number" => "two"}
- end
-end
-
-describe Addressable::Template,
- "created with the pattern '/{number}/{number}/'" do
- before do
- @template = Addressable::Template.new("/{number}/{number}/")
- end
-
- it "should have one variable" do
- @template.variables.should == ["number"]
- end
-
- it "should have the correct mapping when extracting from '/1/1/'" do
- @template.extract("/1/1/").should == {"number" => "1"}
- end
-
- it "should not match '/1/2/'" do
- @template.match("/1/2/").should == nil
- end
-
- it "should not match '/2/1/'" do
- @template.match("/2/1/").should == nil
- end
-
- it "should not match '/1/'" do
- @template.match("/1/").should == nil
- end
-
- it "should not match '/1/1/1/'" do
- @template.match("/1/1/1/").should == nil
- end
-
- it "should not match '/1/2/3/'" do
- @template.match("/1/2/3/").should == nil
end
end
-describe Addressable::Template,
- "created with the pattern '/{number}{-prefix|.|number}'" do
- before do
- @template = Addressable::Template.new("/{number}{-prefix|.|number}")
- end
-
- it "should have one variable" do
- @template.variables.should == ["number"]
- end
-
- it "should have the correct mapping when extracting from '/1.1'" do
- @template.extract("/1.1").should == {"number" => "1"}
- end
-
- it "should have the correct mapping when extracting from '/99.99'" do
- @template.extract("/99.99").should == {"number" => "99"}
- end
-
- it "should not match '/1.2'" do
- @template.match("/1.2").should == nil
- end
-
- it "should not match '/2.1'" do
- @template.match("/2.1").should == nil
- end
-
- it "should not match '/1'" do
- @template.match("/1").should == nil
- end
-
- it "should not match '/1.1.1'" do
- @template.match("/1.1.1").should == nil
- end
-
- it "should not match '/1.23'" do
- @template.match("/1.23").should == nil
- end
+describe "Level 1:" do
+ subject{
+ {:var => "value", :hello => "Hello World!"}
+ }
+ it_behaves_like 'expands', {
+ '{var}' => 'value',
+ '{hello}' => 'Hello%20World%21'
+ }
end
-describe Addressable::Template,
- "created with the pattern '/{number}/{-suffix|/|number}'" do
- before do
- @template = Addressable::Template.new("/{number}/{-suffix|/|number}")
- end
-
- it "should have one variable" do
- @template.variables.should == ["number"]
- end
-
- it "should have the correct mapping when extracting from '/1/1/'" do
- @template.extract("/1/1/").should == {"number" => "1"}
- end
-
- it "should have the correct mapping when extracting from '/99/99/'" do
- @template.extract("/99/99/").should == {"number" => "99"}
- end
-
- it "should not match '/1/1'" do
- @template.match("/1/1").should == nil
- end
-
- it "should not match '/11/'" do
- @template.match("/11/").should == nil
- end
-
- it "should not match '/1/2/'" do
- @template.match("/1/2/").should == nil
- end
-
- it "should not match '/2/1/'" do
- @template.match("/2/1/").should == nil
- end
-
- it "should not match '/1/'" do
- @template.match("/1/").should == nil
- end
-
- it "should not match '/1/1/1/'" do
- @template.match("/1/1/1/").should == nil
+describe "Level 2" do
+ subject{
+ {
+ :var => "value",
+ :hello => "Hello World!",
+ :path => "/foo/bar"
+ }
+ }
+ context "Operator +:" do
+ it_behaves_like 'expands', {
+ '{+var}' => 'value',
+ '{+hello}' => 'Hello%20World!',
+ '{+path}/here' => '/foo/bar/here',
+ 'here?ref={+path}' => 'here?ref=/foo/bar'
+ }
end
-
- it "should not match '/1/23/'" do
- @template.match("/1/23/").should == nil
+ context "Operator #:" do
+ it_behaves_like 'expands', {
+ 'X{#var}' => 'X#value',
+ 'X{#hello}' => 'X#Hello%20World!',
+ }
end
end
-describe Addressable::Template,
- "created with the pattern '/{number}/?{-join|&|number}'" do
- before do
- @template = Addressable::Template.new(
- "/{number}/?{-join|&|number,letter}"
- )
- end
-
- it "should have one variable" do
- @template.variables.should == ["number", "letter"]
- end
-
- it "should have the correct mapping when extracting from '/1/?number=1'" do
- @template.extract("/1/?number=1").should == {"number" => "1"}
- end
-
- it "should have the correct mapping when extracting " +
- "from '/99/?number=99'" do
- @template.extract("/99/?number=99").should == {"number" => "99"}
- end
-
- it "should have the correct mapping when extracting " +
- "from '/1/?number=1&letter=a'" do
- @template.extract("/1/?number=1&letter=a").should == {
- "number" => "1", "letter" => "a"
+describe "Level 3" do
+ subject{
+ {
+ :var => "value",
+ :hello => "Hello World!",
+ :empty => "",
+ :path => "/foo/bar",
+ :x => "1024",
+ :y => "768"
+ }
+ }
+ context "Operator nil (multiple vars):" do
+ it_behaves_like 'expands', {
+ 'map?{x,y}' => 'map?1024,768',
+ '{x,hello,y}' => '1024,Hello%20World%21,768'
}
end
-
- it "should not match '/1/?number=1&bogus=foo'" do
- @template.match("/1/?number=1&bogus=foo").should == nil
- end
-
- it "should not match '/1/?number=2'" do
- @template.match("/1/?number=2").should == nil
- end
-
- it "should not match '/2/?number=1'" do
- @template.match("/2/?number=1").should == nil
- end
-
- it "should not match '/1/?'" do
- @template.match("/1/?").should == nil
- end
-end
-
-describe Addressable::Template,
- "created with the pattern '/{number}/{-list|/|number}/'" do
- before do
- @template = Addressable::Template.new("/{number}/{-list|/|number}/")
+ context "Operator + (multiple vars):" do
+ it_behaves_like 'expands', {
+ '{+x,hello,y}' => '1024,Hello%20World!,768',
+ '{+path,x}/here' => '/foo/bar,1024/here',
+ }
end
-
- it "should have one variable" do
- @template.variables.should == ["number"]
+ context "Operator # (multiple vars):" do
+ it_behaves_like 'expands', {
+ '{#x,hello,y}' => '#1024,Hello%20World!,768',
+ '{#path,x}/here' => '#/foo/bar,1024/here',
+ }
end
-
- it "should not match '/1/1/'" do
- @template.match("/1/1/").should == nil
+ context "Operator ." do
+ it_behaves_like 'expands', {
+ 'X{.var}' => 'X.value',
+ 'X{.x,y}' => 'X.1024.768',
+ }
end
-
- it "should not match '/1/2/'" do
- @template.match("/1/2/").should == nil
+ context "Operator /" do
+ it_behaves_like 'expands', {
+ '{/var}' => '/value',
+ '{/var,x}/here' => '/value/1024/here',
+ }
end
-
- it "should not match '/2/1/'" do
- @template.match("/2/1/").should == nil
+ context "Operator ;" do
+ it_behaves_like 'expands', {
+ '{;x,y}' => ';x=1024;y=768',
+ '{;x,y,empty}' => ';x=1024;y=768;empty',
+ }
end
-
- it "should not match '/1/1/1/'" do
- @template.match("/1/1/1/").should == nil
+ context "Operator ?" do
+ it_behaves_like 'expands', {
+ '{?x,y}' => '?x=1024&y=768',
+ '{?x,y,empty}' => '?x=1024&y=768&empty=',
+ }
end
-
- it "should not match '/1/1/1/1/'" do
- @template.match("/1/1/1/1/").should == nil
+ context "Operator &" do
+ it_behaves_like 'expands', {
+ '?fixed=yes{&x}' => '?fixed=yes&x=1024',
+ '{&x,y,empty}' => '&x=1024&y=768&empty=',
+ }
end
end
-
-describe Addressable::Template, "created with the pattern " +
- "'http://www.example.com/?{-join|&|query,number}'" do
- before do
- @template = Addressable::Template.new(
- "http://www.example.com/?{-join|&|query,number}"
- )
- end
-
- it "when inspected, should have the correct class name" do
- @template.inspect.should include("Addressable::Template")
- end
-
- it "when inspected, should have the correct object id" do
- @template.inspect.should include("%#0x" % @template.object_id)
- end
-
- it "should have the variables ['query', 'number']" do
- @template.variables.should == ["query", "number"]
- end
-
- it "should not match the pattern 'http://www.example.com/'" do
- @template.match("http://www.example.com/").should == nil
+describe "Level 4" do
+ subject{
+ {
+ :var => "value",
+ :hello => "Hello World!",
+ :path => "/foo/bar",
+ :semi => ";",
+ :list => %w(red green blue),
+ :keys => {"semi" => ';', "dot" => '.', "comma" => ','}
+ }
+ }
+ context "Expansion with value modifiers" do
+ it_behaves_like 'expands', {
+ '{var:3}' => 'val',
+ '{var:30}' => 'value',
+ '{list}' => 'red,green,blue',
+ '{list*}' => 'red,green,blue',
+ '{keys}' => 'semi,%3B,dot,.,comma,%2C',
+ '{keys*}' => 'semi=%3B,dot=.,comma=%2C',
+ }
end
-
- it "should match the pattern 'http://www.example.com/?'" do
- @template.match("http://www.example.com/?").mapping.should == {}
+ context "Operator + with value modifiers" do
+ it_behaves_like 'expands', {
+ '{+path:6}/here' => '/foo/b/here',
+ '{+list}' => 'red,green,blue',
+ '{+list*}' => 'red,green,blue',
+ '{+keys}' => 'semi,;,dot,.,comma,,',
+ '{+keys*}' => 'semi=;,dot=.,comma=,',
+ }
end
-
- it "should match the pattern " +
- "'http://www.example.com/?query=mycelium'" do
- match = @template.match(
- "http://www.example.com/?query=mycelium"
- )
- match.variables.should == ["query", "number"]
- match.values.should == ["mycelium", nil]
- match.mapping.should == {"query" => "mycelium"}
- match.inspect.should =~ /MatchData/
+ context "Operator # with value modifiers" do
+ it_behaves_like 'expands', {
+ '{#path:6}/here' => '#/foo/b/here',
+ '{#list}' => '#red,green,blue',
+ '{#list*}' => '#red,green,blue',
+ '{#keys}' => '#semi,;,dot,.,comma,,',
+ '{#keys*}' => '#semi=;,dot=.,comma=,',
+ }
end
-
- it "should match the pattern " +
- "'http://www.example.com/?query=mycelium&number=100'" do
- @template.match(
- "http://www.example.com/?query=mycelium&number=100"
- ).mapping.should == {"query" => "mycelium", "number" => "100"}
+ context "Operator . with value modifiers" do
+ it_behaves_like 'expands', {
+ 'X{.var:3}' => 'X.val',
+ 'X{.list}' => 'X.red,green,blue',
+ 'X{.list*}' => 'X.red.green.blue',
+ 'X{.keys}' => 'X.semi,%3B,dot,.,comma,%2C',
+ 'X{.keys*}' => 'X.semi=%3B.dot=..comma=%2C',
+ }
end
-end
-
-describe Addressable::URI, "when parsed from '/one/two/'" do
- before do
- @uri = Addressable::URI.parse("/one/two/")
+ context "Operator / with value modifiers" do
+ it_behaves_like 'expands', {
+ '{/var:1,var}' => '/v/value',
+ '{/list}' => '/red,green,blue',
+ '{/list*}' => '/red/green/blue',
+ '{/list*,path:4}' => '/red/green/blue/%2Ffoo',
+ '{/keys}' => '/semi,%3B,dot,.,comma,%2C',
+ '{/keys*}' => '/semi=%3B/dot=./comma=%2C',
+ }
end
-
- it "should not match the pattern '/{number}/' " +
- "with the SlashlessProcessor" do
- Addressable::Template.new(
- "/{number}/"
- ).extract(@uri, SlashlessProcessor).should == nil
+ context "Operator ; with value modifiers" do
+ it_behaves_like 'expands', {
+ '{;hello:5}' => ';hello=Hello',
+ '{;list}' => ';list=red,green,blue',
+ '{;list*}' => ';list=red;list=green;list=blue',
+ '{;keys}' => ';keys=semi,%3B,dot,.,comma,%2C',
+ '{;keys*}' => ';semi=%3B;dot=.;comma=%2C',
+ }
end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern '/{number}/' without a processor" do
- Addressable::Template.new("/{number}/").extract(@uri).should == {
- "number" => "one/two"
+ context "Operator ? with value modifiers" do
+ it_behaves_like 'expands', {
+ '{?var:3}' => '?var=val',
+ '{?list}' => '?list=red,green,blue',
+ '{?list*}' => '?list=red&list=green&list=blue',
+ '{?keys}' => '?keys=semi,%3B,dot,.,comma,%2C',
+ '{?keys*}' => '?semi=%3B&dot=.&comma=%2C',
}
end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern '/{first}/{second}/' with the SlashlessProcessor" do
- Addressable::Template.new(
- "/{first}/{second}/"
- ).extract(@uri, SlashlessProcessor).should == {
- "first" => "one",
- "second" => "two"
+ context "Operator & with value modifiers" do
+ it_behaves_like 'expands', {
+ '{&var:3}' => '&var=val',
+ '{&list}' => '&list=red,green,blue',
+ '{&list*}' => '&list=red&list=green&list=blue',
+ '{&keys}' => '&keys=semi,%3B,dot,.,comma,%2C',
+ '{&keys*}' => '&semi=%3B&dot=.&comma=%2C',
}
end
end
-
-describe Addressable::URI, "when parsed from " +
- "'http://example.com/search/an+example+search+query/'" do
- before do
- @uri = Addressable::URI.parse(
- "http://example.com/search/an+example+search+query/")
- end
-
- it "should have the correct mapping when extracting values using " +
- "the pattern 'http://example.com/search/{query}/' with the " +
- "ExampleProcessor" do
- Addressable::Template.new(
- "http://example.com/search/{query}/"
- ).extract(@uri, ExampleProcessor).should == {
- "query" => "an example search query"
+describe "Modifiers" do
+ subject{
+ {
+ :var => "value",
+ :semi => ";",
+ :year => %w(1965 2000 2012),
+ :dom => %w(example com)
}
- end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/search/{-list|+|query}/'" do
- Addressable::Template.new(
- "http://example.com/search/{-list|+|query}/"
- ).extract(@uri).should == {
- "query" => ["an", "example", "search", "query"]
+ }
+ context "length" do
+ it_behaves_like 'expands', {
+ '{var:3}' => 'val',
+ '{var:30}' => 'value',
+ '{var}' => 'value',
+ '{semi}' => '%3B',
+ '{semi:2}' => '%3B',
}
end
-
- it "should return nil when extracting values using " +
- "a non-matching pattern" do
- Addressable::Template.new(
- "http://bogus.com/{thingy}/"
- ).extract(@uri).should == nil
+ context "explode" do
+ it_behaves_like 'expands', {
+ 'find{?year*}' => 'find?year=1965&year=2000&year=2012',
+ 'www{.dom*}' => 'www.example.com',
+ }
end
end
-
-describe Addressable::URI, "when parsed from " +
- "'http://example.com/a/b/c/'" do
- before do
- @uri = Addressable::URI.parse(
- "http://example.com/a/b/c/")
- end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/{first}/{second}/' with the ExampleProcessor" do
- Addressable::Template.new(
- "http://example.com/{first}/{second}/"
- ).extract(@uri, ExampleProcessor).should == {
- "first" => "a",
- "second" => "b/c"
+describe "Expansion" do
+ subject{
+ {
+ :count => [ "one", "two", "three" ],
+ :dom => [ "example", "com" ],
+ :dub => "me/too",
+ :hello => "Hello World!",
+ :half => "50%",
+ :var => "value",
+ :who => "fred",
+ :base => "http://example.com/home/",
+ :path => "/foo/bar",
+ :list => [ "red", "green", "blue" ],
+ :keys => { "semi"=>";","dot"=>".","comma"=>"," },
+ :v => "6",
+ :x => "1024",
+ :y => "768",
+ :empty => "",
+ :empty_keys => {},
+ :undef => nil,
}
- end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/{first}/{-list|/|second}/'" do
- Addressable::Template.new(
- "http://example.com/{first}/{-list|/|second}/"
- ).extract(@uri).should == {
- "first" => "a",
- "second" => ["b", "c"]
+ }
+ context "concatenation" do
+ it_behaves_like 'expands', {
+ '{count}' => 'one,two,three',
+ '{count*}' => 'one,two,three',
+ '{/count}' => '/one,two,three',
+ '{/count*}' => '/one/two/three',
+ '{;count}' => ';count=one,two,three',
+ '{;count*}' => ';count=one;count=two;count=three',
+ '{?count}' => '?count=one,two,three',
+ '{?count*}' => '?count=one&count=two&count=three',
+ '{&count*}' => '&count=one&count=two&count=three',
}
end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/{first}/{-list|/|rot13}/' " +
- "with the ExampleProcessor" do
- Addressable::Template.new(
- "http://example.com/{first}/{-list|/|rot13}/"
- ).extract(@uri, ExampleProcessor).should == {
- "first" => "a",
- "rot13" => ["o", "p"]
+ context "simple expansion" do
+ it_behaves_like 'expands', {
+ '{var}' => 'value',
+ '{hello}' => 'Hello%20World%21',
+ '{half}' => '50%25',
+ 'O{empty}X' => 'OX',
+ 'O{undef}X' => 'OX',
+ '{x,y}' => '1024,768',
+ '{x,hello,y}' => '1024,Hello%20World%21,768',
+ '?{x,empty}' => '?1024,',
+ '?{x,undef}' => '?1024',
+ '?{undef,y}' => '?768',
+ '{var:3}' => 'val',
+ '{var:30}' => 'value',
+ '{list}' => 'red,green,blue',
+ '{list*}' => 'red,green,blue',
+ '{keys}' => 'semi,%3B,dot,.,comma,%2C',
+ '{keys*}' => 'semi=%3B,dot=.,comma=%2C',
}
end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/{-list|/|rot13}/' " +
- "with the ExampleProcessor" do
- Addressable::Template.new(
- "http://example.com/{-list|/|rot13}/"
- ).extract(@uri, ExampleProcessor).should == {
- "rot13" => ["n", "o", "p"]
+ context "reserved expansion (+)" do
+ it_behaves_like 'expands', {
+ '{+var}' => 'value',
+ '{+hello}' => 'Hello%20World!',
+ '{+half}' => '50%25',
+ '{base}index' => 'http%3A%2F%2Fexample.com%2Fhome%2Findex',
+ '{+base}index' => 'http://example.com/home/index',
+ 'O{+empty}X' => 'OX',
+ 'O{+undef}X' => 'OX',
+ '{+path}/here' => '/foo/bar/here',
+ 'here?ref={+path}' => 'here?ref=/foo/bar',
+ 'up{+path}{var}/here' => 'up/foo/barvalue/here',
+ '{+x,hello,y}' => '1024,Hello%20World!,768',
+ '{+path,x}/here' => '/foo/bar,1024/here',
+ '{+path:6}/here' => '/foo/b/here',
+ '{+list}' => 'red,green,blue',
+ '{+list*}' => 'red,green,blue',
+ '{+keys}' => 'semi,;,dot,.,comma,,',
+ '{+keys*}' => 'semi=;,dot=.,comma=,',
}
end
-
- it "should not map to anything when extracting values " +
- "using the pattern " +
- "'http://example.com/{-list|/|rot13}/'" do
- Addressable::Template.new(
- "http://example.com/{-join|/|a,b,c}/"
- ).extract(@uri).should == nil
- end
-end
-
-describe Addressable::URI, "when parsed from " +
- "'http://example.com/?a=one&b=two&c=three'" do
- before do
- @uri = Addressable::URI.parse("http://example.com/?a=one&b=two&c=three")
- end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/?{-join|&|a,b,c}'" do
- Addressable::Template.new(
- "http://example.com/?{-join|&|a,b,c}"
- ).extract(@uri).should == {
- "a" => "one",
- "b" => "two",
- "c" => "three"
+ context "fragment expansion (#)" do
+ it_behaves_like 'expands', {
+ '{#var}' => '#value',
+ '{#hello}' => '#Hello%20World!',
+ '{#half}' => '#50%25',
+ 'foo{#empty}' => 'foo#',
+ 'foo{#undef}' => 'foo',
+ '{#x,hello,y}' => '#1024,Hello%20World!,768',
+ '{#path,x}/here' => '#/foo/bar,1024/here',
+ '{#path:6}/here' => '#/foo/b/here',
+ '{#list}' => '#red,green,blue',
+ '{#list*}' => '#red,green,blue',
+ '{#keys}' => '#semi,;,dot,.,comma,,',
+ '{#keys*}' => '#semi=;,dot=.,comma=,',
}
end
-end
-
-describe Addressable::URI, "when parsed from " +
- "'http://example.com/?rot13=frperg'" do
- before do
- @uri = Addressable::URI.parse("http://example.com/?rot13=frperg")
- end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/?{-join|&|rot13}' with the ExampleProcessor" do
- Addressable::Template.new(
- "http://example.com/?{-join|&|rot13}"
- ).extract(@uri, ExampleProcessor).should == {
- "rot13" => "secret"
+ context "label expansion (.)" do
+ it_behaves_like 'expands', {
+ '{.who}' => '.fred',
+ '{.who,who}' => '.fred.fred',
+ '{.half,who}' => '.50%25.fred',
+ 'www{.dom*}' => 'www.example.com',
+ 'X{.var}' => 'X.value',
+ 'X{.empty}' => 'X.',
+ 'X{.undef}' => 'X',
+ 'X{.var:3}' => 'X.val',
+ 'X{.list}' => 'X.red,green,blue',
+ 'X{.list*}' => 'X.red.green.blue',
+ 'X{.keys}' => 'X.semi,%3B,dot,.,comma,%2C',
+ 'X{.keys*}' => 'X.semi=%3B.dot=..comma=%2C',
+ 'X{.empty_keys}' => 'X',
+ 'X{.empty_keys*}' => 'X',
}
end
-end
-
-describe Addressable::URI, "when parsed from " +
- "'http://example.org///something///'" do
- before do
- @uri = Addressable::URI.parse("http://example.org///something///")
+ context "path expansion (/)" do
+ it_behaves_like 'expands', {
+ '{/who}' => '/fred',
+ '{/who,who}' => '/fred/fred',
+ '{/half,who}' => '/50%25/fred',
+ '{/who,dub}' => '/fred/me%2Ftoo',
+ '{/var}' => '/value',
+ '{/var,empty}' => '/value/',
+ '{/var,undef}' => '/value',
+ '{/var,x}/here' => '/value/1024/here',
+ '{/var:1,var}' => '/v/value',
+ '{/list}' => '/red,green,blue',
+ '{/list*}' => '/red/green/blue',
+ '{/list*,path:4}' => '/red/green/blue/%2Ffoo',
+ '{/keys}' => '/semi,%3B,dot,.,comma,%2C',
+ '{/keys*}' => '/semi=%3B/dot=./comma=%2C',
+ }
end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern 'http://example.org{-prefix|/|parts}/'" do
- Addressable::Template.new(
- "http://example.org{-prefix|/|parts}/"
- ).extract(@uri).should == {
- "parts" => ["", "", "something", "", ""]
+ context "path-style expansion (;)" do
+ it_behaves_like 'expands', {
+ '{;who}' => ';who=fred',
+ '{;half}' => ';half=50%25',
+ '{;empty}' => ';empty',
+ '{;v,empty,who}' => ';v=6;empty;who=fred',
+ '{;v,bar,who}' => ';v=6;who=fred',
+ '{;x,y}' => ';x=1024;y=768',
+ '{;x,y,empty}' => ';x=1024;y=768;empty',
+ '{;x,y,undef}' => ';x=1024;y=768',
+ '{;hello:5}' => ';hello=Hello',
+ '{;list}' => ';list=red,green,blue',
+ '{;list*}' => ';list=red;list=green;list=blue',
+ '{;keys}' => ';keys=semi,%3B,dot,.,comma,%2C',
+ '{;keys*}' => ';semi=%3B;dot=.;comma=%2C',
}
end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern 'http://example.org/{-suffix|/|parts}'" do
- Addressable::Template.new(
- "http://example.org/{-suffix|/|parts}"
- ).extract(@uri).should == {
- "parts" => ["", "", "something", "", ""]
+ context "form query expansion (?)" do
+ it_behaves_like 'expands', {
+ '{?who}' => '?who=fred',
+ '{?half}' => '?half=50%25',
+ '{?x,y}' => '?x=1024&y=768',
+ '{?x,y,empty}' => '?x=1024&y=768&empty=',
+ '{?x,y,undef}' => '?x=1024&y=768',
+ '{?var:3}' => '?var=val',
+ '{?list}' => '?list=red,green,blue',
+ '{?list*}' => '?list=red&list=green&list=blue',
+ '{?keys}' => '?keys=semi,%3B,dot,.,comma,%2C',
+ '{?keys*}' => '?semi=%3B&dot=.&comma=%2C',
}
end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern 'http://example.org/{-list|/|parts}'" do
- Addressable::Template.new(
- "http://example.org/{-list|/|parts}"
- ).extract(@uri).should == {
- "parts" => ["", "", "something", "", ""]
+ context "form query expansion (&)" do
+ it_behaves_like 'expands', {
+ '{&who}' => '&who=fred',
+ '{&half}' => '&half=50%25',
+ '?fixed=yes{&x}' => '?fixed=yes&x=1024',
+ '{&x,y,empty}' => '&x=1024&y=768&empty=',
+ '{&x,y,undef}' => '&x=1024&y=768',
+ '{&var:3}' => '&var=val',
+ '{&list}' => '&list=red,green,blue',
+ '{&list*}' => '&list=red&list=green&list=blue',
+ '{&keys}' => '&keys=semi,%3B,dot,.,comma,%2C',
+ '{&keys*}' => '&semi=%3B&dot=.&comma=%2C',
}
end
end
-describe Addressable::URI, "when parsed from " +
- "'http://example.com/one/spacer/two/'" do
- before do
- @uri = Addressable::URI.parse("http://example.com/one/spacer/two/")
- end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/{first}/spacer/{second}/'" do
- Addressable::Template.new(
- "http://example.com/{first}/spacer/{second}/"
- ).extract(@uri).should == {
- "first" => "one",
- "second" => "two"
- }
+class ExampleTwoProcessor
+ def self.restore(name, value)
+ return value.gsub(/-/, " ") if name == "query"
+ return value
end
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com{-prefix|/|stuff}/'" do
- Addressable::Template.new(
- "http://example.com{-prefix|/|stuff}/"
- ).extract(@uri).should == {
- "stuff" => ["one", "spacer", "two"]
- }
+ def self.match(name)
+ return ".*?" if name == "first"
+ return ".*"
end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/o{-prefix|/|stuff}/'" do
- Addressable::Template.new(
- "http://example.com/o{-prefix|/|stuff}/"
- ).extract(@uri).should == nil
+ def self.validate(name, value)
+ return !!(value =~ /^[\w ]+$/) if name == "query"
+ return true
end
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/{first}/spacer{-prefix|/|stuff}/'" do
- Addressable::Template.new(
- "http://example.com/{first}/spacer{-prefix|/|stuff}/"
- ).extract(@uri).should == {
- "first" => "one",
- "stuff" => "two"
- }
+ def self.transform(name, value)
+ return value.gsub(/ /, "+") if name == "query"
+ return value
end
+end
- it "should not match anything when extracting values " +
- "using the incorrect suffix pattern " +
- "'http://example.com/{-prefix|/|stuff}/'" do
- Addressable::Template.new(
- "http://example.com/{-prefix|/|stuff}/"
- ).extract(@uri).should == nil
- end
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com{-prefix|/|rot13}/' with the ExampleProcessor" do
- Addressable::Template.new(
- "http://example.com{-prefix|/|rot13}/"
- ).extract(@uri, ExampleProcessor).should == {
- "rot13" => ["bar", "fcnpre", "gjb"]
+describe Addressable::Template do
+ describe "Matching" do
+ let(:uri){
+ Addressable::URI.parse(
+ "http://example.com/search/an-example-search-query/"
+ )
}
- end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com{-prefix|/|rot13}' with the ExampleProcessor" do
- Addressable::Template.new(
- "http://example.com{-prefix|/|rot13}"
- ).extract(@uri, ExampleProcessor).should == {
- "rot13" => ["bar", "fcnpre", "gjb", ""]
+ let(:uri2){
+ Addressable::URI.parse("http://example.com/a/b/c/")
}
- end
+ let(:uri3){
+ Addressable::URI.parse("http://example.com/;a=1;b=2;c=3;first=foo")
+ }
+ let(:uri4){
+ Addressable::URI.parse("http://example.com/?a=1&b=2&c=3&first=foo")
+ }
+ context "first uri with ExampleTwoProcessor" do
+ subject{
+ match = Addressable::Template.new(
+ "http://example.com/search/{query}/"
+ ).match(uri, ExampleTwoProcessor)
+ }
+ its(:variables){ should == ["query"]}
+ its(:captures){ should == ["an example search query"]}
+ end
- it "should not match anything when extracting values " +
- "using the incorrect suffix pattern " +
- "'http://example.com/{-prefix|/|rot13}' with the ExampleProcessor" do
- Addressable::Template.new(
- "http://example.com/{-prefix|/|rot13}"
- ).extract(@uri, ExampleProcessor).should == nil
+ context "second uri with ExampleTwoProcessor" do
+ subject{
+ match = Addressable::Template.new(
+ "http://example.com/{first}/{+second}/"
+ ).match(uri2, ExampleTwoProcessor)
+ }
+ its(:variables){ should == ["first", "second"]}
+ its(:captures){ should == ["a", "b/c"] }
+ end
+ context "second uri" do
+ subject{
+ match = Addressable::Template.new(
+ "http://example.com/{first}{/second*}/"
+ ).match(uri2)
+ }
+ its(:variables){ should == ["first", "second"]}
+ its(:captures){ should == ["a", ["b","c"]] }
+ end
+ context "third uri" do
+ subject{
+ match = Addressable::Template.new(
+ "http://example.com/{;hash*,first}"
+ ).match(uri3)
+ }
+ its(:variables){ should == ["hash", "first"]}
+ its(:captures){ should == [
+ {"a" => "1", "b" => "2", "c" => "3", "first" => "foo"}, nil] }
+ end
+ context "fourth uri" do
+ subject{
+ match = Addressable::Template.new(
+ "http://example.com/{?hash*,first}"
+ ).match(uri4)
+ }
+ its(:variables){ should == ["hash", "first"]}
+ its(:captures){ should == [
+ {"a" => "1", "b" => "2", "c" => "3", "first"=> "foo"}, nil] }
+ end
end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/{-suffix|/|stuff}'" do
- Addressable::Template.new(
- "http://example.com/{-suffix|/|stuff}"
- ).extract(@uri).should == {
- "stuff" => ["one", "spacer", "two"]
+ describe "extract" do
+ let(:template) {
+ Addressable::Template.new(
+ "http://{host}{/segments*}/{?one,two,bogus}{#fragment}"
+ )
}
+ let(:uri){ "http://example.com/a/b/c/?one=1&two=2#foo" }
+ it "should be able to extract" do
+ template.extract(uri).should == {
+ "host" => "example.com",
+ "segments" => %w(a b c),
+ "one" => "1",
+ "bogus" => nil,
+ "two" => "2",
+ "fragment" => "foo"
+ }
+ end
end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/{-suffix|/|stuff}o'" do
- Addressable::Template.new(
- "http://example.com/{-suffix|/|stuff}o"
- ).extract(@uri).should == nil
+ describe "Partial expand" do
+ context "partial_expand with two simple values" do
+ subject{
+ Addressable::Template.new("http://example.com/{one}/{two}/")
+ }
+ it "builds a new pattern" do
+ subject.partial_expand("one" => "1").pattern.should ==
+ "http://example.com/1/{two}/"
+ end
+ end
+ context "partial_expand query with missing param in middle" do
+ subject{
+ Addressable::Template.new("http://example.com/{?one,two,three}/")
+ }
+ it "builds a new pattern" do
+ subject.partial_expand("one" => "1", "three" => "3").pattern.should ==
+ "http://example.com/?one=1{&two}&three=3/"
+ end
+ end
+ context "partial_expand with query string" do
+ subject{
+ Addressable::Template.new("http://example.com/{?two,one}/")
+ }
+ it "builds a new pattern" do
+ subject.partial_expand("one" => "1").pattern.should ==
+ "http://example.com/{?two}&one=1/"
+ end
+ end
+ context "partial_expand with path operator" do
+ subject{
+ Addressable::Template.new("http://example.com{/one,two}/")
+ }
+ it "builds a new pattern" do
+ subject.partial_expand("one" => "1").pattern.should ==
+ "http://example.com/1{/two}/"
+ end
+ end
end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/o{-suffix|/|stuff}'" do
- Addressable::Template.new(
- "http://example.com/o{-suffix|/|stuff}"
- ).extract(@uri).should == {"stuff"=>["ne", "spacer", "two"]}
+ describe "Expand" do
+ context "expand with a processor" do
+ subject{
+ Addressable::Template.new("http://example.com/search/{query}/")
+ }
+ it "processes spaces" do
+ subject.expand({"query" => "an example search query"},
+ ExampleTwoProcessor).to_str.should ==
+ "http://example.com/search/an+example+search+query/"
+ end
+ it "validates" do
+ lambda{
+ subject.expand({"query" => "Bogus!"},
+ ExampleTwoProcessor).to_str
+ }.should raise_error(Addressable::Template::InvalidTemplateValueError)
+ end
+ end
+ context "partial_expand query with missing param in middle" do
+ subject{
+ Addressable::Template.new("http://example.com/{?one,two,three}/")
+ }
+ it "builds a new pattern" do
+ subject.partial_expand("one" => "1", "three" => "3").pattern.should ==
+ "http://example.com/?one=1{&two}&three=3/"
+ end
+ end
+ context "partial_expand with query string" do
+ subject{
+ Addressable::Template.new("http://example.com/{?two,one}/")
+ }
+ it "builds a new pattern" do
+ subject.partial_expand("one" => "1").pattern.should ==
+ "http://example.com/{?two}&one=1/"
+ end
+ end
+ context "partial_expand with path operator" do
+ subject{
+ Addressable::Template.new("http://example.com{/one,two}/")
+ }
+ it "builds a new pattern" do
+ subject.partial_expand("one" => "1").pattern.should ==
+ "http://example.com/1{/two}/"
+ end
+ end
end
+ context "Matching with operators" do
+ describe "Level 1:" do
+ subject { Addressable::Template.new("foo{foo}/{bar}baz") }
+ it "can match" do
+ data = subject.match("foofoo/bananabaz")
+ data.mapping["foo"].should == "foo"
+ data.mapping["bar"].should == "banana"
+ end
+ it "lists vars" do
+ subject.variables.should == ["foo", "bar"]
+ end
+ end
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/{first}/spacer/{-suffix|/|stuff}'" do
- Addressable::Template.new(
- "http://example.com/{first}/spacer/{-suffix|/|stuff}"
- ).extract(@uri).should == {
- "first" => "one",
- "stuff" => "two"
- }
- end
+ describe "Level 2:" do
+ subject { Addressable::Template.new("foo{+foo}{#bar}baz") }
+ it "can match" do
+ data = subject.match("foo/test/banana#bazbaz")
+ data.mapping["foo"].should == "/test/banana"
+ data.mapping["bar"].should == "baz"
+ end
+ it "lists vars" do
+ subject.variables.should == ["foo", "bar"]
+ end
+ end
- it "should not match anything when extracting values " +
- "using the incorrect suffix pattern " +
- "'http://example.com/{-suffix|/|stuff}/'" do
- Addressable::Template.new(
- "http://example.com/{-suffix|/|stuff}/"
- ).extract(@uri).should == nil
+ describe "Level 3:" do
+ context "no operator" do
+ subject { Addressable::Template.new("foo{foo,bar}baz") }
+ it "can match" do
+ data = subject.match("foofoo,barbaz")
+ data.mapping["foo"].should == "foo"
+ data.mapping["bar"].should == "bar"
+ end
+ it "lists vars" do
+ subject.variables.should == ["foo", "bar"]
+ end
+ end
+ context "+ operator" do
+ subject { Addressable::Template.new("foo{+foo,bar}baz") }
+ it "can match" do
+ data = subject.match("foofoo/bar,barbaz")
+ data.mapping["bar"].should == "foo/bar,bar"
+ data.mapping["foo"].should == ""
+ end
+ it "lists vars" do
+ subject.variables.should == ["foo", "bar"]
+ end
+ end
+ context ". operator" do
+ subject { Addressable::Template.new("foo{.foo,bar}baz") }
+ it "can match" do
+ data = subject.match("foo.foo.barbaz")
+ data.mapping["foo"].should == "foo"
+ data.mapping["bar"].should == "bar"
+ end
+ it "lists vars" do
+ subject.variables.should == ["foo", "bar"]
+ end
+ end
+ context "/ operator" do
+ subject { Addressable::Template.new("foo{/foo,bar}baz") }
+ it "can match" do
+ data = subject.match("foo/foo/barbaz")
+ data.mapping["foo"].should == "foo"
+ data.mapping["bar"].should == "bar"
+ end
+ it "lists vars" do
+ subject.variables.should == ["foo", "bar"]
+ end
+ end
+ context "; operator" do
+ subject { Addressable::Template.new("foo{;foo,bar,baz}baz") }
+ it "can match" do
+ data = subject.match("foo;foo=bar%20baz;bar=foo;bazbaz")
+ data.mapping["foo"].should == "bar baz"
+ data.mapping["bar"].should == "foo"
+ data.mapping["baz"].should == ""
+ end
+ it "lists vars" do
+ subject.variables.should == %w(foo bar baz)
+ end
+ end
+ context "? operator" do
+ subject { Addressable::Template.new("foo{?foo,bar}baz") }
+ it "can match" do
+ data = subject.match("foo?foo=bar%20baz&bar=foobaz")
+ data.mapping["foo"].should == "bar baz"
+ data.mapping["bar"].should == "foo"
+ end
+ it "lists vars" do
+ subject.variables.should == %w(foo bar)
+ end
+ end
+ context "& operator" do
+ subject { Addressable::Template.new("foo{&foo,bar}baz") }
+ it "can match" do
+ data = subject.match("foo&foo=bar%20baz&bar=foobaz")
+ data.mapping["foo"].should == "bar baz"
+ data.mapping["bar"].should == "foo"
+ end
+ it "lists vars" do
+ subject.variables.should == %w(foo bar)
+ end
+ end
+ end
end
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com/{-suffix|/|rot13}' with the ExampleProcessor" do
- Addressable::Template.new(
- "http://example.com/{-suffix|/|rot13}"
- ).extract(@uri, ExampleProcessor).should == {
- "rot13" => ["bar", "fcnpre", "gjb"]
- }
- end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://example.com{-suffix|/|rot13}' with the ExampleProcessor" do
- Addressable::Template.new(
- "http://example.com{-suffix|/|rot13}"
- ).extract(@uri, ExampleProcessor).should == {
- "rot13" => ["", "bar", "fcnpre", "gjb"]
- }
- end
-
- it "should not match anything when extracting values " +
- "using the incorrect suffix pattern " +
- "'http://example.com/{-suffix|/|rot13}/' with the ExampleProcessor" do
- Addressable::Template.new(
- "http://example.com/{-suffix|/|rot13}/"
- ).extract(@uri, ExampleProcessor).should == nil
- end
-end
-
-describe Addressable::URI, "when parsed from " +
- "'http://example.com/?email=bob@sporkmonger.com'" do
- before do
- @uri = Addressable::URI.parse(
- "http://example.com/?email=bob@sporkmonger.com"
- )
- end
-
- it "should not match anything when extracting values " +
- "using the incorrect opt pattern " +
- "'http://example.com/?email={-opt|bogus@bogus.com|test}'" do
- Addressable::Template.new(
- "http://example.com/?email={-opt|bogus@bogus.com|test}"
- ).extract(@uri).should == nil
- end
-
- it "should not match anything when extracting values " +
- "using the incorrect neg pattern " +
- "'http://example.com/?email={-neg|bogus@bogus.com|test}'" do
- Addressable::Template.new(
- "http://example.com/?email={-neg|bogus@bogus.com|test}"
- ).extract(@uri).should == nil
- end
-
- it "should indicate a match when extracting values " +
- "using the opt pattern " +
- "'http://example.com/?email={-opt|bob@sporkmonger.com|test}'" do
- Addressable::Template.new(
- "http://example.com/?email={-opt|bob@sporkmonger.com|test}"
- ).extract(@uri).should == {}
- end
-
- it "should indicate a match when extracting values " +
- "using the neg pattern " +
- "'http://example.com/?email={-neg|bob@sporkmonger.com|test}'" do
- Addressable::Template.new(
- "http://example.com/?email={-neg|bob@sporkmonger.com|test}"
- ).extract(@uri).should == {}
- end
-end
-
-describe Addressable::URI, "when parsed from " +
- "'http://example.com/?email='" do
- before do
- @uri = Addressable::URI.parse(
- "http://example.com/?email="
- )
- end
-
- it "should indicate a match when extracting values " +
- "using the opt pattern " +
- "'http://example.com/?email={-opt|bob@sporkmonger.com|test}'" do
- Addressable::Template.new(
- "http://example.com/?email={-opt|bob@sporkmonger.com|test}"
- ).extract(@uri).should == {}
- end
-
- it "should indicate a match when extracting values " +
- "using the neg pattern " +
- "'http://example.com/?email={-neg|bob@sporkmonger.com|test}'" do
- Addressable::Template.new(
- "http://example.com/?email={-neg|bob@sporkmonger.com|test}"
- ).extract(@uri).should == {}
- end
-end
-
-describe Addressable::URI, "when parsed from " +
- "'http://example.com/a/b/c/?one=1&two=2#foo'" do
- before do
- @uri = Addressable::URI.parse(
- "http://example.com/a/b/c/?one=1&two=2#foo"
- )
- end
-
- it "should have the correct mapping when extracting values " +
- "using the pattern " +
- "'http://{host}/{-suffix|/|segments}?{-join|&|one,two}\#{fragment}'" do
- Addressable::Template.new(
- "http://{host}/{-suffix|/|segments}?{-join|&|one,two}\#{fragment}"
- ).extract(@uri).should == {
- "host" => "example.com",
- "segments" => ["a", "b", "c"],
- "one" => "1",
- "two" => "2",
- "fragment" => "foo"
- }
- end
-
- it "should not match when extracting values " +
- "using the pattern " +
- "'http://{host}/{-suffix|/|segments}?{-join|&|one}\#{fragment}'" do
- Addressable::Template.new(
- "http://{host}/{-suffix|/|segments}?{-join|&|one}\#{fragment}"
- ).extract(@uri).should == nil
- end
-
- it "should not match when extracting values " +
- "using the pattern " +
- "'http://{host}/{-suffix|/|segments}?{-join|&|bogus}\#{fragment}'" do
- Addressable::Template.new(
- "http://{host}/{-suffix|/|segments}?{-join|&|bogus}\#{fragment}"
- ).extract(@uri).should == nil
- end
-
- it "should not match when extracting values " +
- "using the pattern " +
- "'http://{host}/{-suffix|/|segments}?" +
- "{-join|&|one,bogus}\#{fragment}'" do
- Addressable::Template.new(
- "http://{host}/{-suffix|/|segments}?{-join|&|one,bogus}\#{fragment}"
- ).extract(@uri).should == nil
- end
-
- it "should not match when extracting values " +
- "using the pattern " +
- "'http://{host}/{-suffix|/|segments}?" +
- "{-join|&|one,two,bogus}\#{fragment}'" do
- Addressable::Template.new(
- "http://{host}/{-suffix|/|segments}?{-join|&|one,two,bogus}\#{fragment}"
- ).extract(@uri).should == {
- "host" => "example.com",
- "segments" => ["a", "b", "c"],
- "one" => "1",
- "two" => "2",
- "fragment" => "foo"
- }
- end
-end
-
-describe Addressable::URI, "when given a pattern with bogus operators" do
- before do
- @uri = Addressable::URI.parse("http://example.com/a/b/c/")
- end
-
- it "should raise an InvalidTemplateOperatorError" do
- (lambda do
- Addressable::Template.new(
- "http://example.com/{-bogus|/|a,b,c}/"
- ).extract(@uri)
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- end
-
- it "should raise an InvalidTemplateOperatorError" do
- (lambda do
- Addressable::Template.new(
- "http://example.com{-prefix|/|a,b,c}/"
- ).extract(@uri)
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- end
-
- it "should raise an InvalidTemplateOperatorError" do
- (lambda do
- Addressable::Template.new(
- "http://example.com/{-suffix|/|a,b,c}"
- ).extract(@uri)
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- end
-
- it "should raise an InvalidTemplateOperatorError" do
- (lambda do
- Addressable::Template.new(
- "http://example.com/{-list|/|a,b,c}/"
- ).extract(@uri)
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- end
-end
-
-describe Addressable::URI, "when given a mapping that contains an Array" do
- before do
- @mapping = {"query" => "an example search query".split(" ")}
- end
-
- it "should result in 'http://example.com/search/an+example+search+query/'" +
- " when used to expand 'http://example.com/search/{-list|+|query}/'" do
- Addressable::Template.new(
- "http://example.com/search/{-list|+|query}/"
- ).expand(@mapping).to_str.should ==
- "http://example.com/search/an+example+search+query/"
- end
-
- it "should result in 'http://example.com/search/an+example+search+query/'" +
- " when used to expand 'http://example.com/search/{-list|+|query}/'" +
- " with a NoOpProcessor" do
- Addressable::Template.new(
- "http://example.com/search/{-list|+|query}/"
- ).expand(@mapping, NoOpProcessor).to_str.should ==
- "http://example.com/search/an+example+search+query/"
- end
-end
-
-describe Addressable::URI, "when given an empty mapping" do
- before do
- @mapping = {}
- end
-
- it "should result in 'http://example.com/search/'" +
- " when used to expand 'http://example.com/search/{-list|+|query}'" do
- Addressable::Template.new(
- "http://example.com/search/{-list|+|query}"
- ).expand(@mapping).to_str.should == "http://example.com/search/"
- end
-
- it "should result in 'http://example.com'" +
- " when used to expand 'http://example.com{-prefix|/|foo}'" do
- Addressable::Template.new(
- "http://example.com{-prefix|/|foo}"
- ).expand(@mapping).to_str.should == "http://example.com"
- end
-
- it "should result in 'http://example.com'" +
- " when used to expand 'http://example.com{-suffix|/|foo}'" do
- Addressable::Template.new(
- "http://example.com{-suffix|/|foo}"
- ).expand(@mapping).to_str.should == "http://example.com"
- end
-end
-
-describe Addressable::URI, "when given the template pattern " +
- "'http://example.com/search/{query}/' " +
- "to be processed with the ExampleProcessor" do
- before do
- @pattern = "http://example.com/search/{query}/"
- end
-
- it "should expand to " +
- "'http://example.com/search/an+example+search+query/' " +
- "with a mapping of {\"query\" => \"an example search query\"} " do
- Addressable::Template.new(
- "http://example.com/search/{query}/"
- ).expand({
- "query" => "an example search query"
- }, ExampleProcessor).to_s.should ==
- "http://example.com/search/an+example+search+query/"
- end
-
- it "should raise an error " +
- "with a mapping of {\"query\" => \"invalid!\"}" do
- (lambda do
- Addressable::Template.new(
- "http://example.com/search/{query}/"
- ).expand({"query" => "invalid!"}, ExampleProcessor).to_s
- end).should raise_error(Addressable::Template::InvalidTemplateValueError)
- end
-end
-
-# Section 3.3.1 of the URI Template draft v 01
-describe Addressable::URI, "when given the mapping supplied in " +
- "Section 3.3.1 of the URI Template draft v 01" do
- before do
- @mapping = {
- "a" => "fred",
- "b" => "barney",
- "c" => "cheeseburger",
- "d" => "one two three",
- "e" => "20% tricky",
- "f" => "",
- "20" => "this-is-spinal-tap",
- "scheme" => "https",
- "p" => "quote=to+be+or+not+to+be",
- "q" => "hullo#world"
- }
- end
-
- it "should result in 'http://example.org/page1#fred' " +
- "when used to expand 'http://example.org/page1\#{a}'" do
- Addressable::Template.new(
- "http://example.org/page1\#{a}"
- ).expand(@mapping).to_s.should == "http://example.org/page1#fred"
- end
-
- it "should result in 'http://example.org/fred/barney/' " +
- "when used to expand 'http://example.org/{a}/{b}/'" do
- Addressable::Template.new(
- "http://example.org/{a}/{b}/"
- ).expand(@mapping).to_s.should == "http://example.org/fred/barney/"
- end
-
- it "should result in 'http://example.org/fredbarney/' " +
- "when used to expand 'http://example.org/{a}{b}/'" do
- Addressable::Template.new(
- "http://example.org/{a}{b}/"
- ).expand(@mapping).to_s.should == "http://example.org/fredbarney/"
- end
-
- it "should result in " +
- "'http://example.com/order/cheeseburger/cheeseburger/cheeseburger/' " +
- "when used to expand 'http://example.com/order/{c}/{c}/{c}/'" do
- Addressable::Template.new(
- "http://example.com/order/{c}/{c}/{c}/"
- ).expand(@mapping).to_s.should ==
- "http://example.com/order/cheeseburger/cheeseburger/cheeseburger/"
- end
-
- it "should result in 'http://example.org/one%20two%20three' " +
- "when used to expand 'http://example.org/{d}'" do
- Addressable::Template.new(
- "http://example.org/{d}"
- ).expand(@mapping).to_s.should ==
- "http://example.org/one%20two%20three"
- end
-
- it "should result in 'http://example.org/20%25%20tricky' " +
- "when used to expand 'http://example.org/{e}'" do
- Addressable::Template.new(
- "http://example.org/{e}"
- ).expand(@mapping).to_s.should ==
- "http://example.org/20%25%20tricky"
- end
-
- it "should result in 'http://example.com//' " +
- "when used to expand 'http://example.com/{f}/'" do
- Addressable::Template.new(
- "http://example.com/{f}/"
- ).expand(@mapping).to_s.should ==
- "http://example.com//"
- end
-
- it "should result in " +
- "'https://this-is-spinal-tap.example.org?date=&option=fred' " +
- "when used to expand " +
- "'{scheme}://{20}.example.org?date={wilma}&option={a}'" do
- Addressable::Template.new(
- "{scheme}://{20}.example.org?date={wilma}&option={a}"
- ).expand(@mapping).to_s.should ==
- "https://this-is-spinal-tap.example.org?date=&option=fred"
- end
-
- # The v 01 draft conflicts with the v 03 draft here.
- # The Addressable implementation uses v 03.
- it "should result in " +
- "'http://example.org?quote%3Dto%2Bbe%2Bor%2Bnot%2Bto%2Bbe' " +
- "when used to expand 'http://example.org?{p}'" do
- Addressable::Template.new(
- "http://example.org?{p}"
- ).expand(@mapping).to_s.should ==
- "http://example.org?quote%3Dto%2Bbe%2Bor%2Bnot%2Bto%2Bbe"
- end
-
- # The v 01 draft conflicts with the v 03 draft here.
- # The Addressable implementation uses v 03.
- it "should result in 'http://example.com/hullo%23world' " +
- "when used to expand 'http://example.com/{q}'" do
- Addressable::Template.new(
- "http://example.com/{q}"
- ).expand(@mapping).to_s.should == "http://example.com/hullo%23world"
- end
-end
-
-# Section 4.5 of the URI Template draft v 03
-describe Addressable::URI, "when given the mapping supplied in " +
- "Section 4.5 of the URI Template draft v 03" do
- before do
- @mapping = {
- "foo" => "ϓ",
- "bar" => "fred",
- "baz" => "10,20,30",
- "qux" => ["10","20","30"],
- "corge" => [],
- "grault" => "",
- "garply" => "a/b/c",
- "waldo" => "ben & jerrys",
- "fred" => ["fred", "", "wilma"],
- "plugh" => ["ẛ", "ṡ"],
- "1-a_b.c" => "200"
- }
- end
-
- it "should result in 'http://example.org/?q=fred' " +
- "when used to expand 'http://example.org/?q={bar}'" do
- Addressable::Template.new(
- "http://example.org/?q={bar}"
- ).expand(@mapping).to_s.should == "http://example.org/?q=fred"
- end
-
- it "should result in '/' " +
- "when used to expand '/{xyzzy}'" do
- Addressable::Template.new(
- "/{xyzzy}"
- ).expand(@mapping).to_s.should == "/"
- end
-
- it "should result in " +
- "'http://example.org/?foo=%CE%8E&bar=fred&baz=10%2C20%2C30' " +
- "when used to expand " +
- "'http://example.org/?{-join|&|foo,bar,xyzzy,baz}'" do
- Addressable::Template.new(
- "http://example.org/?{-join|&|foo,bar,xyzzy,baz}"
- ).expand(@mapping).to_s.should ==
- "http://example.org/?foo=%CE%8E&bar=fred&baz=10%2C20%2C30"
- end
-
- it "should result in 'http://example.org/?d=10,20,30' " +
- "when used to expand 'http://example.org/?d={-list|,|qux}'" do
- Addressable::Template.new(
- "http://example.org/?d={-list|,|qux}"
- ).expand(
- @mapping
- ).to_s.should == "http://example.org/?d=10,20,30"
- end
-
- it "should result in 'http://example.org/?d=10&d=20&d=30' " +
- "when used to expand 'http://example.org/?d={-list|&d=|qux}'" do
- Addressable::Template.new(
- "http://example.org/?d={-list|&d=|qux}"
- ).expand(
- @mapping
- ).to_s.should == "http://example.org/?d=10&d=20&d=30"
- end
-
- it "should result in 'http://example.org/fredfred/a%2Fb%2Fc' " +
- "when used to expand 'http://example.org/{bar}{bar}/{garply}'" do
- Addressable::Template.new(
- "http://example.org/{bar}{bar}/{garply}"
- ).expand(
- @mapping
- ).to_s.should == "http://example.org/fredfred/a%2Fb%2Fc"
- end
-
- it "should result in 'http://example.org/fred/fred//wilma' " +
- "when used to expand 'http://example.org/{bar}{-prefix|/|fred}'" do
- Addressable::Template.new(
- "http://example.org/{bar}{-prefix|/|fred}"
- ).expand(
- @mapping
- ).to_s.should == "http://example.org/fred/fred//wilma"
- end
-
- it "should result in ':%E1%B9%A1:%E1%B9%A1:' " +
- "when used to expand '{-neg|:|corge}{-suffix|:|plugh}'" do
- # Note: We need to check the path, because technically, this is an
- # invalid URI.
- Addressable::Template.new(
- "{-neg|:|corge}{-suffix|:|plugh}"
- ).expand(@mapping).path.should == ":%E1%B9%A1:%E1%B9%A1:"
- end
-
- it "should result in '../ben%20%26%20jerrys/' " +
- "when used to expand '../{waldo}/'" do
- Addressable::Template.new(
- "../{waldo}/"
- ).expand(
- @mapping
- ).to_s.should == "../ben%20%26%20jerrys/"
- end
-
- it "should result in 'telnet:192.0.2.16:80' " +
- "when used to expand 'telnet:192.0.2.16{-opt|:80|grault}'" do
- Addressable::Template.new(
- "telnet:192.0.2.16{-opt|:80|grault}"
- ).expand(
- @mapping
- ).to_s.should == "telnet:192.0.2.16:80"
- end
-
- it "should result in ':200:' " +
- "when used to expand ':{1-a_b.c}:'" do
- # Note: We need to check the path, because technically, this is an
- # invalid URI.
- Addressable::Template.new(
- ":{1-a_b.c}:"
- ).expand(@mapping).path.should == ":200:"
- end
-end
-
-describe Addressable::URI, "when given a mapping that contains a " +
- "template-var within a value" do
- before do
- @mapping = {
- "a" => "{b}",
- "b" => "barney",
- }
- end
-
- it "should result in 'http://example.com/%7Bb%7D/barney/' " +
- "when used to expand 'http://example.com/{a}/{b}/'" do
- Addressable::Template.new(
- "http://example.com/{a}/{b}/"
- ).expand(
- @mapping
- ).to_s.should == "http://example.com/%7Bb%7D/barney/"
- end
-
- it "should result in 'http://example.com//%7Bb%7D/' " +
- "when used to expand 'http://example.com/{-opt|foo|foo}/{a}/'" do
- Addressable::Template.new(
- "http://example.com/{-opt|foo|foo}/{a}/"
- ).expand(
- @mapping
- ).to_s.should == "http://example.com//%7Bb%7D/"
- end
-
- it "should result in 'http://example.com//%7Bb%7D/' " +
- "when used to expand 'http://example.com/{-neg|foo|b}/{a}/'" do
- Addressable::Template.new(
- "http://example.com/{-neg|foo|b}/{a}/"
- ).expand(
- @mapping
- ).to_s.should == "http://example.com//%7Bb%7D/"
- end
-
- it "should result in 'http://example.com//barney/%7Bb%7D/' " +
- "when used to expand 'http://example.com/{-prefix|/|b}/{a}/'" do
- Addressable::Template.new(
- "http://example.com/{-prefix|/|b}/{a}/"
- ).expand(
- @mapping
- ).to_s.should == "http://example.com//barney/%7Bb%7D/"
- end
-
- it "should result in 'http://example.com/barney//%7Bb%7D/' " +
- "when used to expand 'http://example.com/{-suffix|/|b}/{a}/'" do
- Addressable::Template.new(
- "http://example.com/{-suffix|/|b}/{a}/"
- ).expand(
- @mapping
- ).to_s.should == "http://example.com/barney//%7Bb%7D/"
- end
-
- it "should result in 'http://example.com/%7Bb%7D/?b=barney&c=42' " +
- "when used to expand 'http://example.com/{a}/?{-join|&|b,c=42}'" do
- Addressable::Template.new(
- "http://example.com/{a}/?{-join|&|b,c=42}"
- ).expand(
- @mapping
- ).to_s.should == "http://example.com/%7Bb%7D/?b=barney&c=42"
- end
-
- it "should result in 'http://example.com/42/?b=barney' " +
- "when used to expand 'http://example.com/{c=42}/?{-join|&|b}'" do
- Addressable::Template.new(
- "http://example.com/{c=42}/?{-join|&|b}"
- ).expand(@mapping).to_s.should == "http://example.com/42/?b=barney"
- end
-end
-
-describe Addressable::URI, "when given a single variable mapping" do
- before do
- @mapping = {
- "foo" => "fred"
- }
- end
-
- it "should result in 'fred' when used to expand '{foo}'" do
- Addressable::Template.new(
- "{foo}"
- ).expand(@mapping).to_s.should == "fred"
- end
-
- it "should result in 'wilma' when used to expand '{bar=wilma}'" do
- Addressable::Template.new(
- "{bar=wilma}"
- ).expand(@mapping).to_s.should == "wilma"
- end
-
- it "should result in '' when used to expand '{baz}'" do
- Addressable::Template.new(
- "{baz}"
- ).expand(@mapping).to_s.should == ""
- end
-end
-
-describe Addressable::URI, "when given a simple mapping" do
- before do
- @mapping = {
- "foo" => "fred",
- "bar" => "barney",
- "baz" => ""
- }
- end
-
- it "should result in 'foo=fred&bar=barney&baz=' when used to expand " +
- "'{-join|&|foo,bar,baz,qux}'" do
- Addressable::Template.new(
- "{-join|&|foo,bar,baz,qux}"
- ).expand(@mapping).to_s.should == "foo=fred&bar=barney&baz="
- end
-
- it "should result in 'bar=barney' when used to expand " +
- "'{-join|&|bar}'" do
- Addressable::Template.new(
- "{-join|&|bar}"
- ).expand(@mapping).to_s.should == "bar=barney"
- end
-
- it "should result in '' when used to expand " +
- "'{-join|&|qux}'" do
- Addressable::Template.new(
- "{-join|&|qux}"
- ).expand(@mapping).to_s.should == ""
- end
-end
-
-describe Addressable::URI, "extracting defaults from a pattern" do
- before do
- @template = Addressable::Template.new("{foo}{bar=baz}{-opt|found|cond}")
- end
-
- it "should extract default value" do
- @template.variable_defaults.should == {"bar" => "baz"}
- end
-end
-
-describe Addressable::URI, "when given a mapping with symbol keys" do
- before do
- @mapping = { :name => "fred" }
- end
-
- it "should result in 'fred' when used to expand '{foo}'" do
- Addressable::Template.new(
- "{name}"
- ).expand(@mapping).to_s.should == "fred"
- end
-end
-
-describe Addressable::URI, "when given a mapping with bogus keys" do
- before do
- @mapping = { Object.new => "fred" }
- end
-
- it "should raise an error" do
- (lambda do
- Addressable::Template.new(
- "{name}"
- ).expand(@mapping)
- end).should raise_error(TypeError)
- end
-end
-
-describe Addressable::URI, "when given a mapping with numeric values" do
- before do
- @mapping = { :id => 123 }
- end
-
- it "should result in 'fred' when used to expand '{foo}'" do
- Addressable::Template.new(
- "{id}"
- ).expand(@mapping).to_s.should == "123"
- end
-end
-
-describe Addressable::URI, "when given a mapping containing values " +
- "that are already percent-encoded" do
- before do
- @mapping = {
- "a" => "%7Bb%7D"
- }
- end
-
- it "should result in 'http://example.com/%257Bb%257D/' " +
- "when used to expand 'http://example.com/{a}/'" do
- Addressable::Template.new(
- "http://example.com/{a}/"
- ).expand(@mapping).to_s.should == "http://example.com/%257Bb%257D/"
- end
-end
-
-describe Addressable::URI, "when given a pattern with bogus operators" do
- it "should raise an InvalidTemplateOperatorError" do
- (lambda do
- Addressable::Template.new(
- "http://example.com/{-bogus|/|a,b,c}/"
- ).expand({
- "a" => "a", "b" => "b", "c" => "c"
- })
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- end
-
- it "should raise an InvalidTemplateOperatorError" do
- (lambda do
- Addressable::Template.new(
- "http://example.com/{-prefix|/|a,b,c}/"
- ).expand({
- "a" => "a", "b" => "b", "c" => "c"
- })
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- end
-
- it "should raise an InvalidTemplateOperatorError" do
- (lambda do
- Addressable::Template.new(
- "http://example.com/{-suffix|/|a,b,c}/"
- ).expand({
- "a" => "a", "b" => "b", "c" => "c"
- })
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- end
-
- it "should raise an InvalidTemplateOperatorError" do
- (lambda do
- Addressable::Template.new(
- "http://example.com/{-join|/|a,b,c}/"
- ).expand({
- "a" => ["a"], "b" => ["b"], "c" => "c"
- })
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- end
-
- it "should raise an InvalidTemplateOperatorError" do
- (lambda do
- Addressable::Template.new(
- "http://example.com/{-list|/|a,b,c}/"
- ).expand({
- "a" => ["a"], "b" => ["b"], "c" => "c"
- })
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{one}/{two}/"
- )
- @partial_template = @initial_template.partial_expand({"one" => "1"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1", "two" => "2"}).should ==
- @partial_template.expand({"two" => "2"})
- end
-
- it "should raise an error if the template is expanded with bogus values" do
- (lambda do
- @initial_template.expand({"one" => Object.new, "two" => Object.new})
- end).should raise_error(TypeError)
- (lambda do
- @partial_template.expand({"two" => Object.new})
- end).should raise_error(TypeError)
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{one}/{two}/"
- )
- @partial_template = @initial_template.partial_expand({"two" => "2"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1", "two" => "2"}).should ==
- @partial_template.expand({"one" => "1"})
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{one}/{two}/"
- )
- @partial_template = @initial_template.partial_expand({"two" => "2"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1", "two" => "2"}).should ==
- @partial_template.expand({"one" => "1"})
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{one=1}/{two=2}/"
- )
- @partial_template = @initial_template.partial_expand({"one" => "3"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "3", "two" => "4"}).should ==
- @partial_template.expand({"two" => "4"})
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).should === "http://example.com/3/2/"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{one=1}/{two=2}/"
- )
- @partial_template = @initial_template.partial_expand({"two" => "4"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "3", "two" => "4"}).should ==
- @partial_template.expand({"one" => "3"})
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).should === "http://example.com/1/4/"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{-opt|found|one,two,three}"
- )
- @partial_template = @initial_template.partial_expand({})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1"}).to_str.should ==
- @partial_template.expand({"two" => "2"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"one" => "1"}).to_str.should ==
- "http://example.com/found"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"two" => "2"}).to_str.should ==
- "http://example.com/found"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"three" => "3"}).to_str.should ==
- "http://example.com/found"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"four" => "4"}).to_str.should ==
- "http://example.com/"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"one" => "1", "two" => "2"}).to_str.should ==
- "http://example.com/found"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{-opt|found|one,two,three}"
- )
- @partial_template = @initial_template.partial_expand({"one" => "1"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1"}).to_str.should ==
- @partial_template.expand({"two" => "2"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/found"
- end
-
- it "should produce the correct pattern" do
- @partial_template.pattern.should == "http://example.com/found"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{-neg|notfound|one,two,three}"
- )
- @partial_template = @initial_template.partial_expand({})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1"}).to_str.should ==
- @partial_template.expand({"two" => "2"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/notfound"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"one" => "1"}).to_str.should ==
- "http://example.com/"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"two" => "2"}).to_str.should ==
- "http://example.com/"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"three" => "3"}).to_str.should ==
- "http://example.com/"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"four" => "4"}).to_str.should ==
- "http://example.com/notfound"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"one" => "1", "two" => "2"}).to_str.should ==
- "http://example.com/"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{-neg|notfound|one,two,three}"
- )
- @partial_template = @initial_template.partial_expand({"one" => "1"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1"}).to_str.should ==
- @partial_template.expand({"two" => "2"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/"
- end
-
- it "should produce the correct pattern" do
- @partial_template.pattern.should == "http://example.com/"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-prefix|x=|one}"
- )
- @partial_template = @initial_template.partial_expand({})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1"}).to_str.should ==
- @partial_template.expand({"one" => "1"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/?"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"one" => "1"}).to_str.should ==
- "http://example.com/?x=1"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"two" => "2"}).to_str.should ==
- "http://example.com/?"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"one" => "1", "two" => "2"}).to_str.should ==
- "http://example.com/?x=1"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-prefix|x=|one}"
- )
- @partial_template = @initial_template.partial_expand({"one" => "1"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1"}).to_str.should ==
- @partial_template.expand({}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/?x=1"
- end
-
- it "should produce the correct pattern" do
- @partial_template.pattern.should == "http://example.com/?x=1"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-suffix|=x|one}"
- )
- @partial_template = @initial_template.partial_expand({})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1"}).to_str.should ==
- @partial_template.expand({"one" => "1"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/?"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"one" => "1"}).to_str.should ==
- "http://example.com/?1=x"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"two" => "2"}).to_str.should ==
- "http://example.com/?"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"one" => "1", "two" => "2"}).to_str.should ==
- "http://example.com/?1=x"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-suffix|=x|one}"
- )
- @partial_template = @initial_template.partial_expand({"one" => "1"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1"}).to_str.should ==
- @partial_template.expand({}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/?1=x"
- end
-
- it "should produce the correct pattern" do
- @partial_template.pattern.should == "http://example.com/?1=x"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-join|&|one}"
- )
- @partial_template = @initial_template.partial_expand({})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1"}).to_str.should ==
- @partial_template.expand({"one" => "1"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/?"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.pattern.should == @initial_template.pattern
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-join|&|one,two}"
- )
- @partial_template = @initial_template.partial_expand({"one" => "1"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1", "two" => "2"}).to_str.should ==
- @partial_template.expand({"two" => "2"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/?one=1"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-join|&|one,two}"
- )
- @partial_template = @initial_template.partial_expand({"two" => "2"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({"one" => "1", "two" => "2"}).to_str.should ==
- @partial_template.expand({"one" => "1"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/?two=2"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-join|&|one,two,three}"
- )
- @partial_template = @initial_template.partial_expand({"one" => "1"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({
- "one" => "1", "two" => "2", "three" => "3"
- }).to_str.should ==
- @partial_template.expand({"two" => "2", "three" => "3"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/?one=1"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"two" => "2"}).to_str.should ==
- "http://example.com/?one=1&two=2"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"three" => "3"}).to_str.should ==
- "http://example.com/?one=1&three=3"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-join|&|one,two,three}"
- )
- @partial_template = @initial_template.partial_expand({"two" => "2"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({
- "one" => "1", "two" => "2", "three" => "3"
- }).to_str.should ==
- @partial_template.expand({"one" => "1", "three" => "3"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/?two=2"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"one" => "1"}).to_str.should ==
- "http://example.com/?one=1&two=2"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"three" => "3"}).to_str.should ==
- "http://example.com/?two=2&three=3"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-join|&|one,two,three}"
- )
- @partial_template = @initial_template.partial_expand({"three" => "3"})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({
- "one" => "1", "two" => "2", "three" => "3"
- }).to_str.should ==
- @partial_template.expand({"one" => "1", "two" => "2"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/?three=3"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"one" => "1"}).to_str.should ==
- "http://example.com/?one=1&three=3"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"two" => "2"}).to_str.should ==
- "http://example.com/?two=2&three=3"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-join|&|one,two,three}"
- )
- @partial_template = @initial_template.partial_expand({
- "one" => "1", "two" => "2"
- })
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({
- "one" => "1", "two" => "2", "three" => "3"
- }).to_str.should ==
- @partial_template.expand({"three" => "3"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should ==
- "http://example.com/?one=1&two=2"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"three" => "3"}).to_str.should ==
- "http://example.com/?one=1&two=2&three=3"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-join|&|one,two,three}"
- )
- @partial_template = @initial_template.partial_expand({
- "one" => "1", "three" => "3"
- })
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({
- "one" => "1", "two" => "2", "three" => "3"
- }).to_str.should ==
- @partial_template.expand({"two" => "2"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should ==
- "http://example.com/?one=1&three=3"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"two" => "2"}).to_str.should ==
- "http://example.com/?one=1&two=2&three=3"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-join|&|one,two,three}"
- )
- @partial_template = @initial_template.partial_expand({
- "two" => "2", "three" => "3"
- })
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({
- "one" => "1", "two" => "2", "three" => "3"
- }).to_str.should ==
- @partial_template.expand({"one" => "1"}).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should ==
- "http://example.com/?two=2&three=3"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({"one" => "1"}).to_str.should ==
- "http://example.com/?one=1&two=2&three=3"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/?{-join|&|one,two,three}"
- )
- end
-
- it "should raise an error when partially expanding a bogus operator" do
- (lambda do
- @initial_template.partial_expand({"one" => ["1"]})
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- (lambda do
- @initial_template.partial_expand({"two" => "2", "three" => ["3"]})
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{-list|/|numbers}/{-list|/|letters}/"
- )
- @partial_template = @initial_template.partial_expand({})
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({
- "numbers" => ["1", "2", "3"], "letters" => ["a", "b", "c"]
- }).to_str.should == @partial_template.expand({
- "numbers" => ["1", "2", "3"], "letters" => ["a", "b", "c"]
- }).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com///"
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.pattern.should == @initial_template.pattern
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{-list|/|numbers}/{-list|/|letters}/"
- )
- @partial_template = @initial_template.partial_expand({
- "numbers" => ["1", "2", "3"]
- })
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({
- "numbers" => ["1", "2", "3"], "letters" => ["a", "b", "c"]
- }).to_str.should == @partial_template.expand({
- "letters" => ["a", "b", "c"]
- }).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com/1/2/3//"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{-list|/|numbers}/{-list|/|letters}/"
- )
- @partial_template = @initial_template.partial_expand({
- "letters" => ["a", "b", "c"]
- })
- end
-
- it "should produce the same result when fully expanded" do
- @initial_template.expand({
- "numbers" => ["1", "2", "3"], "letters" => ["a", "b", "c"]
- }).to_str.should == @partial_template.expand({
- "numbers" => ["1", "2", "3"]
- }).to_str
- end
-
- it "should produce the correct result when fully expanded" do
- @partial_template.expand({}).to_str.should == "http://example.com//a/b/c/"
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{-list|/|numbers}/{-list|/|letters}/"
- )
- end
-
- it "should raise an error when partially expanding a bogus operator" do
- (lambda do
- @initial_template.partial_expand({"numbers" => "1"})
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- (lambda do
- @initial_template.partial_expand({"letters" => "a"})
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
- end
-end
-
-describe Addressable::Template, "with a partially expanded template" do
- before do
- @initial_template = Addressable::Template.new(
- "http://example.com/{-bogus|/|one,two}/"
- )
- end
-
- it "should raise an error when partially expanding a bogus operator" do
- (lambda do
- @initial_template.partial_expand({"one" => "1"})
- end).should raise_error(
- Addressable::Template::InvalidTemplateOperatorError
- )
+ context "support regexes:" do
+ context "EXPRESSION" do
+ subject { Addressable::Template::EXPRESSION }
+ it "should be able to match an expression" do
+ subject.should match("{foo}")
+ subject.should match("{foo,9}")
+ subject.should match("{foo.bar,baz}")
+ subject.should match("{+foo.bar,baz}")
+ subject.should match("{foo,foo%20bar}")
+ subject.should match("{#foo:20,baz*}")
+ subject.should match("stuff{#foo:20,baz*}things")
+ end
+ it "should fail on non vars" do
+ subject.should_not match("!{foo")
+ subject.should_not match("{foo.bar.}")
+ subject.should_not match("!{}")
+ end
+ end
+ context "VARNAME" do
+ subject { Addressable::Template::VARNAME }
+ it "should be able to match a variable" do
+ subject.should match("foo")
+ subject.should match("9")
+ subject.should match("foo.bar")
+ subject.should match("foo_bar")
+ subject.should match("foo_bar.baz")
+ subject.should match("foo%20bar")
+ subject.should match("foo%20bar.baz")
+ end
+ it "should fail on non vars" do
+ subject.should_not match("!foo")
+ subject.should_not match("foo.bar.")
+ subject.should_not match("foo%2%00bar")
+ subject.should_not match("foo_ba%r")
+ subject.should_not match("foo_bar*")
+ subject.should_not match("foo_bar:20")
+ end
+ end
+ context "VARIABLE_LIST" do
+ subject { Addressable::Template::VARIABLE_LIST }
+ it "should be able to match a variable list" do
+ subject.should match("foo,bar")
+ subject.should match("foo")
+ subject.should match("foo,bar*,baz")
+ subject.should match("foo.bar,bar_baz*,baz:12")
+ end
+ it "should fail on non vars" do
+ subject.should_not match(",foo,bar*,baz")
+ subject.should_not match("foo,*bar,baz")
+ subject.should_not match("foo,,bar*,baz")
+ end
+ end
+ context "VARSPEC" do
+ subject { Addressable::Template::VARSPEC }
+ it "should be able to match a variable with modifier" do
+ subject.should match("9:8")
+ subject.should match("foo.bar*")
+ subject.should match("foo_bar:12")
+ subject.should match("foo_bar.baz*")
+ subject.should match("foo%20bar:12")
+ subject.should match("foo%20bar.baz*")
+ end
+ it "should fail on non vars" do
+ subject.should_not match("!foo")
+ subject.should_not match("*foo")
+ subject.should_not match("fo*o")
+ subject.should_not match("fo:o")
+ subject.should_not match("foo:")
+ end
+ end
end
end
diff --git a/spec/addressable/uri_template_spec.rb b/spec/addressable/uri_template_spec.rb
deleted file mode 100644
index b72ee622..00000000
--- a/spec/addressable/uri_template_spec.rb
+++ /dev/null
@@ -1,758 +0,0 @@
-require "addressable/uri_template"
-
-shared_examples_for 'expands' do |tests|
- tests.each do |template, expansion|
- it "#{template} to #{expansion}" do
- tmpl = Addressable::UriTemplate.new(template).expand(subject)
- tmpl.to_str.should == expansion
- end
- end
-end
-
-describe "Level 1:" do
- subject{
- {:var => "value", :hello => "Hello World!"}
- }
- it_behaves_like 'expands', {
- '{var}' => 'value',
- '{hello}' => 'Hello%20World%21'
- }
-end
-
-describe "Level 2" do
- subject{
- {
- :var => "value",
- :hello => "Hello World!",
- :path => "/foo/bar"
- }
- }
- context "Operator +:" do
- it_behaves_like 'expands', {
- '{+var}' => 'value',
- '{+hello}' => 'Hello%20World!',
- '{+path}/here' => '/foo/bar/here',
- 'here?ref={+path}' => 'here?ref=/foo/bar'
- }
- end
- context "Operator #:" do
- it_behaves_like 'expands', {
- 'X{#var}' => 'X#value',
- 'X{#hello}' => 'X#Hello%20World!',
- }
- end
-end
-
-describe "Level 3" do
- subject{
- {
- :var => "value",
- :hello => "Hello World!",
- :empty => "",
- :path => "/foo/bar",
- :x => "1024",
- :y => "768"
- }
- }
- context "Operator nil (multiple vars):" do
- it_behaves_like 'expands', {
- 'map?{x,y}' => 'map?1024,768',
- '{x,hello,y}' => '1024,Hello%20World%21,768'
- }
- end
- context "Operator + (multiple vars):" do
- it_behaves_like 'expands', {
- '{+x,hello,y}' => '1024,Hello%20World!,768',
- '{+path,x}/here' => '/foo/bar,1024/here',
- }
- end
- context "Operator # (multiple vars):" do
- it_behaves_like 'expands', {
- '{#x,hello,y}' => '#1024,Hello%20World!,768',
- '{#path,x}/here' => '#/foo/bar,1024/here',
- }
- end
- context "Operator ." do
- it_behaves_like 'expands', {
- 'X{.var}' => 'X.value',
- 'X{.x,y}' => 'X.1024.768',
- }
- end
- context "Operator /" do
- it_behaves_like 'expands', {
- '{/var}' => '/value',
- '{/var,x}/here' => '/value/1024/here',
- }
- end
- context "Operator ;" do
- it_behaves_like 'expands', {
- '{;x,y}' => ';x=1024;y=768',
- '{;x,y,empty}' => ';x=1024;y=768;empty',
- }
- end
- context "Operator ?" do
- it_behaves_like 'expands', {
- '{?x,y}' => '?x=1024&y=768',
- '{?x,y,empty}' => '?x=1024&y=768&empty=',
- }
- end
- context "Operator &" do
- it_behaves_like 'expands', {
- '?fixed=yes{&x}' => '?fixed=yes&x=1024',
- '{&x,y,empty}' => '&x=1024&y=768&empty=',
- }
- end
-end
-describe "Level 4" do
- subject{
- {
- :var => "value",
- :hello => "Hello World!",
- :path => "/foo/bar",
- :semi => ";",
- :list => %w(red green blue),
- :keys => {"semi" => ';', "dot" => '.', "comma" => ','}
- }
- }
- context "Expansion with value modifiers" do
- it_behaves_like 'expands', {
- '{var:3}' => 'val',
- '{var:30}' => 'value',
- '{list}' => 'red,green,blue',
- '{list*}' => 'red,green,blue',
- '{keys}' => 'semi,%3B,dot,.,comma,%2C',
- '{keys*}' => 'semi=%3B,dot=.,comma=%2C',
- }
- end
- context "Operator + with value modifiers" do
- it_behaves_like 'expands', {
- '{+path:6}/here' => '/foo/b/here',
- '{+list}' => 'red,green,blue',
- '{+list*}' => 'red,green,blue',
- '{+keys}' => 'semi,;,dot,.,comma,,',
- '{+keys*}' => 'semi=;,dot=.,comma=,',
- }
- end
- context "Operator # with value modifiers" do
- it_behaves_like 'expands', {
- '{#path:6}/here' => '#/foo/b/here',
- '{#list}' => '#red,green,blue',
- '{#list*}' => '#red,green,blue',
- '{#keys}' => '#semi,;,dot,.,comma,,',
- '{#keys*}' => '#semi=;,dot=.,comma=,',
- }
- end
- context "Operator . with value modifiers" do
- it_behaves_like 'expands', {
- 'X{.var:3}' => 'X.val',
- 'X{.list}' => 'X.red,green,blue',
- 'X{.list*}' => 'X.red.green.blue',
- 'X{.keys}' => 'X.semi,%3B,dot,.,comma,%2C',
- 'X{.keys*}' => 'X.semi=%3B.dot=..comma=%2C',
- }
- end
- context "Operator / with value modifiers" do
- it_behaves_like 'expands', {
- '{/var:1,var}' => '/v/value',
- '{/list}' => '/red,green,blue',
- '{/list*}' => '/red/green/blue',
- '{/list*,path:4}' => '/red/green/blue/%2Ffoo',
- '{/keys}' => '/semi,%3B,dot,.,comma,%2C',
- '{/keys*}' => '/semi=%3B/dot=./comma=%2C',
- }
- end
- context "Operator ; with value modifiers" do
- it_behaves_like 'expands', {
- '{;hello:5}' => ';hello=Hello',
- '{;list}' => ';list=red,green,blue',
- '{;list*}' => ';list=red;list=green;list=blue',
- '{;keys}' => ';keys=semi,%3B,dot,.,comma,%2C',
- '{;keys*}' => ';semi=%3B;dot=.;comma=%2C',
- }
- end
- context "Operator ? with value modifiers" do
- it_behaves_like 'expands', {
- '{?var:3}' => '?var=val',
- '{?list}' => '?list=red,green,blue',
- '{?list*}' => '?list=red&list=green&list=blue',
- '{?keys}' => '?keys=semi,%3B,dot,.,comma,%2C',
- '{?keys*}' => '?semi=%3B&dot=.&comma=%2C',
- }
- end
- context "Operator & with value modifiers" do
- it_behaves_like 'expands', {
- '{&var:3}' => '&var=val',
- '{&list}' => '&list=red,green,blue',
- '{&list*}' => '&list=red&list=green&list=blue',
- '{&keys}' => '&keys=semi,%3B,dot,.,comma,%2C',
- '{&keys*}' => '&semi=%3B&dot=.&comma=%2C',
- }
- end
-end
-describe "Modifiers" do
- subject{
- {
- :var => "value",
- :semi => ";",
- :year => %w(1965 2000 2012),
- :dom => %w(example com)
- }
- }
- context "length" do
- it_behaves_like 'expands', {
- '{var:3}' => 'val',
- '{var:30}' => 'value',
- '{var}' => 'value',
- '{semi}' => '%3B',
- '{semi:2}' => '%3B',
- }
- end
- context "explode" do
- it_behaves_like 'expands', {
- 'find{?year*}' => 'find?year=1965&year=2000&year=2012',
- 'www{.dom*}' => 'www.example.com',
- }
- end
-end
-describe "Expansion" do
- subject{
- {
- :count => [ "one", "two", "three" ],
- :dom => [ "example", "com" ],
- :dub => "me/too",
- :hello => "Hello World!",
- :half => "50%",
- :var => "value",
- :who => "fred",
- :base => "http://example.com/home/",
- :path => "/foo/bar",
- :list => [ "red", "green", "blue" ],
- :keys => { "semi"=>";","dot"=>".","comma"=>"," },
- :v => "6",
- :x => "1024",
- :y => "768",
- :empty => "",
- :empty_keys => {},
- :undef => nil,
- }
- }
- context "concatenation" do
- it_behaves_like 'expands', {
- '{count}' => 'one,two,three',
- '{count*}' => 'one,two,three',
- '{/count}' => '/one,two,three',
- '{/count*}' => '/one/two/three',
- '{;count}' => ';count=one,two,three',
- '{;count*}' => ';count=one;count=two;count=three',
- '{?count}' => '?count=one,two,three',
- '{?count*}' => '?count=one&count=two&count=three',
- '{&count*}' => '&count=one&count=two&count=three',
- }
- end
- context "simple expansion" do
- it_behaves_like 'expands', {
- '{var}' => 'value',
- '{hello}' => 'Hello%20World%21',
- '{half}' => '50%25',
- 'O{empty}X' => 'OX',
- 'O{undef}X' => 'OX',
- '{x,y}' => '1024,768',
- '{x,hello,y}' => '1024,Hello%20World%21,768',
- '?{x,empty}' => '?1024,',
- '?{x,undef}' => '?1024',
- '?{undef,y}' => '?768',
- '{var:3}' => 'val',
- '{var:30}' => 'value',
- '{list}' => 'red,green,blue',
- '{list*}' => 'red,green,blue',
- '{keys}' => 'semi,%3B,dot,.,comma,%2C',
- '{keys*}' => 'semi=%3B,dot=.,comma=%2C',
- }
- end
- context "reserved expansion (+)" do
- it_behaves_like 'expands', {
- '{+var}' => 'value',
- '{+hello}' => 'Hello%20World!',
- '{+half}' => '50%25',
- '{base}index' => 'http%3A%2F%2Fexample.com%2Fhome%2Findex',
- '{+base}index' => 'http://example.com/home/index',
- 'O{+empty}X' => 'OX',
- 'O{+undef}X' => 'OX',
- '{+path}/here' => '/foo/bar/here',
- 'here?ref={+path}' => 'here?ref=/foo/bar',
- 'up{+path}{var}/here' => 'up/foo/barvalue/here',
- '{+x,hello,y}' => '1024,Hello%20World!,768',
- '{+path,x}/here' => '/foo/bar,1024/here',
- '{+path:6}/here' => '/foo/b/here',
- '{+list}' => 'red,green,blue',
- '{+list*}' => 'red,green,blue',
- '{+keys}' => 'semi,;,dot,.,comma,,',
- '{+keys*}' => 'semi=;,dot=.,comma=,',
- }
- end
- context "fragment expansion (#)" do
- it_behaves_like 'expands', {
- '{#var}' => '#value',
- '{#hello}' => '#Hello%20World!',
- '{#half}' => '#50%25',
- 'foo{#empty}' => 'foo#',
- 'foo{#undef}' => 'foo',
- '{#x,hello,y}' => '#1024,Hello%20World!,768',
- '{#path,x}/here' => '#/foo/bar,1024/here',
- '{#path:6}/here' => '#/foo/b/here',
- '{#list}' => '#red,green,blue',
- '{#list*}' => '#red,green,blue',
- '{#keys}' => '#semi,;,dot,.,comma,,',
- '{#keys*}' => '#semi=;,dot=.,comma=,',
- }
- end
- context "label expansion (.)" do
- it_behaves_like 'expands', {
- '{.who}' => '.fred',
- '{.who,who}' => '.fred.fred',
- '{.half,who}' => '.50%25.fred',
- 'www{.dom*}' => 'www.example.com',
- 'X{.var}' => 'X.value',
- 'X{.empty}' => 'X.',
- 'X{.undef}' => 'X',
- 'X{.var:3}' => 'X.val',
- 'X{.list}' => 'X.red,green,blue',
- 'X{.list*}' => 'X.red.green.blue',
- 'X{.keys}' => 'X.semi,%3B,dot,.,comma,%2C',
- 'X{.keys*}' => 'X.semi=%3B.dot=..comma=%2C',
- 'X{.empty_keys}' => 'X',
- 'X{.empty_keys*}' => 'X',
- }
- end
- context "path expansion (/)" do
- it_behaves_like 'expands', {
- '{/who}' => '/fred',
- '{/who,who}' => '/fred/fred',
- '{/half,who}' => '/50%25/fred',
- '{/who,dub}' => '/fred/me%2Ftoo',
- '{/var}' => '/value',
- '{/var,empty}' => '/value/',
- '{/var,undef}' => '/value',
- '{/var,x}/here' => '/value/1024/here',
- '{/var:1,var}' => '/v/value',
- '{/list}' => '/red,green,blue',
- '{/list*}' => '/red/green/blue',
- '{/list*,path:4}' => '/red/green/blue/%2Ffoo',
- '{/keys}' => '/semi,%3B,dot,.,comma,%2C',
- '{/keys*}' => '/semi=%3B/dot=./comma=%2C',
- }
- end
- context "path-style expansion (;)" do
- it_behaves_like 'expands', {
- '{;who}' => ';who=fred',
- '{;half}' => ';half=50%25',
- '{;empty}' => ';empty',
- '{;v,empty,who}' => ';v=6;empty;who=fred',
- '{;v,bar,who}' => ';v=6;who=fred',
- '{;x,y}' => ';x=1024;y=768',
- '{;x,y,empty}' => ';x=1024;y=768;empty',
- '{;x,y,undef}' => ';x=1024;y=768',
- '{;hello:5}' => ';hello=Hello',
- '{;list}' => ';list=red,green,blue',
- '{;list*}' => ';list=red;list=green;list=blue',
- '{;keys}' => ';keys=semi,%3B,dot,.,comma,%2C',
- '{;keys*}' => ';semi=%3B;dot=.;comma=%2C',
- }
- end
- context "form query expansion (?)" do
- it_behaves_like 'expands', {
- '{?who}' => '?who=fred',
- '{?half}' => '?half=50%25',
- '{?x,y}' => '?x=1024&y=768',
- '{?x,y,empty}' => '?x=1024&y=768&empty=',
- '{?x,y,undef}' => '?x=1024&y=768',
- '{?var:3}' => '?var=val',
- '{?list}' => '?list=red,green,blue',
- '{?list*}' => '?list=red&list=green&list=blue',
- '{?keys}' => '?keys=semi,%3B,dot,.,comma,%2C',
- '{?keys*}' => '?semi=%3B&dot=.&comma=%2C',
- }
- end
- context "form query expansion (&)" do
- it_behaves_like 'expands', {
- '{&who}' => '&who=fred',
- '{&half}' => '&half=50%25',
- '?fixed=yes{&x}' => '?fixed=yes&x=1024',
- '{&x,y,empty}' => '&x=1024&y=768&empty=',
- '{&x,y,undef}' => '&x=1024&y=768',
- '{&var:3}' => '&var=val',
- '{&list}' => '&list=red,green,blue',
- '{&list*}' => '&list=red&list=green&list=blue',
- '{&keys}' => '&keys=semi,%3B,dot,.,comma,%2C',
- '{&keys*}' => '&semi=%3B&dot=.&comma=%2C',
- }
- end
-end
-
-class ExampleTwoProcessor
- def self.restore(name, value)
- return value.gsub(/-/, " ") if name == "query"
- return value
- end
-
- def self.match(name)
- return ".*?" if name == "first"
- return ".*"
- end
- def self.validate(name, value)
- return !!(value =~ /^[\w ]+$/) if name == "query"
- return true
- end
-
- def self.transform(name, value)
- return value.gsub(/ /, "+") if name == "query"
- return value
- end
-end
-
-
-describe Addressable::UriTemplate do
- describe "Matching" do
- let(:uri){
- Addressable::URI.parse(
- "http://example.com/search/an-example-search-query/"
- )
- }
- let(:uri2){
- Addressable::URI.parse("http://example.com/a/b/c/")
- }
- let(:uri3){
- Addressable::URI.parse("http://example.com/;a=1;b=2;c=3;first=foo")
- }
- let(:uri4){
- Addressable::URI.parse("http://example.com/?a=1&b=2&c=3&first=foo")
- }
- context "first uri with ExampleTwoProcessor" do
- subject{
- match = Addressable::UriTemplate.new(
- "http://example.com/search/{query}/"
- ).match(uri, ExampleTwoProcessor)
- }
- its(:variables){ should == ["query"]}
- its(:captures){ should == ["an example search query"]}
- end
-
- context "second uri with ExampleTwoProcessor" do
- subject{
- match = Addressable::UriTemplate.new(
- "http://example.com/{first}/{+second}/"
- ).match(uri2, ExampleTwoProcessor)
- }
- its(:variables){ should == ["first", "second"]}
- its(:captures){ should == ["a", "b/c"] }
- end
- context "second uri" do
- subject{
- match = Addressable::UriTemplate.new(
- "http://example.com/{first}{/second*}/"
- ).match(uri2)
- }
- its(:variables){ should == ["first", "second"]}
- its(:captures){ should == ["a", ["b","c"]] }
- end
- context "third uri" do
- subject{
- match = Addressable::UriTemplate.new(
- "http://example.com/{;hash*,first}"
- ).match(uri3)
- }
- its(:variables){ should == ["hash", "first"]}
- its(:captures){ should == [
- {"a" => "1", "b" => "2", "c" => "3", "first" => "foo"}, nil] }
- end
- context "fourth uri" do
- subject{
- match = Addressable::UriTemplate.new(
- "http://example.com/{?hash*,first}"
- ).match(uri4)
- }
- its(:variables){ should == ["hash", "first"]}
- its(:captures){ should == [
- {"a" => "1", "b" => "2", "c" => "3", "first"=> "foo"}, nil] }
- end
- end
- describe "extract" do
- let(:template) {
- Addressable::UriTemplate.new(
- "http://{host}{/segments*}/{?one,two,bogus}{#fragment}"
- )
- }
- let(:uri){ "http://example.com/a/b/c/?one=1&two=2#foo" }
- it "should be able to extract" do
- template.extract(uri).should == {
- "host" => "example.com",
- "segments" => %w(a b c),
- "one" => "1",
- "bogus" => nil,
- "two" => "2",
- "fragment" => "foo"
- }
- end
- end
- describe "Partial expand" do
- context "partial_expand with two simple values" do
- subject{
- Addressable::UriTemplate.new("http://example.com/{one}/{two}/")
- }
- it "builds a new pattern" do
- subject.partial_expand("one" => "1").pattern.should ==
- "http://example.com/1/{two}/"
- end
- end
- context "partial_expand query with missing param in middle" do
- subject{
- Addressable::UriTemplate.new("http://example.com/{?one,two,three}/")
- }
- it "builds a new pattern" do
- subject.partial_expand("one" => "1", "three" => "3").pattern.should ==
- "http://example.com/?one=1{&two}&three=3/"
- end
- end
- context "partial_expand with query string" do
- subject{
- Addressable::UriTemplate.new("http://example.com/{?two,one}/")
- }
- it "builds a new pattern" do
- subject.partial_expand("one" => "1").pattern.should ==
- "http://example.com/{?two}&one=1/"
- end
- end
- context "partial_expand with path operator" do
- subject{
- Addressable::UriTemplate.new("http://example.com{/one,two}/")
- }
- it "builds a new pattern" do
- subject.partial_expand("one" => "1").pattern.should ==
- "http://example.com/1{/two}/"
- end
- end
- end
- describe "Expand" do
- context "expand with a processor" do
- subject{
- Addressable::UriTemplate.new("http://example.com/search/{query}/")
- }
- it "processes spaces" do
- subject.expand({"query" => "an example search query"},
- ExampleTwoProcessor).to_str.should ==
- "http://example.com/search/an+example+search+query/"
- end
- it "validates" do
- lambda{
- subject.expand({"query" => "Bogus!"},
- ExampleTwoProcessor).to_str
- }.should raise_error(Addressable::Template::InvalidTemplateValueError)
- end
- end
- context "partial_expand query with missing param in middle" do
- subject{
- Addressable::UriTemplate.new("http://example.com/{?one,two,three}/")
- }
- it "builds a new pattern" do
- subject.partial_expand("one" => "1", "three" => "3").pattern.should ==
- "http://example.com/?one=1{&two}&three=3/"
- end
- end
- context "partial_expand with query string" do
- subject{
- Addressable::UriTemplate.new("http://example.com/{?two,one}/")
- }
- it "builds a new pattern" do
- subject.partial_expand("one" => "1").pattern.should ==
- "http://example.com/{?two}&one=1/"
- end
- end
- context "partial_expand with path operator" do
- subject{
- Addressable::UriTemplate.new("http://example.com{/one,two}/")
- }
- it "builds a new pattern" do
- subject.partial_expand("one" => "1").pattern.should ==
- "http://example.com/1{/two}/"
- end
- end
- end
- context "Matching with operators" do
- describe "Level 1:" do
- subject { Addressable::UriTemplate.new("foo{foo}/{bar}baz") }
- it "can match" do
- data = subject.match("foofoo/bananabaz")
- data.mapping["foo"].should == "foo"
- data.mapping["bar"].should == "banana"
- end
- it "lists vars" do
- subject.variables.should == ["foo", "bar"]
- end
- end
-
- describe "Level 2:" do
- subject { Addressable::UriTemplate.new("foo{+foo}{#bar}baz") }
- it "can match" do
- data = subject.match("foo/test/banana#bazbaz")
- data.mapping["foo"].should == "/test/banana"
- data.mapping["bar"].should == "baz"
- end
- it "lists vars" do
- subject.variables.should == ["foo", "bar"]
- end
- end
-
- describe "Level 3:" do
- context "no operator" do
- subject { Addressable::UriTemplate.new("foo{foo,bar}baz") }
- it "can match" do
- data = subject.match("foofoo,barbaz")
- data.mapping["foo"].should == "foo"
- data.mapping["bar"].should == "bar"
- end
- it "lists vars" do
- subject.variables.should == ["foo", "bar"]
- end
- end
- context "+ operator" do
- subject { Addressable::UriTemplate.new("foo{+foo,bar}baz") }
- it "can match" do
- data = subject.match("foofoo/bar,barbaz")
- data.mapping["bar"].should == "foo/bar,bar"
- data.mapping["foo"].should == ""
- end
- it "lists vars" do
- subject.variables.should == ["foo", "bar"]
- end
- end
- context ". operator" do
- subject { Addressable::UriTemplate.new("foo{.foo,bar}baz") }
- it "can match" do
- data = subject.match("foo.foo.barbaz")
- data.mapping["foo"].should == "foo"
- data.mapping["bar"].should == "bar"
- end
- it "lists vars" do
- subject.variables.should == ["foo", "bar"]
- end
- end
- context "/ operator" do
- subject { Addressable::UriTemplate.new("foo{/foo,bar}baz") }
- it "can match" do
- data = subject.match("foo/foo/barbaz")
- data.mapping["foo"].should == "foo"
- data.mapping["bar"].should == "bar"
- end
- it "lists vars" do
- subject.variables.should == ["foo", "bar"]
- end
- end
- context "; operator" do
- subject { Addressable::UriTemplate.new("foo{;foo,bar,baz}baz") }
- it "can match" do
- data = subject.match("foo;foo=bar%20baz;bar=foo;bazbaz")
- data.mapping["foo"].should == "bar baz"
- data.mapping["bar"].should == "foo"
- data.mapping["baz"].should == ""
- end
- it "lists vars" do
- subject.variables.should == %w(foo bar baz)
- end
- end
- context "? operator" do
- subject { Addressable::UriTemplate.new("foo{?foo,bar}baz") }
- it "can match" do
- data = subject.match("foo?foo=bar%20baz&bar=foobaz")
- data.mapping["foo"].should == "bar baz"
- data.mapping["bar"].should == "foo"
- end
- it "lists vars" do
- subject.variables.should == %w(foo bar)
- end
- end
- context "& operator" do
- subject { Addressable::UriTemplate.new("foo{&foo,bar}baz") }
- it "can match" do
- data = subject.match("foo&foo=bar%20baz&bar=foobaz")
- data.mapping["foo"].should == "bar baz"
- data.mapping["bar"].should == "foo"
- end
- it "lists vars" do
- subject.variables.should == %w(foo bar)
- end
- end
- end
- end
-
- context "support regexes:" do
- context "EXPRESSION" do
- subject { Addressable::UriTemplate::EXPRESSION }
- it "should be able to match an expression" do
- subject.should match("{foo}")
- subject.should match("{foo,9}")
- subject.should match("{foo.bar,baz}")
- subject.should match("{+foo.bar,baz}")
- subject.should match("{foo,foo%20bar}")
- subject.should match("{#foo:20,baz*}")
- subject.should match("stuff{#foo:20,baz*}things")
- end
- it "should fail on non vars" do
- subject.should_not match("!{foo")
- subject.should_not match("{foo.bar.}")
- subject.should_not match("!{}")
- end
- end
- context "VARNAME" do
- subject { Addressable::UriTemplate::VARNAME }
- it "should be able to match a variable" do
- subject.should match("foo")
- subject.should match("9")
- subject.should match("foo.bar")
- subject.should match("foo_bar")
- subject.should match("foo_bar.baz")
- subject.should match("foo%20bar")
- subject.should match("foo%20bar.baz")
- end
- it "should fail on non vars" do
- subject.should_not match("!foo")
- subject.should_not match("foo.bar.")
- subject.should_not match("foo%2%00bar")
- subject.should_not match("foo_ba%r")
- subject.should_not match("foo_bar*")
- subject.should_not match("foo_bar:20")
- end
- end
- context "VARIABLE_LIST" do
- subject { Addressable::UriTemplate::VARIABLE_LIST }
- it "should be able to match a variable list" do
- subject.should match("foo,bar")
- subject.should match("foo")
- subject.should match("foo,bar*,baz")
- subject.should match("foo.bar,bar_baz*,baz:12")
- end
- it "should fail on non vars" do
- subject.should_not match(",foo,bar*,baz")
- subject.should_not match("foo,*bar,baz")
- subject.should_not match("foo,,bar*,baz")
- end
- end
- context "VARSPEC" do
- subject { Addressable::UriTemplate::VARSPEC }
- it "should be able to match a variable with modifier" do
- subject.should match("9:8")
- subject.should match("foo.bar*")
- subject.should match("foo_bar:12")
- subject.should match("foo_bar.baz*")
- subject.should match("foo%20bar:12")
- subject.should match("foo%20bar.baz*")
- end
- it "should fail on non vars" do
- subject.should_not match("!foo")
- subject.should_not match("*foo")
- subject.should_not match("fo*o")
- subject.should_not match("fo:o")
- subject.should_not match("foo:")
- end
- end
- end
-end