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

options[R]

Public Class Methods

new(pattern_or_tokens,options={}) click to toggle source

@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

extract(uri_or_match, post_processing = DEFAULT_PROCESSING ) { |result| ... } click to toggle source

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
extract_simple(uri_or_match) click to toggle source

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
level() click to toggle source

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
to_r() click to toggle source

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
tokens() click to toggle source

Returns an array containing a the template tokens.

# File lib/uri_template/rfc6570.rb, line 464
def tokens
  @tokens ||= tokenize!
end
type() click to toggle source

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

extract_matchdata(matchdata, post_processing) click to toggle source

@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
tokenize!() click to toggle source

@private

# File lib/uri_template/rfc6570.rb, line 470
def tokenize!
  self.class::Tokenizer.new(pattern, self.class::OPERATORS).to_a
end