class URITemplate::RFC6570
A uri template which should comply with the rfc 6570 ( 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.
Constants
- CHARACTER_CLASSES
@private
- CONVERT_RESULT
Specifies that the extracted variable list should be processed. @see
extract
- CONVERT_VALUES
Specifies that the extracted values should be processed. @see
extract
- DEFAULT_PROCESSING
Default processing. Means: convert values and the list itself. @see
extract
- EXPRESSION
@private
- LITERAL
@private
\/ - unicode ctrl-chars
- NO_PROCESSING
Specifies that no processing should be done upon extraction. @see
extract
- OPERATORS
@private
- TYPE
- URI
@private
- VAR
@private
Attributes
Public Class Methods
@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.
# File lib/uri_template/rfc6570.rb, line 293 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
Public Instance Methods
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.
# File lib/uri_template/rfc6570.rb, line 386 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
# File lib/uri_template/rfc6570.rb, line 413 def extract_simple(uri_or_match) extract( uri_or_match, NO_PROCESSING ) end
Returns the level of this template according to the rfc 6570 ( 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.
# File lib/uri_template/rfc6570.rb, line 459 def level tokens.map(&:level).max end
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
# File lib/uri_template/rfc6570.rb, line 342 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
Returns an array containing a the template tokens.
# File lib/uri_template/rfc6570.rb, line 464 def tokens @tokens ||= tokenize! end
The type of this template.
@example
tpl1 = URITemplate::RFC6570.new('/foo') tpl2 = URITemplate.new( tpl1.pattern, tpl1.type ) tpl1 == tpl2 #=> true
@see {URITemplate#type}
# File lib/uri_template/rfc6570.rb, line 436 def type self.class::TYPE end
Protected Instance Methods
@private
# File lib/uri_template/rfc6570.rb, line 475 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
@private
# File lib/uri_template/rfc6570.rb, line 470 def tokenize! self.class::Tokenizer.new(pattern, self.class::OPERATORS).to_a end