Class: Addressable::Template

Inherits:
Object
  • Object
show all
Defined in:
lib/addressable/template.rb

Overview

This is an implementation of a URI template based on URI Template draft 03.

Defined Under Namespace

Classes: InvalidTemplateOperatorError, InvalidTemplateValueError, MatchData, TemplateOperatorAbortedError

Constant Summary

OPERATOR_EXPANSION =
/\{-([a-zA-Z]+)\|([#{anything}]+)\|([#{anything}]+)\}/
VARIABLE_EXPANSION =
/\{([#{anything}]+?)(?:=([#{anything}]+))?\}/

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Addressable::Template) initialize(pattern)

Creates a new Addressable::Template object.

Parameters:

  • pattern (#to_str)

    The URI Template pattern.



123
124
125
126
127
128
# File 'lib/addressable/template.rb', line 123

def initialize(pattern)
  if !pattern.respond_to?(:to_str)
    raise TypeError, "Can't convert #{pattern.class} into String."
  end
  @pattern = pattern.to_str.freeze
end

Instance Attribute Details

- (String) pattern (readonly)

The Template object's pattern.

Returns:

  • (String)

    The Template object's pattern.



132
133
134
# File 'lib/addressable/template.rb', line 132

def pattern
  @pattern
end

Instance Method Details

- (Addressable::URI) expand(mapping, processor = nil)

Expands a URI template into a full URI.

The object should respond to either the validate or transform messages or both. Both the validate and transform methods should take two parameters: name and value. The validate method should return true or false; true if the value of the variable is valid, false otherwise. An InvalidTemplateValueError exception will be raised if the value is invalid. The transform method should return the transformed variable value as a String. If a transform method is used, the value will not be percent encoded automatically. Unicode normalization will be performed both before and after sending the value to the transform method.

Examples:

class ExampleProcessor
  def self.validate(name, value)
    return !!(value =~ /^[\w ]+$/) if name == "query"
    return true
  end

  def self.transform(name, value)
    return value.gsub(/ /, "+") if name == "query"
    return value
  end
end

Addressable::Template.new(
  "http://example.com/search/{query}/"
).expand(
  {"query" => "an example search query"},
  ExampleProcessor
).to_str
#=> "http://example.com/search/an+example+search+query/"

Addressable::Template.new(
  "http://example.com/search/{-list|+|query}/"
).expand(
  {"query" => "an example search query".split(" ")}
).to_str
#=> "http://example.com/search/an+example+search+query/"

Addressable::Template.new(
  "http://example.com/search/{query}/"
).expand(
  {"query" => "bogus!"},
  ExampleProcessor
).to_str
#=> Addressable::Template::InvalidTemplateValueError

Parameters:

  • mapping (Hash)

    The mapping that corresponds to the pattern.

  • processor (#validate, #transform) (defaults to: nil)

    An optional processor object may be supplied.

Returns:



454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# File 'lib/addressable/template.rb', line 454

def expand(mapping, processor=nil)
  result = self.pattern.dup
  transformed_mapping = transform_mapping(mapping, processor)
  result.gsub!(
    /#{OPERATOR_EXPANSION}|#{VARIABLE_EXPANSION}/
  ) do |capture|
    if capture =~ OPERATOR_EXPANSION
      operator, argument, variables, default_mapping =
        parse_template_expansion(capture, transformed_mapping)
      expand_method = "expand_#{operator}_operator"
      if ([expand_method, expand_method.to_sym] & private_methods).empty?
        raise InvalidTemplateOperatorError,
          "Invalid template operator: #{operator}"
      else
        send(expand_method.to_sym, argument, variables, default_mapping)
      end
    else
      varname, _, vardefault = capture.scan(/^\{(.+?)(=(.*))?\}$/)[0]
      transformed_mapping[varname] || vardefault
    end
  end
  return Addressable::URI.parse(result)
end

- (Hash, NilClass) extract(uri, processor = nil)

Extracts a mapping from the URI using a URI Template pattern.

Examples:

class ExampleProcessor
  def self.restore(name, value)
    return value.gsub(/\+/, " ") if name == "query"
    return value
  end

  def self.match(name)
    return ".*?" if name == "first"
    return ".*"
  end
end

uri = Addressable::URI.parse(
  "http://example.com/search/an+example+search+query/"
)
Addressable::Template.new(
  "http://example.com/search/{query}/"
).extract(uri, ExampleProcessor)
#=> {"query" => "an example search query"}

uri = Addressable::URI.parse("http://example.com/a/b/c/")
Addressable::Template.new(
  "http://example.com/{first}/{second}/"
).extract(uri, ExampleProcessor)
#=> {"first" => "a", "second" => "b/c"}

uri = Addressable::URI.parse("http://example.com/a/b/c/")
Addressable::Template.new(
  "http://example.com/{first}/{-list|/|second}/"
).extract(uri)
#=> {"first" => "a", "second" => ["b", "c"]}

Parameters:

  • uri (Addressable::URI, #to_str)

    The URI to extract from.

  • processor (#restore, #match) (defaults to: nil)

    A template processor object may optionally be supplied.

    The object should respond to either the restore or match messages or both. The restore method should take two parameters: [String] name and [String] value. The restore method should reverse any transformations that have been performed on the value to ensure a valid URI. The match method should take a single parameter: [String] name. The match method should return a String containing a regular expression capture group for matching on that particular variable. The default value is ".*?". The match method has no effect on multivariate operator expansions.

Returns:

  • (Hash, NilClass)

    The Hash mapping that was extracted from the URI, or nil if the URI didn't match the template.



200
201
202
203
# File 'lib/addressable/template.rb', line 200

def extract(uri, processor=nil)
  match_data = self.match(uri, processor)
  return (match_data ? match_data.mapping : nil)
end

- (String) inspect

Returns a String representation of the Template object's state.

Returns:

  • (String)

    The Template object's state, as a String.



138
139
140
141
# File 'lib/addressable/template.rb', line 138

def inspect
  sprintf("#<%s:%#0x PATTERN:%s>",
    self.class.to_s, self.object_id, self.pattern)
end

- (Hash, NilClass) match(uri, processor = nil)

Extracts match data from the URI using a URI Template pattern.

Examples:

class ExampleProcessor
  def self.restore(name, value)
    return value.gsub(/\+/, " ") if name == "query"
    return value
  end

  def self.match(name)
    return ".*?" if name == "first"
    return ".*"
  end
end

uri = Addressable::URI.parse(
  "http://example.com/search/an+example+search+query/"
)
match = Addressable::Template.new(
  "http://example.com/search/{query}/"
).match(uri, ExampleProcessor)
match.variables
#=> ["query"]
match.captures
#=> ["an example search query"]

uri = Addressable::URI.parse("http://example.com/a/b/c/")
match = Addressable::Template.new(
  "http://example.com/{first}/{second}/"
).match(uri, ExampleProcessor)
match.variables
#=> ["first", "second"]
match.captures
#=> ["a", "b/c"]

uri = Addressable::URI.parse("http://example.com/a/b/c/")
match = Addressable::Template.new(
  "http://example.com/{first}/{-list|/|second}/"
).match(uri)
match.variables
#=> ["first", "second"]
match.captures
#=> ["a", ["b", "c"]]

Parameters:

  • uri (Addressable::URI, #to_str)

    The URI to extract from.

  • processor (#restore, #match) (defaults to: nil)

    A template processor object may optionally be supplied.

    The object should respond to either the restore or match messages or both. The restore method should take two parameters: [String] name and [String] value. The restore method should reverse any transformations that have been performed on the value to ensure a valid URI. The match method should take a single parameter: [String] name. The match method should return a String containing a regular expression capture group for matching on that particular variable. The default value is ".*?". The match method has no effect on multivariate operator expansions.

Returns:

  • (Hash, NilClass)

    The Hash mapping that was extracted from the URI, or nil if the URI didn't match the template.



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/addressable/template.rb', line 271

def match(uri, processor=nil)
  uri = Addressable::URI.parse(uri)
  mapping = {}

  # First, we need to process the pattern, and extract the values.
  expansions, expansion_regexp =
    parse_template_pattern(pattern, processor)
  unparsed_values = uri.to_str.scan(expansion_regexp).flatten

  if uri.to_str == pattern
    return Addressable::Template::MatchData.new(uri, self, mapping)
  elsif expansions.size > 0 && expansions.size == unparsed_values.size
    expansions.each_with_index do |expansion, index|
      unparsed_value = unparsed_values[index]
      if expansion =~ OPERATOR_EXPANSION
        operator, argument, variables =
          parse_template_expansion(expansion)
        extract_method = "extract_#{operator}_operator"
        if ([extract_method, extract_method.to_sym] &
            private_methods).empty?
          raise InvalidTemplateOperatorError,
            "Invalid template operator: #{operator}"
        else
          begin
            send(
              extract_method.to_sym, unparsed_value, processor,
              argument, variables, mapping
            )
          rescue TemplateOperatorAbortedError
            return nil
          end
        end
      else
        name = expansion[VARIABLE_EXPANSION, 1]
        value = unparsed_value
        if processor != nil && processor.respond_to?(:restore)
          value = processor.restore(name, value)
        end
        if mapping[name] == nil || mapping[name] == value
          mapping[name] = value
        else
          return nil
        end
      end
    end
    return Addressable::Template::MatchData.new(uri, self, mapping)
  else
    return nil
  end
end

- (Addressable::Template) partial_expand(mapping, processor = nil)

Expands a URI template into another URI template.

The object should respond to either the validate or transform messages or both. Both the validate and transform methods should take two parameters: name and value. The validate method should return true or false; true if the value of the variable is valid, false otherwise. An InvalidTemplateValueError exception will be raised if the value is invalid. The transform method should return the transformed variable value as a String. If a transform method is used, the value will not be percent encoded automatically. Unicode normalization will be performed both before and after sending the value to the transform method.

Examples:

Addressable::Template.new(
  "http://example.com/{one}/{two}/"
).partial_expand({"one" => "1"}).pattern
#=> "http://example.com/1/{two}/"

Addressable::Template.new(
  "http://example.com/search/{-list|+|query}/"
).partial_expand(
  {"query" => "an example search query".split(" ")}
).pattern
#=> "http://example.com/search/an+example+search+query/"

Addressable::Template.new(
  "http://example.com/{-join|&|one,two}/"
).partial_expand({"one" => "1"}).pattern
#=> "http://example.com/?one=1{-prefix|&two=|two}"

Addressable::Template.new(
  "http://example.com/{-join|&|one,two,three}/"
).partial_expand({"one" => "1", "three" => 3}).pattern
#=> "http://example.com/?one=1{-prefix|&two=|two}&three=3"

Parameters:

  • mapping (Hash)

    The mapping that corresponds to the pattern.

  • processor (#validate, #transform) (defaults to: nil)

    An optional processor object may be supplied.

Returns:



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/addressable/template.rb', line 365

def partial_expand(mapping, processor=nil)
  result = self.pattern.dup
  transformed_mapping = transform_mapping(mapping, processor)
  result.gsub!(
    /#{OPERATOR_EXPANSION}|#{VARIABLE_EXPANSION}/
  ) do |capture|
    if capture =~ OPERATOR_EXPANSION
      operator, argument, variables, default_mapping =
        parse_template_expansion(capture, transformed_mapping)
      expand_method = "expand_#{operator}_operator"
      if ([expand_method, expand_method.to_sym] & private_methods).empty?
        raise InvalidTemplateOperatorError,
          "Invalid template operator: #{operator}"
      else
        send(
          expand_method.to_sym, argument, variables,
          default_mapping, true
        )
      end
    else
      varname, _, vardefault = capture.scan(/^\{(.+?)(=(.*))?\}$/)[0]
      if transformed_mapping[varname]
        transformed_mapping[varname]
      elsif vardefault
        "{#{varname}=#{vardefault}}"
      else
        "{#{varname}}"
      end
    end
  end
  return Addressable::Template.new(result)
end

- (Hash) variable_defaults

Returns a mapping of variables to their default values specified in the template. Variables without defaults are not returned.

Returns:

  • (Hash)

    Mapping of template variables to their defaults



495
496
497
498
# File 'lib/addressable/template.rb', line 495

def variable_defaults
  @variable_defaults ||=
    Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten]
end

- (Array) variables Also known as: keys

Returns an Array of variables used within the template pattern. The variables are listed in the Array in the order they appear within the pattern. Multiple occurrences of a variable within a pattern are not represented in this Array.

Returns:

  • (Array)

    The variables present in the template's pattern.



485
486
487
# File 'lib/addressable/template.rb', line 485

def variables
  @variables ||= ordered_variable_defaults.map { |var, val| var }.uniq
end