Class: URITemplate::RFC6570

Inherits:
Object
  • Object
show all
Extended by:
Forwardable, ClassMethods
Includes:
URITemplate
Defined in:
lib/uri_template/rfc6570.rb,
lib/uri_template/rfc6570/expression.rb,
lib/uri_template/rfc6570/regex_builder.rb,
lib/uri_template/rfc6570/expression/named.rb

Overview

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.

A uri template which should comply with the rfc 6570 ( tools.ietf.org/html/rfc6570 ).

Defined Under Namespace

Modules: ClassMethods, Utils Classes: Expression, Invalid, Literal, RegexBuilder, Token, Tokenizer

Constant Summary collapse

TYPE =
:rfc6570
LITERAL =
Regexp.compile('([^"\'%<>\\\\^`{|}\x00-\x1F\x7F-\x9F\s]|%[0-9a-fA-F]{2})+',Utils::KCODE_UTF8)
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_'
  }

}
NO_PROCESSING =

Specifies that no processing should be done upon extraction.

See Also:

[]
CONVERT_VALUES =

Specifies that the extracted values should be processed.

See Also:

[:convert_values]
CONVERT_RESULT =

Specifies that the extracted variable list should be processed.

See Also:

[:convert_result]
DEFAULT_PROCESSING =

Default processing. Means: convert values and the list itself.

See Also:

CONVERT_VALUES + CONVERT_RESULT
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__
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__
URI =
Regexp.compile(<<__REGEXP__.strip, Utils::KCODE_UTF8)
\\A(#{LITERAL.source}|#{EXPRESSION.source})*\\z
__REGEXP__
OPERATORS =
{
  ''  => Expression::Basic,
  '+' => Expression::Reserved,
  '#' => Expression::Fragment,
  '.' => Expression::Label,
  '/' => Expression::Path,
  ';' => Expression::PathParameters,
  '?' => Expression::FormQuery,
  '&' => Expression::FormQueryContinuation
}

Constants included from URITemplate

HOST_REGEX, SCHEME_REGEX, URI_SPLIT, VERSIONS

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ClassMethods

try_convert, valid?

Methods included from URITemplate

apply, coerce, coerce_first_arg, #concat, #eq, #host?, new, #path_concat, #pattern, #relative?, resolve_class, #scheme?, #static_characters, #variables

Methods included from ClassMethods

#convert, #included, #try_convert

Constructor Details

#initialize(pattern_or_tokens, options = {}) ⇒ RFC6570

Returns a new instance of RFC6570.

Parameters:

  • pattern_or_tokens (String, Array)

    either a pattern as String or an Array of tokens

  • options (Hash) (defaults to: {})

    some options

  • :lazy (Hash)

    a customizable set of options



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/uri_template/rfc6570.rb', line 294

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

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



289
290
291
# File 'lib/uri_template/rfc6570.rb', line 289

def options
  @options
end

Instance Method Details

#===(uri) ⇒ Object

Alias for to_r.=== . Tests whether this template matches a given uri.

Returns:

  • TrueClass, FalseClass



421
# File 'lib/uri_template/rfc6570.rb', line 421

def_delegators :to_r, :===

#expand(variables = {}) ⇒ Object

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.

Expands the template with the given variables. The expansion should be compatible to uritemplate spec rfc 6570 ( tools.ietf.org/html/rfc6570 ).

Examples:

URITemplate::RFC6570.new('{foo}').expand('foo'=>'bar') #=> 'bar'
URITemplate::RFC6570.new('{?args*}').expand('args'=>{'key'=>'value'}) #=> '?key=value'
URITemplate::RFC6570.new('{undef}').expand() #=> ''

Parameters:

  • variables (Hash, Array) (defaults to: {})

Returns:

  • String



# File 'lib/uri_template/rfc6570.rb', line 310

#expand_partial(variables = {}) ⇒ Object

Works like expand but keeps missing variables in place.

Examples:

URITemplate::RFC6570.new('{foo}').expand_partial('foo'=>'bar') #=> URITemplate::RFC6570.new('bar{foo}')
URITemplate::RFC6570.new('{undef}').expand_partial() #=> URITemplate::RFC6570.new('{undef}')

Parameters:

  • variables (Hash, Array) (defaults to: {})

Returns:

  • URITemplate



# File 'lib/uri_template/rfc6570.rb', line 325

#extract(uri_or_match, post_processing = DEFAULT_PROCESSING) ⇒ Object

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
Note:

The current implementation drops duplicated variables instead of checking them.

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.

Examples:

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'}

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']]

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

Parameters:

  • uri_or_match (String, MatchData)

    Uri_or_MatchData A uri or a matchdata from which the variables should be extracted.

  • post_processing (Array) (defaults to: DEFAULT_PROCESSING)

    Processing Specifies which processing should be done.

Raises:

  • Encoding::InvalidByteSequenceError when the given uri was not properly encoded.

  • Encoding::UndefinedConversionError when the given uri could not be converted to utf-8.

  • Encoding::CompatibilityError when the given uri could not be converted to utf-8.



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/uri_template/rfc6570.rb', line 387

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) ⇒ Object

Extracts variables without any proccessing. This is equivalent to #extract with options NO_PROCESSING.

See Also:



414
415
416
# File 'lib/uri_template/rfc6570.rb', line 414

def extract_simple(uri_or_match)
  extract( uri_or_match, NO_PROCESSING )
end

#levelObject

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

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.

Examples:

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


460
461
462
# File 'lib/uri_template/rfc6570.rb', line 460

def level
  tokens.map(&:level).max
end

#match(uri) { ... } ⇒ Object

Alias for to_r.match . Matches this template against the given uri.

Yields:

  • MatchData

Returns:

  • MatchData, Object



427
# File 'lib/uri_template/rfc6570.rb', line 427

def_delegators :to_r, :match

#to_rObject

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 #===.

Examples:

tpl = URITemplate::RFC6570.new('/foo/{bar}/')
regex = tpl.to_r
regex === '/foo/baz/' #=> true
regex === '/foz/baz/' #=> false

Returns:

  • Regexp



343
344
345
346
347
348
349
350
# File 'lib/uri_template/rfc6570.rb', line 343

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

#tokensObject

Returns an array containing a the template tokens.



465
466
467
# File 'lib/uri_template/rfc6570.rb', line 465

def tokens
  @tokens ||= tokenize!
end

#typeObject

The type of this template.

Examples:

tpl1 = URITemplate::RFC6570.new('/foo')
tpl2 = URITemplate.new( tpl1.pattern, tpl1.type )
tpl1 == tpl2 #=> true

See Also:

  • URITemplate::RFC6570.{URITemplate{URITemplate#type}


437
438
439
# File 'lib/uri_template/rfc6570.rb', line 437

def type
  self.class::TYPE
end