uri-template-0.7.0/0000755000175000017500000000000013724671637013172 5ustar pravipraviuri-template-0.7.0/lib/0000755000175000017500000000000013724671637013740 5ustar pravipraviuri-template-0.7.0/lib/uri_template.rb0000644000175000017500000003443213724671637016765 0ustar pravipravi# -*- encoding : utf-8 -*- # The MIT License (MIT) # # Copyright (c) 2011-2014 Hannes Georg # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # A base module for all implementations of a uri template. module URITemplate # Helper module which defines class methods for all uri template # classes. module ClassMethods # Tries to convert the given argument into an {URITemplate}. # Returns nil if this fails. # # @return [nil|{URITemplate}] def try_convert(x) if x.kind_of? self return x elsif x.kind_of? String return self.new(x) else return nil end end # Same as {.try_convert} but raises an ArgumentError when the given argument could not be converted. # # @raise ArgumentError if the argument is unconvertable # @return {URITemplate} def convert(x) o = self.try_convert(x) if o.nil? raise ArgumentError, "Expected to receive something that can be converted into a URITemplate of type #{self.inspect}, but got #{x.inspect}" end return o end def included(base) base.extend(ClassMethods) end end extend ClassMethods # @api private SCHEME_REGEX = /\A[a-z]+:/i.freeze # @api private HOST_REGEX = /\A(?:[a-z]+:)?\/\/[^\/]+/i.freeze # @api private URI_SPLIT = /\A(?:([a-z]+):)?(?:\/\/)?([^\/]+)?/i.freeze autoload :Utils, 'uri_template/utils' autoload :Token, 'uri_template/token' autoload :Literal, 'uri_template/literal' autoload :Expression, 'uri_template/expression' autoload :RFC6570, 'uri_template/rfc6570' autoload :Colon, 'uri_template/colon' # A hash with all available implementations. # @see resolve_class VERSIONS = { :rfc6570 => :RFC6570, :default => :RFC6570, :colon => :Colon, :latest => :RFC6570 } # Looks up which implementation to use. # Extracts all symbols from args and looks up the first in {VERSIONS}. # # @return Array an array of the class to use and the unused parameters. # # @example # URITemplate.resolve_class() #=> [ URITemplate::RFC6570, [] ] # URITemplate.resolve_class(:colon) #=> [ URITemplate::Colon, [] ] # URITemplate.resolve_class("template",:rfc6570) #=> [ URITemplate::RFC6570, ["template"] ] # # @raise ArgumentError when no class was found. # def self.resolve_class(*args) symbols, rest = args.partition{|x| x.kind_of? Symbol } version = symbols.fetch(0, :default) raise ArgumentError, "Unknown template version #{version.inspect}, defined versions: #{VERSIONS.keys.inspect}" unless VERSIONS.key?(version) return self.const_get(VERSIONS[version]), rest end # Creates an uri template using an implementation. # The args should at least contain a pattern string. # Symbols in the args are used to determine the actual implementation. # # @example # tpl = URITemplate.new('{x}') # a new template using the default implementation # tpl.expand('x'=>'y') #=> 'y' # # @example # tpl = URITemplate.new(:colon,'/:x') # a new template using the colon implementation # def self.new(*args) klass, rest = resolve_class(*args) return klass.new(*rest) end # Tries to coerce two URITemplates into a common representation. # Returns an array with two {URITemplate}s and two booleans indicating which of the two were converted or raises an ArgumentError. # # @example # URITemplate.coerce( URITemplate.new(:rfc6570,'{x}'), '{y}' ) #=> [URITemplate.new(:rfc6570,'{x}'), URITemplate.new(:rfc6570,'{y}'), false, true] # URITemplate.coerce( '{y}', URITemplate.new(:rfc6570,'{x}') ) #=> [URITemplate.new(:rfc6570,'{y}'), URITemplate.new(:rfc6570,'{x}'), true, false] # # @return [Tuple] def self.coerce(a,b) if a.kind_of? URITemplate if a.class == b.class return [a,b,false,false] end b_as_a = a.class.try_convert(b) if b_as_a return [a,b_as_a,false,true] end end if b.kind_of? URITemplate a_as_b = b.class.try_convert(a) if a_as_b return [a_as_b, b, true, false] end end bc = self.try_convert(b) if bc.kind_of? URITemplate a_as_b = bc.class.try_convert(a) if a_as_b return [a_as_b, bc, true, true] end end raise ArgumentError, "Expected at least on URITemplate, but got #{a.inspect} and #{b.inspect}" unless a.kind_of? URITemplate or b.kind_of? URITemplate raise ArgumentError, "Cannot coerce #{a.inspect} and #{b.inspect} into a common representation." end # Applies a method to a URITemplate with another URITemplate as argument. # This is a useful shorthand since both URITemplates are automatically coerced. # # @example # tpl = URITemplate.new('foo') # URITemplate.apply( tpl, :/, 'bar' ).pattern #=> 'foo/bar' # URITemplate.apply( 'baz', :/, tpl ).pattern #=> 'baz/foo' # URITemplate.apply( 'bla', :/, 'blub' ).pattern #=> 'bla/blub' # def self.apply(a, method, b, *args) a,b,_,_ = self.coerce(a,b) a.send(method,b,*args) end # @api private def self.coerce_first_arg(meth) alias_method( (meth.to_s + '_without_coercion').to_sym , meth ) class_eval(<<-RUBY) def #{meth}(other, *args, &block) this, other, this_converted, _ = URITemplate.coerce( self, other ) if this_converted return this.#{meth}(other,*args, &block) end return #{meth}_without_coercion(other,*args, &block) end RUBY end # A base class for all errors which will be raised upon invalid syntax. module Invalid end # A base class for all errors which will be raised when a variable value # is not allowed for a certain expansion. module InvalidValue end # Expands this uri template with the given variables. # The variables should be converted to strings using {Utils#object_to_param}. # # The keys in the variables hash are converted to # strings in order to support the Ruby 1.9 hash syntax. # # If the variables are given as an array, they will be matched against the variables in the template based on their order. # # @raise {Unconvertable} if a variable could not be converted to a string. # @raise {InvalidValue} if a value is not suiteable for a certain variable ( e.g. a string when a list is expected ). # # @param variables [#map, Array] # @return String def expand(variables = {}) variables = normalize_variables(variables) tokens.map{|part| part.expand(variables) }.join end # Works like #expand with two differences: # # - the result is a uri template instead of string # - undefined variables are left in the template # # @see {#expand} # @param variables [#map, Array] # @return [URITemplate] def expand_partial(variables = {}) variables = normalize_variables(variables) self.class.new(tokens.map{|part| part.expand_partial(variables) }.flatten(1)) end def normalize_variables( variables ) raise ArgumentError, "Expected something that responds to :map, but got: #{variables.inspect}" unless variables.respond_to? :map if variables.kind_of?(Array) return Hash[self.variables.zip(variables)] else # Stringify variables arg = variables.map{ |k, v| [k.to_s, v] } if arg.any?{|elem| !elem.kind_of?(Array) } raise ArgumentError, "Expected the output of variables.map to return an array of arrays but got #{arg.inspect}" end return Hash[arg] end end private :normalize_variables # @abstract # Returns the type of this template. The type is a symbol which can be used in {.resolve_class} to resolve the type of this template. # # @return [Symbol] def type raise "Please implement #type on #{self.class.inspect}." end # @abstract # Returns the tokens of this templates. Tokens should include either {Literal} or {Expression}. # # @return [Array] def tokens raise "Please implement #tokens on #{self.class.inspect}." end # Returns an array containing all variables. Repeated variables are ignored. The concrete order of the variables may change. # @example # URITemplate.new('{foo}{bar}{baz}').variables #=> ['foo','bar','baz'] # URITemplate.new('{a}{c}{a}{b}').variables #=> ['a','c','b'] # # @return [Array] def variables @variables ||= tokens.map(&:variables).flatten.uniq.freeze end # Returns the number of static characters in this template. # This method is useful for routing, since it's often pointful to use the url with fewer variable characters. # For example 'static' and 'sta\\{var\\}' both match 'static', but in most cases 'static' should be prefered over 'sta\\{var\\}' since it's more specific. # # @example # URITemplate.new('/xy/').static_characters #=> 4 # URITemplate.new('{foo}').static_characters #=> 0 # URITemplate.new('a{foo}b').static_characters #=> 2 # # @return Numeric def static_characters @static_characters ||= tokens.select(&:literal?).map{|t| t.string.size }.inject(0,:+) end # Returns whether this uri-template includes a host name # # This method is usefull to check wheter this template will generate # or match a uri with a host. # # @see #scheme? # # @example # URITemplate.new('/foo').host? #=> false # URITemplate.new('//example.com/foo').host? #=> true # URITemplate.new('//{host}/foo').host? #=> true # URITemplate.new('http://example.com/foo').host? #=> true # URITemplate.new('{scheme}://example.com/foo').host? #=> true # def host? return scheme_and_host[1] end # Returns whether this uri-template includes a scheme # # This method is usefull to check wheter this template will generate # or match a uri with a scheme. # # @see #host? # # @example # URITemplate.new('/foo').scheme? #=> false # URITemplate.new('//example.com/foo').scheme? #=> false # URITemplate.new('http://example.com/foo').scheme? #=> true # URITemplate.new('{scheme}://example.com/foo').scheme? #=> true # def scheme? return scheme_and_host[0] end # Returns the pattern for this template. # # @return String def pattern @pattern ||= tokens.map(&:to_s).join end alias to_s pattern # Compares two template patterns. def eq(other) return self.pattern == other.pattern end coerce_first_arg :eq alias == eq # Tries to concatenate two templates, as if they were path segments. # Removes double slashes or insert one if they are missing. # # @example # tpl = URITemplate::RFC6570.new('/xy/') # (tpl / '/z/' ).pattern #=> '/xy/z/' # (tpl / 'z/' ).pattern #=> '/xy/z/' # (tpl / '{/z}' ).pattern #=> '/xy{/z}' # (tpl / 'a' / 'b' ).pattern #=> '/xy/a/b' # # @param other [URITemplate, String, ...] # @return URITemplate def path_concat(other) if other.host? or other.scheme? raise ArgumentError, "Expected to receive a relative template but got an absoulte one: #{other.inspect}. If you think this is a bug, please report it." end return self if other.tokens.none? return other if self.tokens.none? tail = self.tokens.last head = other.tokens.first if tail.ends_with_slash? if head.starts_with_slash? return self.class.new( remove_double_slash(self.tokens, other.tokens).join ) end elsif !head.starts_with_slash? return self.class.new( (self.tokens + ['/'] + other.tokens).join) end return self.class.new( (self.tokens + other.tokens).join ) end coerce_first_arg :path_concat alias / path_concat # Concatenate two template with conversion. # # @example # tpl = URITemplate::RFC6570.new('foo') # (tpl + '{bar}' ).pattern #=> 'foo{bar}' # # @param other [URITemplate, String, ...] # @return URITemplate def concat(other) if other.host? or other.scheme? raise ArgumentError, "Expected to receive a relative template but got an absoulte one: #{other.inspect}. If you think this is a bug, please report it." end return self if other.tokens.none? return other if self.tokens.none? return self.class.new( self.to_s + other.to_s ) end coerce_first_arg :concat alias + concat alias >> concat # @api private def remove_double_slash( first_tokens, second_tokens ) if first_tokens.last.literal? return first_tokens[0..-2] + [ first_tokens.last.to_s[0..-2] ] + second_tokens elsif second_tokens.first.literal? return first_tokens + [ second_tokens.first.to_s[1..-1] ] + second_tokens[1..-1] else raise ArgumentError, "Cannot remove double slashes from #{first_tokens.inspect} and #{second_tokens.inspect}." end end private :remove_double_slash # @api private def scheme_and_host return @scheme_and_host if @scheme_and_host read_chars = "" @scheme_and_host = [false,false] tokens.each do |token| if token.expression? read_chars << "x" if token.scheme? read_chars << ':' end if token.host? read_chars << '//' end read_chars << "x" elsif token.literal? read_chars << token.string end if read_chars =~ SCHEME_REGEX @scheme_and_host = [true, true] break elsif read_chars =~ HOST_REGEX @scheme_and_host[1] = true break elsif read_chars =~ /(^|[^:\/])\/(?!\/)/ break end end return @scheme_and_host end private :scheme_and_host alias absolute? host? # Opposite of {#absolute?} def relative? !absolute? end end uri-template-0.7.0/lib/uri_template/0000755000175000017500000000000013724671637016432 5ustar pravipraviuri-template-0.7.0/lib/uri_template/colon.rb0000644000175000017500000001177113724671637020100 0ustar pravipravi# -*- encoding : utf-8 -*- # The MIT License (MIT) # # Copyright (c) 2011-2014 Hannes Georg # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'forwardable' require 'uri_template' require 'uri_template/utils' module URITemplate # A colon based template denotes variables with a colon. # # This template type is somewhat compatible with sinatra. # # @example # tpl = URITemplate::Colon.new('/foo/:bar') # tpl.extract('/foo/baz') #=> {'bar'=>'baz'} # tpl.expand('bar'=>'boom') #=> '/foo/boom' # class Colon include URITemplate VAR = /(?:\{:(\w+)\}|:(\w+)(?!\w)|\*)/u class InvalidValue < StandardError include URITemplate::InvalidValue attr_reader :variable, :value def initialize(variable, value) @variable = variable @value = value super(generate_message()) end class SplatIsNotAnArray < self end protected def generate_message() return "The template variable " + variable.inspect + " cannot expand the given value "+ value.inspect end end class Token class Variable < self include URITemplate::Expression attr_reader :name def initialize(name) @name = name @variables = [name] end def expand(vars) return Utils.escape_url(Utils.object_to_param(vars[name])) end def to_r return '([^/]*?)' end def to_s return ":#{name}" end end class Splat < Variable SPLAT = 'splat'.freeze attr_reader :index def initialize(index) @index = index super(SPLAT) end def expand(vars) var = vars[name] if Array === var return Utils.escape_uri(Utils.object_to_param(var[index])) else raise InvalidValue::SplatIsNotAnArray.new(name,var) end end def to_r return '(.+?)' end end class Static < self include URITemplate::Literal def initialize(str) @string = str end def expand(_) return @string end def to_r return Regexp.escape(@string) end end end attr_reader :pattern # Tries to convert the value into a colon-template. # @example # URITemplate::Colon.try_convert('/foo/:bar/').pattern #=> '/foo/:bar/' # URITemplate::Colon.try_convert(URITemplate.new(:rfc6570, '/foo/{bar}/')).pattern #=> '/foo/{:bar}/' def self.try_convert(x) if x.kind_of? String return new(x) elsif x.kind_of? self return x elsif x.kind_of? URITemplate::RFC6570 and x.level == 1 return new( x.pattern.gsub(/\{(.*?)\}/u){ "{:#{$1}}" } ) else return nil end end def initialize(pattern) raise ArgumentError,"Expected a String but got #{pattern.inspect}" unless pattern.kind_of? String @pattern = pattern end # Extracts variables from an uri. # # @param uri [String] # @return nil,Hash def extract(uri) md = self.to_r.match(uri) return nil unless md result = {} splat = [] self.tokens.select{|tk| tk.kind_of? URITemplate::Expression }.each_with_index do |tk,i| if tk.kind_of? Token::Splat splat << md[i+1] result['splat'] = splat unless result.key? 'splat' else result[tk.name] = Utils.unescape_url( md[i+1] ) end end if block_given? return yield(result) end return result end def type :colon end def to_r @regexp ||= Regexp.new('\A' + tokens.map(&:to_r).join + '\z', Utils::KCODE_UTF8) end def tokens @tokens ||= tokenize! end protected def tokenize! number_of_splats = 0 RegexpEnumerator.new(VAR).each(@pattern).map{|x| if x.kind_of? String Token::Static.new(Utils.escape_uri(x)) elsif x[0] == '*' n = number_of_splats number_of_splats = number_of_splats + 1 Token::Splat.new(n) else # TODO: when rubinius supports ambigious names this could be replaced with x['name'] *sigh* Token::Variable.new(x[1] || x[2]) end }.to_a end end end uri-template-0.7.0/lib/uri_template/rfc6570/0000755000175000017500000000000013724671637017526 5ustar pravipraviuri-template-0.7.0/lib/uri_template/rfc6570/regex_builder.rb0000644000175000017500000000577313724671637022707 0ustar pravipravi# -*- encoding : utf-8 -*- # The MIT License (MIT) # # Copyright (c) 2011-2014 Hannes Georg # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'uri_template/rfc6570' class URITemplate::RFC6570 class RegexBuilder def initialize(expression_class) @expression_class = expression_class @source = [] end def <<(arg) @source << arg self end def push(*args) @source.push(*args) self end def escaped_pair_connector self << Regexp.escape(@expression_class::PAIR_CONNECTOR) end def escaped_separator self << Regexp.escape(@expression_class::SEPARATOR) end def escaped_prefix self << Regexp.escape(@expression_class::PREFIX) end def join return @source.join end def length(*args) self << format_length(*args) end def character_class_with_comma(max_length=0, min = 0) self << @expression_class::CHARACTER_CLASS[:class_with_comma] << format_length(max_length, min) end def character_class(max_length=0, min = 0) self << @expression_class::CHARACTER_CLASS[:class] << format_length(max_length, min) end def reluctant self << '?' end def group(capture = false) self << '(' self << '?:' unless capture yield self << ')' end def negative_lookahead self << '(?!' yield self << ')' end def lookahead self << '(?=' yield self << ')' end def capture(&block) group(true, &block) end def separated_list(first = true, length = 0, min = 1, &block) if first yield min -= 1 end self.push('(?:').escaped_separator yield self.push(')').length(length, min) end private def format_length(len, min = 0) return len if len.kind_of? String return '{'+min.to_s+','+len.to_s+'}' if len.kind_of?(Numeric) and len > 0 return '*' if min == 0 return '+' if min == 1 return '{'+min.to_s+',}' end end end uri-template-0.7.0/lib/uri_template/rfc6570/expression.rb0000644000175000017500000002424713724671637022263 0ustar pravipravi# -*- encoding : utf-8 -*- # The MIT License (MIT) # # Copyright (c) 2011-2014 Hannes Georg # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'uri_template/rfc6570' class URITemplate::RFC6570 # @private class Expression < Token include URITemplate::Expression attr_reader :variables def initialize(vars) @variable_specs = vars @variables = vars.map(&:first) @variables.uniq! end PREFIX = ''.freeze SEPARATOR = ','.freeze PAIR_CONNECTOR = '='.freeze PAIR_IF_EMPTY = true LIST_CONNECTOR = ','.freeze BASE_LEVEL = 1 CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved] OPERATOR = '' def level if @variable_specs.none?{|_,expand,ml| expand || (ml > 0) } if @variable_specs.size == 1 return self.class::BASE_LEVEL else return 3 end else return 4 end end def arity @variable_specs.size end def expand( vars ) result = [] @variable_specs.each do | var, expand , max_length | if Utils.def? vars[var] result.push(*expand_one(var, vars[var], expand, max_length)) end end if result.any? return (self.class::PREFIX + result.join(self.class::SEPARATOR)) else return '' end end def expand_partial( vars ) result = [] follow_up = self.class::FOLLOW_UP var_specs = [] @variable_specs.each do | var, expand , max_length | if vars.key? var unless var_specs.none? result.push( follow_up.new( var_specs ) ) var_specs = [] end unless result.none? result.push( Literal.new(self.class::SEPARATOR) ) end one = Array(expand_one(var, vars[var], expand, max_length)) result.push( Literal.new(one.join(self.class::SEPARATOR))) end var_specs << [var,expand,max_length] end if result.none? # no literal was emitted so far return [ self ] end unless self.class::PREFIX.empty? || empty_literals?( result ) result.unshift( Literal.new(self.class::PREFIX) ) end if var_specs.size != 0 result.push( follow_up.new( var_specs ) ) end return result end def extract(position,matched) name, expand, max_length = @variable_specs[position] if matched.nil? return [[ name , extracted_nil ]] end if expand it = URITemplate::RegexpEnumerator.new(self.class.hash_extractor(max_length), :rest => :raise) if position == 0 matched = "#{self.class::SEPARATOR}#{matched}" end splitted = it.each(matched)\ .map do |match| raise match.inspect if match.kind_of? String [ decode(match[1]), decode(match[2], false) ] end return after_expand(name, splitted) end return [ [ name, decode( matched ) ] ] end def to_s return '{' + self.class::OPERATOR + @variable_specs.map{|name,expand,max_length| name + (expand ? '*': '') + (max_length > 0 ? (':' + max_length.to_s) : '') }.join(',') + '}' end private def expand_one( name, value, expand, max_length) if value.kind_of?(Hash) or Utils.pair_array?(value) return transform_hash(name, value, expand, max_length) elsif value.kind_of? Array return transform_array(name, value, expand, max_length) else return self_pair(name, value, max_length) end end def length_limited?(max_length) max_length > 0 end def extracted_nil nil end protected module ClassMethods def hash_extractors @hash_extractors ||= Hash.new{|hsh, key| hsh[key] = generate_hash_extractor(key) } end def hash_extractor(max_length) return hash_extractors[max_length] end def generate_hash_extractor(max_length) source = regex_builder source.push('\\A') source.escaped_separator source.capture do source.character_class('+').reluctant end source.group do source.escaped_pair_connector source.capture do source.character_class(max_length,0).reluctant end end.length('?') source.lookahead do source.push '\\z' source.push '|' source.escaped_separator source.push '[^' source.escaped_separator source.push ']' end return Regexp.new( source.join , Utils::KCODE_UTF8) end def regex_builder RegexBuilder.new(self) end end extend ClassMethods def escape(x) Utils.escape_url(Utils.object_to_param(x)) end def unescape(x) Utils.unescape_url(x) end def regex_builder self.class.regex_builder end SPLITTER = /^(,+)|([^,]+)/ COMMA = ",".freeze def decode(x, split = true) if x.nil? return extracted_nil elsif split result = [] # Add a comma if the last character is a comma # seems weird but is more compatible than changing # the regex. x += COMMA if x[-1..-1] == COMMA URITemplate::RegexpEnumerator.new(SPLITTER, :rest => :raise).each(x) do |match| if match[1] next if match[1].size == 1 result << match[1][0..-3] elsif match[2] result << unescape(match[2]) end end case(result.size) when 0 then '' when 1 then result.first else result end else unescape(x) end end def cut(str,chars) if chars > 0 md = Regexp.compile("\\A#{self.class::CHARACTER_CLASS[:class]}{0,#{chars.to_s}}", Utils::KCODE_UTF8).match(str) return md[0] else return str end end def pair(key, value, max_length = 0, &block) ek = key if block ev = value.map(&block).join(self.class::LIST_CONNECTOR) else ev = escape(value) end if !self.class::PAIR_IF_EMPTY and ev.size == 0 return ek else return ek + self.class::PAIR_CONNECTOR + cut( ev, max_length ) end end def transform_hash(name, hsh, expand , max_length) if expand hsh.map{|key,value| pair(escape(key),value) } else [ self_pair(name,hsh, max_length ){|key,value| escape(key)+self.class::LIST_CONNECTOR+escape(value)} ] end end def transform_array(name, ary, expand , max_length) if expand ary.map{|value| self_pair(name,value) } else [ self_pair(name, ary, max_length){|value| escape(value) } ] end end def empty_literals?( list ) list.none?{|x| x.kind_of?(Literal) && !x.to_s.empty? } end end require 'uri_template/rfc6570/expression/named' require 'uri_template/rfc6570/expression/unnamed' class Expression::Basic < Expression::Unnamed FOLLOW_UP = self BULK_FOLLOW_UP = self end class Expression::Reserved < Expression::Unnamed CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct] OPERATOR = '+'.freeze BASE_LEVEL = 2 FOLLOW_UP = self BULK_FOLLOW_UP = self def escape(x) Utils.escape_uri(Utils.object_to_param(x)) end def unescape(x) Utils.unescape_uri(x) end def scheme? true end def host? true end end class Expression::Fragment < Expression::Unnamed CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct] PREFIX = '#'.freeze OPERATOR = '#'.freeze BASE_LEVEL = 2 FOLLOW_UP = Expression::Reserved BULK_FOLLOW_UP = Expression::Reserved def escape(x) Utils.escape_uri(Utils.object_to_param(x)) end def unescape(x) Utils.unescape_uri(x) end end class Expression::Label < Expression::Unnamed SEPARATOR = '.'.freeze PREFIX = '.'.freeze OPERATOR = '.'.freeze BASE_LEVEL = 3 FOLLOW_UP = self BULK_FOLLOW_UP = self end class Expression::Path < Expression::Unnamed SEPARATOR = '/'.freeze PREFIX = '/'.freeze OPERATOR = '/'.freeze BASE_LEVEL = 3 FOLLOW_UP = self BULK_FOLLOW_UP = self def starts_with_slash? true end end class Expression::PathParameters < Expression::Named SEPARATOR = ';'.freeze PREFIX = ';'.freeze PAIR_IF_EMPTY = false OPERATOR = ';'.freeze BASE_LEVEL = 3 FOLLOW_UP = self BULK_FOLLOW_UP = self end class Expression::FormQueryContinuation < Expression::Named SEPARATOR = '&'.freeze PREFIX = '&'.freeze OPERATOR = '&'.freeze BASE_LEVEL = 3 FOLLOW_UP = Expression::Basic BULK_FOLLOW_UP = self end class Expression::FormQuery < Expression::Named SEPARATOR = '&'.freeze PREFIX = '?'.freeze OPERATOR = '?'.freeze BASE_LEVEL = 3 FOLLOW_UP = Expression::Basic BULK_FOLLOW_UP = Expression::FormQueryContinuation end # @private OPERATORS = { '' => Expression::Basic, '+' => Expression::Reserved, '#' => Expression::Fragment, '.' => Expression::Label, '/' => Expression::Path, ';' => Expression::PathParameters, '?' => Expression::FormQuery, '&' => Expression::FormQueryContinuation } end uri-template-0.7.0/lib/uri_template/rfc6570/expression/0000755000175000017500000000000013724671637021725 5ustar pravipraviuri-template-0.7.0/lib/uri_template/rfc6570/expression/named.rb0000644000175000017500000000672413724671637023347 0ustar pravipravi# -*- encoding : utf-8 -*- # The MIT License (MIT) # # Copyright (c) 2011-2014 Hannes Georg # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'uri_template/rfc6570' class URITemplate::RFC6570 class Expression::Named < Expression alias self_pair pair def to_r_source source = regex_builder source.group do source.escaped_prefix first = true @variable_specs.each do | var, expand , max_length | if expand source.capture do source.separated_list(first) do source.character_class('+')\ .escaped_pair_connector\ .character_class_with_comma(max_length) end end else source.group do source.escaped_separator unless first source << Regexp.escape(var) source.group do source.escaped_pair_connector source.capture do source.character_class_with_comma(max_length) end source << '|' unless self.class::PAIR_IF_EMPTY end end.length('?') end first = false end end.length('?') return source.join end def expand_partial( vars ) result = [] rest = [] defined = false @variable_specs.each do | var, expand , max_length | if vars.key? var if Utils.def? vars[var] if result.any? && !self.class::SEPARATOR.empty? result.push( Literal.new(self.class::SEPARATOR) ) end one = expand_one(var, vars[var], expand, max_length) result.push( Literal.new(Array(one).join(self.class::SEPARATOR)) ) end if expand rest << [var, expand, max_length] else result.push( self.class::FOLLOW_UP.new([[var,expand,max_length]]) ) end else rest.push( [var,expand,max_length] ) end end if result.any? unless self.class::PREFIX.empty? || empty_literals?( result ) result.unshift( Literal.new(self.class::PREFIX) ) end result.push( self.class::BULK_FOLLOW_UP.new(rest) ) if rest.size != 0 return result else return [ self ] end end private def extracted_nil self.class::PAIR_IF_EMPTY ? nil : "" end def after_expand(name, splitted) result = URITemplate::Utils.pair_array_to_hash2( splitted ) if result.size == 1 && result[0][0] == name return result else return [ [ name , result ] ] end end end end uri-template-0.7.0/lib/uri_template/rfc6570/expression/unnamed.rb0000644000175000017500000000535213724671637023706 0ustar pravipravi# -*- encoding : utf-8 -*- # The MIT License (MIT) # # Copyright (c) 2011-2014 Hannes Georg # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'uri_template/rfc6570' class URITemplate::RFC6570::Expression::Unnamed < URITemplate::RFC6570::Expression def self_pair(_, value, max_length = 0,&block) if block ev = value.map(&block).join(self.class::LIST_CONNECTOR) else ev = escape(value) end cut( ev, max_length ,&block) end def to_r_source vs = @variable_specs.size - 1 i = 0 source = regex_builder source.group do source.escaped_prefix @variable_specs.each do | var, expand , max_length | last = (vs == i) first = (i == 0) if expand source.group(true) do source.separated_list(first) do source.group do source.character_class('+').reluctant source.escaped_pair_connector end.length('?') source.character_class(max_length) end end else source.escaped_separator unless first source.group(true) do if last source.character_class_with_comma(max_length) else source.character_class(max_length) end end end i = i+1 end end.length('?') return source.join end private def transform_hash(name, hsh, expand , max_length) return [] if hsh.none? super end def transform_array(name, ary, expand , max_length) return [] if ary.none? super end def after_expand(name, splitted) if splitted.none?{|_,b| b } return [ [ name, splitted.map{|a,_| a } ] ] else return [ [ name, splitted ] ] end end end uri-template-0.7.0/lib/uri_template/literal.rb0000644000175000017500000000311413724671637020412 0ustar pravipravi# -*- encoding : utf-8 -*- # The MIT License (MIT) # # Copyright (c) 2011-2014 Hannes Georg # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # A module which all literal tokens should include. module URITemplate::Literal include URITemplate::Token SLASH = ?/ attr_reader :string def literal? true end def expression? false end def size 0 end def expand(_) return string end def expand_partial(_) return [self] end def starts_with_slash? string[0] == SLASH end def ends_with_slash? string[-1] == SLASH end alias to_s string end uri-template-0.7.0/lib/uri_template/expression.rb0000644000175000017500000000252413724671637021161 0ustar pravipravi# -*- encoding : utf-8 -*- # The MIT License (MIT) # # Copyright (c) 2011-2014 Hannes Georg # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # A module which all non-literal tokens should include. module URITemplate::Expression include URITemplate::Token attr_reader :variables def literal? false end def expression? true end end uri-template-0.7.0/lib/uri_template/rfc6570.rb0000644000175000017500000004011313724671637020052 0ustar pravipravi# -*- encoding : utf-8 -*- # The MIT License (MIT) # # Copyright (c) 2011-2014 Hannes Georg # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'strscan' require 'set' require 'forwardable' require 'uri_template' require 'uri_template/utils' # A uri template which should comply with the rfc 6570 ( http://tools.ietf.org/html/rfc6570 ). # @note # Most specs and examples refer to this class directly, because they are acutally refering to this specific implementation. If you just want uri templates, you should rather use the methods on {URITemplate} to create templates since they will select an implementation. class URITemplate::RFC6570 TYPE = :rfc6570 include URITemplate extend Forwardable # @private module Utils include URITemplate::Utils # Returns true iff the value is `defined` [RFC6570 Section 2.3](http://tools.ietf.org/html/rfc6570#section-2.3) # # The only undefined things are: # - nil # - arrays containing no defined value # - associative arrays/hashes containing no defined value # # Things that are always defined: # - Strings, independent of the length def def?( value ) case( value ) when nil then false when Hash then value.any?{|_, v| !v.nil? } when Array then if value.none? false elsif value[0].kind_of?(Array) value.any?{|_,v| !v.nil? } else value.any?{|v| !v.nil? } end else true end end extend self end # :nocov: if Utils.use_unicode? # @private # \/ - unicode ctrl-chars LITERAL = /([^"'%<>\\^`{|}\u0000-\u001F\u007F-\u009F\s]|%[0-9a-fA-F]{2})+/u else # @private LITERAL = Regexp.compile('([^"\'%<>\\\\^`{|}\x00-\x1F\x7F-\x9F\s]|%[0-9a-fA-F]{2})+',Utils::KCODE_UTF8) end # :nocov: # @private CHARACTER_CLASSES = { :unreserved => { :class => '(?:[A-Za-z0-9\-\._]|%[0-9a-fA-F]{2})', :class_with_comma => '(?:[A-Za-z0-9\-\._,]|%[0-9a-fA-F]{2})', :class_without_comma => '(?:[A-Za-z0-9\-\._]|%[0-9a-fA-F]{2})', :grabs_comma => false }, :unreserved_reserved_pct => { :class => '(?:[A-Za-z0-9\-\._:\/?#\[\]@!\$%\'\(\)*+,;=]|%[0-9a-fA-F]{2})', :class_with_comma => '(?:[A-Za-z0-9\-\._:\/?#\[\]@!\$%\'\(\)*+,;=]|%[0-9a-fA-F]{2})', :class_without_comma => '(?:[A-Za-z0-9\-\._:\/?#\[\]@!\$%\'\(\)*+;=]|%[0-9a-fA-F]{2})', :grabs_comma => true }, :varname => { :class => '(?:[A-Za-z0-9\-\._]|%[0-9a-fA-F]{2})+', :class_name => 'c_vn_' } } # Specifies that no processing should be done upon extraction. # @see #extract NO_PROCESSING = [] # Specifies that the extracted values should be processed. # @see #extract CONVERT_VALUES = [:convert_values] # Specifies that the extracted variable list should be processed. # @see #extract CONVERT_RESULT = [:convert_result] # Default processing. Means: convert values and the list itself. # @see #extract DEFAULT_PROCESSING = CONVERT_VALUES + CONVERT_RESULT # @private VAR = Regexp.compile(Utils.compact_regexp(<<'__REGEXP__'), Utils::KCODE_UTF8) ( (?:[a-zA-Z0-9_]|%[0-9a-fA-F]{2}) (?:\.? (?:[a-zA-Z0-9_]|%[0-9a-fA-F]{2}) )* ) (?:(\*)|:([1-9]\d{0,3})|) __REGEXP__ # @private EXPRESSION = Regexp.compile(Utils.compact_regexp(<<'__REGEXP__'), Utils::KCODE_UTF8) \{ ([+#\./;?&]?) ( (?:[a-zA-Z0-9_]|%[0-9a-fA-F]{2}) (?:\.?(?:[a-zA-Z0-9_]|%[0-9a-fA-F]{2}))* (?:\*|:[1-9]\d{0,3}|) (?: , (?:[a-zA-Z0-9_]|%[0-9a-fA-F]{2}) (?:\.?(?:[a-zA-Z0-9_]|%[0-9a-fA-F]{2}))* (?:\*|:[1-9]\d{0,3}|) )* ) \} __REGEXP__ # @private URI = Regexp.compile(<<__REGEXP__.strip, Utils::KCODE_UTF8) \\A(#{LITERAL.source}|#{EXPRESSION.source})*\\z __REGEXP__ # @private class Token end # @private class Literal < Token include URITemplate::Literal def initialize(string) @string = string end def level 1 end def to_r_source(*_) Regexp.escape(@string) end def to_s @string end end # This error is raised when an invalid pattern was given. class Invalid < StandardError include URITemplate::Invalid attr_reader :pattern, :position def initialize(source, position) @pattern = source @position = position super("Invalid expression found in #{source.inspect} at #{position}: '#{source[position..-1]}'") end end # @private class Tokenizer include Enumerable attr_reader :source def initialize(source, ops) @source = source @operators = ops end def each scanner = StringScanner.new(@source) until scanner.eos? expression = scanner.scan(EXPRESSION) if expression vars = scanner[2].split(',').map{|name| match = VAR.match(name) # 1 = varname # 2 = explode # 3 = length [ match[1], match[2] == '*', match[3].to_i ] } yield @operators[scanner[1]].new(vars) else literal = scanner.scan(LITERAL) if literal yield(Literal.new(literal)) else raise Invalid.new(@source,scanner.pos) end end end end end # The class methods for all rfc6570 templates. module ClassMethods # Tries to convert the given param in to a instance of {RFC6570} # It basically passes thru instances of that class, parses strings and return nil on everything else. # # @example # URITemplate::RFC6570.try_convert( Object.new ) #=> nil # tpl = URITemplate::RFC6570.new('{foo}') # URITemplate::RFC6570.try_convert( tpl ) #=> tpl # URITemplate::RFC6570.try_convert('{foo}') #=> tpl # URITemplate::RFC6570.try_convert(URITemplate.new(:colon, ':foo')) #=> tpl # # This pattern is invalid, so it wont be parsed: # URITemplate::RFC6570.try_convert('{foo') #=> nil # def try_convert(x) if x.class == self return x elsif x.kind_of? String and valid? x return new(x) elsif x.kind_of? URITemplate::Colon return nil if x.tokens.any?{|tk| tk.kind_of? URITemplate::Colon::Token::Splat } return new( x.tokens.map{|tk| if tk.literal? Literal.new(tk.string) else Expression.new([[tk.variables.first, false, 0]]) end }) else return nil end end # Tests whether a given pattern is a valid template pattern. # @example # URITemplate::RFC6570.valid? 'foo' #=> true # URITemplate::RFC6570.valid? '{foo}' #=> true # URITemplate::RFC6570.valid? '{foo' #=> false def valid?(pattern) URI === pattern end end extend ClassMethods attr_reader :options # @param pattern_or_tokens [String,Array] either a pattern as String or an Array of tokens # @param options [Hash] some options # @option :lazy [true,false] If true the pattern will be parsed on first access, this also means that syntax errors will not be detected unless accessed. def initialize(pattern_or_tokens,options={}) @options = options.dup.freeze if pattern_or_tokens.kind_of? String @pattern = pattern_or_tokens.dup @pattern.freeze unless @options[:lazy] self.tokens end elsif pattern_or_tokens.kind_of? Array @tokens = pattern_or_tokens.dup @tokens.freeze else raise ArgumentError, "Expected to receive a pattern string, but got #{pattern_or_tokens.inspect}" end end # @method expand(variables = {}) # Expands the template with the given variables. # The expansion should be compatible to uritemplate spec rfc 6570 ( http://tools.ietf.org/html/rfc6570 ). # @note # All keys of the supplied hash should be strings as anything else won't be recognised. # @note # There are neither default values for variables nor will anything be raised if a variable is missing. Please read the spec if you want to know how undefined variables are handled. # @example # URITemplate::RFC6570.new('{foo}').expand('foo'=>'bar') #=> 'bar' # URITemplate::RFC6570.new('{?args*}').expand('args'=>{'key'=>'value'}) #=> '?key=value' # URITemplate::RFC6570.new('{undef}').expand() #=> '' # # @param variables [Hash, Array] # @return String # @method expand_partial(variables = {}) # Works like expand but keeps missing variables in place. # @example # URITemplate::RFC6570.new('{foo}').expand_partial('foo'=>'bar') #=> URITemplate::RFC6570.new('bar{foo}') # URITemplate::RFC6570.new('{undef}').expand_partial() #=> URITemplate::RFC6570.new('{undef}') # # @param variables [Hash,Array] # @return URITemplate # Compiles this template into a regular expression which can be used to test whether a given uri matches this template. This template is also used for {#===}. # # @example # tpl = URITemplate::RFC6570.new('/foo/{bar}/') # regex = tpl.to_r # regex === '/foo/baz/' #=> true # regex === '/foz/baz/' #=> false # # @return Regexp def to_r @regexp ||= begin source = tokens.map(&:to_r_source) source.unshift('\A') source.push('\z') Regexp.new( source.join, Utils::KCODE_UTF8) end end # Extracts variables from a uri ( given as string ) or an instance of MatchData ( which was matched by the regexp of this template. # The actual result depends on the value of post_processing. # This argument specifies whether pair arrays should be converted to hashes. # # @example Default Processing # URITemplate::RFC6570.new('{var}').extract('value') #=> {'var'=>'value'} # URITemplate::RFC6570.new('{&args*}').extract('&a=1&b=2') #=> {'args'=>{'a'=>'1','b'=>'2'}} # URITemplate::RFC6570.new('{&arg,arg}').extract('&arg=1&arg=2') #=> {'arg'=>'2'} # # @example No Processing # URITemplate::RFC6570.new('{var}').extract('value', URITemplate::RFC6570::NO_PROCESSING) #=> [['var','value']] # URITemplate::RFC6570.new('{&args*}').extract('&a=1&b=2', URITemplate::RFC6570::NO_PROCESSING) #=> [['args',[['a','1'],['b','2']]]] # URITemplate::RFC6570.new('{&arg,arg}').extract('&arg=1&arg=2', URITemplate::RFC6570::NO_PROCESSING) #=> [['arg','1'],['arg','2']] # # @raise Encoding::InvalidByteSequenceError when the given uri was not properly encoded. # @raise Encoding::UndefinedConversionError when the given uri could not be converted to utf-8. # @raise Encoding::CompatibilityError when the given uri could not be converted to utf-8. # # @param uri_or_match [String,MatchData] Uri_or_MatchData A uri or a matchdata from which the variables should be extracted. # @param post_processing [Array] Processing Specifies which processing should be done. # # @note # Don't expect that an extraction can fully recover the expanded variables. Extract rather generates a variable list which should expand to the uri from which it were extracted. In general the following equation should hold true: # a_tpl.expand( a_tpl.extract( an_uri ) ) == an_uri # # @example Extraction cruces # two_lists = URITemplate::RFC6570.new('{listA*,listB*}') # uri = two_lists.expand('listA'=>[1,2],'listB'=>[3,4]) #=> "1,2,3,4" # variables = two_lists.extract( uri ) #=> {'listA'=>["1","2","3"],'listB'=>["4"]} # # However, like said in the note: # two_lists.expand( variables ) == uri #=> true # # @note # The current implementation drops duplicated variables instead of checking them. # def extract(uri_or_match, post_processing = DEFAULT_PROCESSING ) if uri_or_match.kind_of? String m = self.to_r.match(uri_or_match) elsif uri_or_match.kind_of?(MatchData) if uri_or_match.respond_to?(:regexp) and uri_or_match.regexp != self.to_r raise ArgumentError, "Trying to extract variables from MatchData which was not generated by this template." end m = uri_or_match elsif uri_or_match.nil? return nil else raise ArgumentError, "Expected to receive a String or a MatchData, but got #{uri_or_match.inspect}." end if m.nil? return nil else result = extract_matchdata(m, post_processing) if block_given? return yield result end return result end end # Extracts variables without any proccessing. # This is equivalent to {#extract} with options {NO_PROCESSING}. # @see #extract def extract_simple(uri_or_match) extract( uri_or_match, NO_PROCESSING ) end # @method ===(uri) # Alias for to_r.=== . Tests whether this template matches a given uri. # @return TrueClass, FalseClass def_delegators :to_r, :=== # @method match(uri) # Alias for to_r.match . Matches this template against the given uri. # @yield MatchData # @return MatchData, Object def_delegators :to_r, :match # The type of this template. # # @example # tpl1 = URITemplate::RFC6570.new('/foo') # tpl2 = URITemplate.new( tpl1.pattern, tpl1.type ) # tpl1 == tpl2 #=> true # # @see {URITemplate#type} def type self.class::TYPE end # Returns the level of this template according to the rfc 6570 ( http://tools.ietf.org/html/rfc6570#section-1.2 ). Higher level means higher complexity. # Basically this is defined as: # # * Level 1: no operators, one variable per expansion, no variable modifiers # * Level 2: '+' and '#' operators, one variable per expansion, no variable modifiers # * Level 3: all operators, multiple variables per expansion, no variable modifiers # * Level 4: all operators, multiple variables per expansion, all variable modifiers # # @example # URITemplate::RFC6570.new('/foo/').level #=> 1 # URITemplate::RFC6570.new('/foo{bar}').level #=> 1 # URITemplate::RFC6570.new('/foo{#bar}').level #=> 2 # URITemplate::RFC6570.new('/foo{.bar}').level #=> 3 # URITemplate::RFC6570.new('/foo{bar,baz}').level #=> 3 # URITemplate::RFC6570.new('/foo{bar:20}').level #=> 4 # URITemplate::RFC6570.new('/foo{bar*}').level #=> 4 # # Templates of lower levels might be convertible to other formats while templates of higher levels might be incompatible. Level 1 for example should be convertible to any other format since it just contains simple expansions. # def level tokens.map(&:level).max end # Returns an array containing a the template tokens. def tokens @tokens ||= tokenize! end protected # @private def tokenize! self.class::Tokenizer.new(pattern, self.class::OPERATORS).to_a end # @private def extract_matchdata(matchdata, post_processing) bc = 1 vars = [] tokens.each{|part| next if part.literal? i = 0 pa = part.arity while i < pa vars.push( *part.extract(i, matchdata[bc]) ) bc += 1 i += 1 end } if post_processing.include? :convert_result if post_processing.include? :convert_values return Hash[ vars.map!{|k,v| [k,Utils.pair_array_to_hash(v)] } ] else return Hash[vars] end else if post_processing.include? :convert_values return vars.collect{|k,v| [k,Utils.pair_array_to_hash(v)] } else return vars end end end end require 'uri_template/rfc6570/regex_builder.rb' require 'uri_template/rfc6570/expression.rb' uri-template-0.7.0/lib/uri_template/token.rb0000644000175000017500000000403613724671637020102 0ustar pravipravi# -*- encoding : utf-8 -*- # The MIT License (MIT) # # Copyright (c) 2011-2014 Hannes Georg # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # This should make it possible to do basic analysis independently from the concrete type. # Usually the submodules {URITemplate::Literal} and {URITemplate::Expression} are used. # # @abstract module URITemplate::Token EMPTY_ARRAY = [].freeze # The variable names used in this token. # # @return [Array] def variables EMPTY_ARRAY end # Number of variables in this token def size variables.size end def starts_with_slash? false end def ends_with_slash? false end def scheme? false end def host? false end # @abstract def expand(variables) raise "Please implement #expand(variables) on #{self.class.inspect}." end # @abstract def expand_partial(variables) raise "Please implement #expand_partial(variables) on #{self.class.inspect}." end # @abstract def to_s raise "Please implement #to_s on #{self.class.inspect}." end end uri-template-0.7.0/lib/uri_template/utils.rb0000644000175000017500000002240213724671637020117 0ustar pravipravi# -*- encoding : utf-8 -*- # The MIT License (MIT) # # Copyright (c) 2011-2014 Hannes Georg # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module URITemplate # An awesome little helper which helps iterating over a string. # Initialize with a regexp and pass a string to :each. # It will yield a string or a MatchData class RegexpEnumerator include Enumerable def initialize(regexp, options = {}) @regexp = regexp @rest = options.fetch(:rest){ :yield } end def each(str) raise ArgumentError, "RegexpEnumerator#each expects a String, but got #{str.inspect}" unless str.kind_of? String return self.to_enum(:each,str) unless block_given? rest = str loop do m = @regexp.match(rest) if m.nil? if rest.size > 0 yield rest end break end yield m.pre_match if m.pre_match.size > 0 yield m if m[0].size == 0 # obviously matches empty string, so post_match will equal rest # terminate or this will loop forever if m.post_match.size > 0 yield m.post_match if @rest == :yield raise "#{@regexp.inspect} matched an empty string. The rest is #{m.post_match.inspect}." if @rest == :raise end break end rest = m.post_match end return self end end # This error will be raised whenever an object could not be converted to a param string. class Unconvertable < StandardError attr_reader :object def initialize(object) @object = object super("Could not convert the given object (#{Object.instance_method(:inspect).bind(@object).call() rescue ''}) to a param since it doesn't respond to :to_param or :to_s.") end end # A collection of some utility methods. # The most methods are used to parse or generate uri-parameters. # I will use the escape_utils library if available, but runs happily without. # module Utils KCODE_UTF8 = (Regexp::KCODE_UTF8 rescue 0) # Bundles some string encoding methods. module StringEncoding # Methods which do actual encoding. module Encode # converts a string to ascii # # @param str [String] # @return String # @visibility public def to_ascii(str) str.encode(Encoding::ASCII) end # converts a string to utf8 # # @param str [String] # @return String # @visibility public def to_utf8(str) str.encode(Encoding::UTF_8) end # enforces UTF8 encoding # # @param str [String] # @return String # @visibility public def force_utf8(str) return str if str.encoding == Encoding::UTF_8 str = str.dup if str.frozen? return str.force_encoding(Encoding::UTF_8) end end # Fallback methods to be used in pre 1.9 rubies. module Fallback def to_ascii(str) str end def to_utf8(str) str end def force_utf8(str) str end end # :nocov: if "".respond_to?(:encode) include Encode else include Fallback end # :nocov: private :force_utf8 end module Escaping # A pure escaping module, which implements escaping methods in pure ruby. # The performance is acceptable, but could be better with escape_utils. module Pure # @private URL_ESCAPED = /([^A-Za-z0-9\-\._])/.freeze # @private URI_ESCAPED = /([^A-Za-z0-9!$&'()*+,.\/:;=?@\[\]_~])/.freeze # @private PCT = /%([0-9a-fA-F]{2})/.freeze def escape_url(s) to_utf8(s.to_s).gsub(URL_ESCAPED){ '%'+$1.unpack('H2'*$1.bytesize).join('%').upcase } end def escape_uri(s) to_utf8(s.to_s).gsub(URI_ESCAPED){ '%'+$1.unpack('H2'*$1.bytesize).join('%').upcase } end def unescape_url(s) force_utf8( to_ascii(s.to_s).gsub('+',' ').gsub(PCT){ $1.to_i(16).chr } ) end def unescape_uri(s) force_utf8( to_ascii(s.to_s).gsub(PCT){ $1.to_i(16).chr }) end def using_escape_utils? false end end if defined? EscapeUtils # A escaping module, which is backed by escape_utils. # The performance is good, espacially for strings with many escaped characters. module EscapeUtils include ::EscapeUtils def using_escape_utils? true end def escape_url(s) super(to_utf8(s.to_s)).gsub('+','%20') end def escape_uri(s) super(to_utf8(s.to_s)) end def unescape_url(s) force_utf8(super(to_ascii(s.to_s))) end def unescape_uri(s) force_utf8(super(to_ascii(s.to_s))) end end end end include StringEncoding # :nocov: if Escaping.const_defined? :EscapeUtils include Escaping::EscapeUtils puts "Using escape_utils." if $VERBOSE else include Escaping::Pure puts "Not using escape_utils." if $VERBOSE end # :nocov: # Converts an object to a param value. # Tries to call :to_param and then :to_s on that object. # @raise Unconvertable if the object could not be converted. # @example # URITemplate::Utils.object_to_param(5) #=> "5" # o = Object.new # def o.to_param # "42" # end # URITemplate::Utils.object_to_param(o) #=> "42" def object_to_param(object) if object.respond_to? :to_param object.to_param elsif object.respond_to? :to_s object.to_s else raise Unconvertable.new(object) end rescue NoMethodError raise Unconvertable.new(object) end # @api private # Should we use \u.... or \x.. in regexps? def use_unicode? eval('Regexp.compile("\u0020")') =~ " " rescue false end # Returns true when the given value is an array and it only consists of arrays with two items. # This useful when using a hash is not ideal, since it doesn't allow duplicate keys. # @example # URITemplate::Utils.pair_array?( Object.new ) #=> false # URITemplate::Utils.pair_array?( [] ) #=> true # URITemplate::Utils.pair_array?( [1,2,3] ) #=> false # URITemplate::Utils.pair_array?( [ ['a',1],['b',2],['c',3] ] ) #=> true # URITemplate::Utils.pair_array?( [ ['a',1],['b',2],['c',3],[] ] ) #=> false def pair_array?(a) return false unless a.kind_of? Array return a.all?{|p| p.kind_of? Array and p.size == 2 } end # Turns the given value into a hash if it is an array of pairs. # Otherwise it returns the value. # You can test whether a value will be converted with {#pair_array?}. # # @example # URITemplate::Utils.pair_array_to_hash( 'x' ) #=> 'x' # URITemplate::Utils.pair_array_to_hash( [ ['a',1],['b',2],['c',3] ] ) #=> {'a'=>1,'b'=>2,'c'=>3} # URITemplate::Utils.pair_array_to_hash( [ ['a',1],['a',2],['a',3] ] ) #=> {'a'=>3} # # @example Carful vs. Ignorant # URITemplate::Utils.pair_array_to_hash( [ ['a',1],'foo','bar'], false ) #UNDEFINED! # URITemplate::Utils.pair_array_to_hash( [ ['a',1],'foo','bar'], true ) #=> [ ['a',1], 'foo', 'bar'] # # @param x the value to convert # @param careful [true,false] wheter to check every array item. Use this when you expect array with subarrays which are not pairs. Setting this to false however improves runtime by ~30% even with comparetivly short arrays. def pair_array_to_hash(x, careful = false ) if careful ? pair_array?(x) : (x.kind_of?(Array) and ( x.empty? or x.first.kind_of?(Array) ) ) return Hash[ x ] else return x end end extend self # @api privat def pair_array_to_hash2(x) c = {} result = [] x.each do | (k,v) | e = c[k] if !e result << c[k] = [k,v] else e[1] = [e[1]] unless e[1].kind_of? Array e[1] << v end end return result end # @api private def compact_regexp(rx) rx.split("\n").map(&:strip).join end end end uri-template-0.7.0/README.md0000644000175000017500000000546013724671637014456 0ustar pravipravi# URITemplate - a uri template library [![Build Status](https://secure.travis-ci.org/hannesg/uri_template.png)](http://travis-ci.org/hannesg/uri_template) [![Dependency Status](https://gemnasium.com/hannesg/uri_template.png)](https://gemnasium.com/hannesg/uri_template) [![Code Climate](https://codeclimate.com/github/hannesg/uri_template.png)](https://codeclimate.com/github/hannesg/uri_template) [![Coverage](https://coveralls.io/repos/hannesg/uri_template/badge.png?branch=master)](https://coveralls.io/r/hannesg/uri_template) With URITemplate you can generate URIs based on simple templates and extract variables from URIs using the same templates. There are currently two syntaxes defined. Namely the one defined in [RFC 6570]( http://tools.ietf.org/html/rfc6570 ) and a colon based syntax, similiar to the one used by sinatra. From version 0.2.0, it will use escape_utils if available. This will significantly boost uri-escape/unescape performance if more characters need to be escaped ( may be slightly slower in trivial cases. working on that ... ), but does not run everywhere. To enable this, do the following: ```ruby # escape_utils has to be loaded when uri_templates is loaded gem 'escape_utils' require 'escape_utils' gem 'uri_template' require 'uri_template' URITemplate::Utils.using_escape_utils? #=> true ``` ## Examples ```ruby require 'uri_template' tpl = URITemplate.new('http://{host}{/segments*}/{file}{.extensions*}') # This will give: http://www.host.com/path/to/a/file.x.y tpl.expand('host'=>'www.host.com','segments'=>['path','to','a'],'file'=>'file','extensions'=>['x','y']) # This will give: { 'host'=>'www.host.com','segments'=>['path','to','a'],'file'=>'file','extensions'=>['x','y']} tpl.extract('http://www.host.com/path/to/a/file.x.y') # If you like colon templates more: tpl2 = URITemplate.new(:colon, '/:x/y') # This will give: {'x' => 'z'} tpl2.extract('/z/y') # This will give a new uri template with just the host expanded: tpl.expand_partial(host: "www.host.com") ``` ## RFC 6570 Syntax The syntax defined by [RFC 6570]( http://tools.ietf.org/html/rfc6570 ) is pretty straight forward. Basically anything surrounded by curly brackets is interpreted as variable. ```ruby URITemplate.new('{variable}').expand('variable' => 'value') #=> "value" ``` The way variables are inserted can be modified using operators. The operator is the first character between the curly brackets. There are seven operators defined `#`, `+`, `;`, `?`, `&`, `/` and `.`. So if you want to create a form-style query do this: ```ruby URITemplate.new('{?variable}').expand('variable' => 'value') #=> "?variable=value" ``` ## Benchmarks I have assembled one benchmark based on the uritemplate-test examples. You can find them in the "benchmarks" folder. The short result: uri_template is 2-10x faster than addressable on ruby 1.9.3. uri-template-0.7.0/uri_template.gemspec0000644000175000017500000000141713724671637017234 0ustar pravipraviGem::Specification.new do |s| s.name = 'uri_template' s.version = '0.7.0' s.date = Time.now.strftime('%Y-%m-%d') s.authors = ["HannesG"] s.email = %q{hannes.georg@googlemail.com} s.summary = 'A templating system for URIs.' s.homepage = 'http://github.com/hannesg/uri_template' s.description = 'A templating system for URIs, which implements RFC6570 and Colon based URITemplates in a clean and straight forward way.' s.require_paths = ['lib'] s.license = 'MIT' s.files = Dir.glob('lib/**/**/*.rb') + ['uri_template.gemspec', 'README.md', 'CHANGELOG.md'] s.add_development_dependency 'multi_json' s.add_development_dependency 'rspec' s.add_development_dependency 'yard' s.add_development_dependency 'rake' s.add_development_dependency 'bundler' end uri-template-0.7.0/CHANGELOG.md0000644000175000017500000000526413724671637015012 0ustar pravipravi# 0.7.0 - 21.03.2014 * [CHANGE] Licence is now MIT * [ENHANCEMENT] partial expansion has now more features # 0.6.0 - 09.08.2013 * [FEATURE] partial expansion * [FEATURE] You can now pass variables as an Array to URITemplate#expand ( thanks to @bmaland ) Example: tpl = URITemplate.new("/{var}/") tpl.expand(["value"]) # => '/value/' * [BUGFIX] Expanding arrays/hashes with a length limit now actually works # 0.5.2 - 24.02.2013 * [ENHANCEMENT] The colon based uri templates now allow more characters in variable names. # 0.5.1 - 23.09.2012 * [BUGFIX] f*** bug. # 0.5.0 - 23.09.2012 * [ENHANCEMENT] Removed draft7 * [ENHANCEMENT] splitted absoulte? method into host? and scheme? * [ENHANCEMENT] the URITemplate interface is now much stronger * [ENHANCEMENT] code quality _significantly_ improved * [ENHANCEMENT] concat method # 0.4.0 - 06.07.2012 * [ENHANCEMENT] expand now accepts symbols as keys ( thanks to @peterhellber ) * [ENHANCEMENT] expand now accepts arrays of pairs ( thanks to @peterhellber ) * [BUGFIX] fixed some testing bugs # 0.3.0 - 24.05.2012 * [ENHANCEMENT] Implemented the final version. Default implementation is now RFC 6570 * [BUGFIX] variables with terminal dots were allowed * [BUGFIX] lists of commas were parsed incorrectly # 0.2.1 - 30.12.2011 * [ENHANCEMENT] Works now with MRI 1.8.7 and REE # 0.2.0 - 03.12.2011 * [ENHANCEMENT] Reworked the escaping mechanism * [ENHANCEMENT] escape_utils can now be used to boost escape/unescape performance # 0.1.4 - 19.11.2011 * [ENHANCEMENT] Compatiblity: Works now with MRI 1.9.3, Rubinius and JRuby * [ENHANCEMENT] Various (significant!) performance improvements # 0.1.3 - 15.11.2011 * [BUGFIX] Draft7./ now concatenates literals correctly * [BUGFIX] Draft7.tokens is now public # 0.1.2 - 10.11.2011 * [FEATURE] added a new template-type: Colon this should allow (some day) to rails-like routing tables * [ENHANCEMENT] made the tokens-method mandatory and added two interfaces for tokens. this allows cross-type features like variable anaylisis # 0.1.1 - 4.11.2011 * [ENHANCEMENT] added a bunch of useful helper methods # 0.1.0 - 2.11.2011 * [ENHANCEMENT] Removed Sections. They made too many headaches. * [ENHANCEMENT] Made draft7 template concatenateable. This should replace sections. * [BUGFIX] multiline uris were matched * [BUGFIX] variablenames were decoded when this was not appreciated # 0.0.2 - 1.11.2011 * [BUGFIX] Concatenating empty sections no more leads to catch-all templates, when an emtpy template was appreciated. * [ENHANCEMENT] The extracted variables now contains the keys :suffix and :prefix if the match didn't consume the whole uri. # 0.0.1 - 30.10.2011 * [FEATURE] Initial version