class Addressable::Template

Experimental RBS support (using type sampling data from the type_fusion project).

# sig/addressable/template.rbs

class Addressable::Template
  def initialize: (String pattern) -> void
  def normalize_keys: (Hash mapping) -> untyped
  def ordered_variable_defaults: () -> untyped
  def parse_new_template_pattern: (String pattern, ?nil processor) -> untyped
  def parse_template_pattern: (String pattern, ?nil processor) -> untyped
  def partial_expand: (Hash mapping, ?nil processor, ?true normalize_values) -> untyped
  def variables: () -> untyped
end

RFC 6570 (tools.ietf.org/html/rfc6570).
This is an implementation of a URI template based on
#

def ==(template)

Returns:
  • (TrueClass, FalseClass) -

Parameters:
  • template (Object) -- The Template to compare.
def ==(template)
  return false unless template.kind_of?(Template)
  return self.pattern == template.pattern
end

def expand(mapping, processor=nil, normalize_values=true)

Returns:
  • (Addressable::URI) - The expanded URI template.

Parameters:
  • normalize_values (Boolean) --
  • processor (#validate, #transform) --
  • mapping (Hash) -- The mapping that corresponds to the pattern.
def expand(mapping, processor=nil, normalize_values=true)
  result = self.pattern.dup
  mapping = normalize_keys(mapping)
  result.gsub!( EXPRESSION ) do |capture|
    transform_capture(mapping, capture, processor, normalize_values)
  end
  return Addressable::URI.parse(result)
end

def extract(uri, processor=nil)

Returns:
  • (Hash, NilClass) -

Parameters:
  • processor (#restore, #match) --
  • uri (Addressable::URI, #to_str) --
def extract(uri, processor=nil)
  match_data = self.match(uri, processor)
  return (match_data ? match_data.mapping : nil)
end

def freeze

Returns:
  • (Addressable::URI) - The frozen URI object.
def freeze
  self.variables
  self.variable_defaults
  self.named_captures
  super
end

def initialize(pattern)

Experimental RBS support (using type sampling data from the type_fusion project).

def initialize: (String pattern) -> void

This signature was generated using 5 samples from 2 applications.

Returns:
  • (Addressable::Template) - The initialized Template object.

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

def inspect

Returns:
  • (String) - The Template object's state, as a String.
def inspect
  sprintf("#<%s:%#0x PATTERN:%s>",
    self.class.to_s, self.object_id, self.pattern)
end

def join_values(operator, return_value)

Returns:
  • (String) - The transformed mapped value

Parameters:
  • return_value (Array) --
  • operator (String, Nil) -- One of the operators from the set
def join_values(operator, return_value)
  leader = LEADERS.fetch(operator, '')
  joiner = JOINERS.fetch(operator, ',')
  case operator
  when '&', '?'
    leader + return_value.map{|k,v|
      if v.is_a?(Array) && v.first =~ /=/
        v.join(joiner)
      elsif v.is_a?(Array)
        v.map{|inner_value| "#{k}=#{inner_value}"}.join(joiner)
      else
        "#{k}=#{v}"
      end
    }.join(joiner)
  when ';'
    return_value.map{|k,v|
      if v.is_a?(Array) && v.first =~ /=/
        ';' + v.join(";")
      elsif v.is_a?(Array)
        ';' + v.map{|inner_value| "#{k}=#{inner_value}"}.join(";")
      else
        v && v != '' ?  ";#{k}=#{v}" : ";#{k}"
      end
    }.join
  else
    leader + return_value.map{|k,v| v}.join(joiner)
  end
end

def match(uri, processor=nil)

Returns:
  • (Hash, NilClass) -

Parameters:
  • processor (#restore, #match) --
  • uri (Addressable::URI, #to_str) --
def match(uri, processor=nil)
  uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
  mapping = {}
  # First, we need to process the pattern, and extract the values.
  expansions, expansion_regexp =
    parse_template_pattern(pattern, processor)
  return nil unless uri.to_str.match(expansion_regexp)
  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
    index = 0
    expansions.each do |expansion|
      _, operator, varlist = *expansion.match(EXPRESSION)
      varlist.split(',').each do |varspec|
        _, name, modifier = *varspec.match(VARSPEC)
        mapping[name] ||= nil
        case operator
        when nil, '+', '#', '/', '.'
          unparsed_value = unparsed_values[index]
          name = varspec[VARSPEC, 1]
          value = unparsed_value
          value = value.split(JOINERS[operator]) if value && modifier == '*'
        when ';', '?', '&'
          if modifier == '*'
            if unparsed_values[index]
              value = unparsed_values[index].split(JOINERS[operator])
              value = value.inject({}) do |acc, v|
                key, val = v.split('=')
                val = "" if val.nil?
                acc[key] = val
                acc
              end
            end
          else
            if (unparsed_values[index])
              name, value = unparsed_values[index].split('=')
              value = "" if value.nil?
            end
          end
        end
        if processor != nil && processor.respond_to?(:restore)
          value = processor.restore(name, value)
        end
        if processor == nil
          if value.is_a?(Hash)
            value = value.inject({}){|acc, (k, v)|
              acc[Addressable::URI.unencode_component(k)] =
                Addressable::URI.unencode_component(v)
              acc
            }
          elsif value.is_a?(Array)
            value = value.map{|v| Addressable::URI.unencode_component(v) }
          else
            value = Addressable::URI.unencode_component(value)
          end
        end
        if !mapping.has_key?(name) || mapping[name].nil?
          # Doesn't exist, set to value (even if value is nil)
          mapping[name] = value
        end
        index = index + 1
      end
    end
    return Addressable::Template::MatchData.new(uri, self, mapping)
  else
    return nil
  end
end

def named_captures

Other tags:
    Api: - private

Returns:
  • (Hash) - The named captures of the `Regexp` given by {#to_regexp}.
def named_captures
  self.to_regexp.named_captures
end

def normalize_keys(mapping)

Experimental RBS support (using type sampling data from the type_fusion project).

def normalize_keys: (sample | gem_name | String | gem_version | String | receiver | String | method_name | String | application_name | String | location | String | type_fusion_version | String | parameters |  | return_value | Array | String | String mapping) -> untyped

This signature was generated using 1 sample from 1 application.

Returns:
  • (Hash) -

Parameters:
  • mapping (Hash) -- A mapping hash to normalize
def normalize_keys(mapping)
  return mapping.inject({}) do |accu, pair|
    name, value = pair
    if Symbol === name
      name = name.to_s
    elsif name.respond_to?(:to_str)
      name = name.to_str
    else
      raise TypeError,
        "Can't convert #{name.class} into String."
    end
    accu[name] = value
    accu
  end
end

def normalize_value(value)

Returns:
  • (Hash, Array, String) - The normalized values

Parameters:
  • value (Hash, Array, String) --
def normalize_value(value)
  # Handle unicode normalization
  if value.respond_to?(:to_ary)
    value.to_ary.map! { |val| normalize_value(val) }
  elsif value.kind_of?(Hash)
    value = value.inject({}) { |acc, (k, v)|
      acc[normalize_value(k)] = normalize_value(v)
      acc
    }
  else
    value = value.to_s if !value.kind_of?(String)
    if value.encoding != Encoding::UTF_8
      value = value.dup.force_encoding(Encoding::UTF_8)
    end
    value = value.unicode_normalize(:nfc)
  end
  value
end

def ordered_variable_defaults

Experimental RBS support (using type sampling data from the type_fusion project).

def ordered_variable_defaults: () -> untyped

This signature was generated using 4 samples from 1 application.

def ordered_variable_defaults
  @ordered_variable_defaults ||= begin
    expansions, _ = parse_template_pattern(pattern)
    expansions.flat_map do |capture|
      _, _, varlist = *capture.match(EXPRESSION)
      varlist.split(',').map do |varspec|
        varspec[VARSPEC, 1]
      end
    end
  end
end

def parse_new_template_pattern(pattern, processor = nil)

Experimental RBS support (using type sampling data from the type_fusion project).

def parse_new_template_pattern: (String pattern, ?nil processor) -> untyped

This signature was generated using 6 samples from 2 applications.

Returns:
  • (Array, Regexp) -

Parameters:
  • processor (#match) -- The template processor to use.
  • pattern (String) -- The URI template pattern.
def parse_new_template_pattern(pattern, processor = nil)
  # Escape the pattern. The two gsubs restore the escaped curly braces
  # back to their original form. Basically, escape everything that isn't
  # within an expansion.
  escaped_pattern = Regexp.escape(
    pattern
  ).gsub(/\\\{(.*?)\\\}/) do |escaped|
    escaped.gsub(/\\(.)/, "\\1")
  end
  expansions = []
  # Create a regular expression that captures the values of the
  # variables in the URI.
  regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion|
    expansions << expansion
    _, operator, varlist = *expansion.match(EXPRESSION)
    leader = Regexp.escape(LEADERS.fetch(operator, ''))
    joiner = Regexp.escape(JOINERS.fetch(operator, ','))
    combined = varlist.split(',').map do |varspec|
      _, name, modifier = *varspec.match(VARSPEC)
      result = processor && processor.respond_to?(:match) ? processor.match(name) : nil
      if result
        "(?<#{name}>#{ result })"
      else
        group = case operator
        when '+'
          "#{ RESERVED }*?"
        when '#'
          "#{ RESERVED }*?"
        when '/'
          "#{ UNRESERVED }*?"
        when '.'
          "#{ UNRESERVED.gsub('\.', '') }*?"
        when ';'
          "#{ UNRESERVED }*=?#{ UNRESERVED }*?"
        when '?'
          "#{ UNRESERVED }*=#{ UNRESERVED }*?"
        when '&'
          "#{ UNRESERVED }*=#{ UNRESERVED }*?"
        else
          "#{ UNRESERVED }*?"
        end
        if modifier == '*'
          "(?<#{name}>#{group}(?:#{joiner}?#{group})*)?"
        else
          "(?<#{name}>#{group})?"
        end
      end
    end.join("#{joiner}?")
    "(?:|#{leader}#{combined})"
  end
  # Ensure that the regular expression matches the whole URI.
  regexp_string = "\\A#{regexp_string}\\z"
  return expansions, Regexp.new(regexp_string)
end

def parse_template_pattern(pattern, processor = nil)

Experimental RBS support (using type sampling data from the type_fusion project).

def parse_template_pattern: (String pattern, ?nil processor) -> untyped

This signature was generated using 5 samples from 2 applications.

Returns:
  • (Array, Regexp) -

Parameters:
  • processor (#match) -- The template processor to use.
  • pattern (String) -- The URI template pattern.
def parse_template_pattern(pattern, processor = nil)
  if processor.nil? && pattern == @pattern
    @cached_template_parse ||=
      parse_new_template_pattern(pattern, processor)
  else
    parse_new_template_pattern(pattern, processor)
  end
end

def partial_expand(mapping, processor=nil, normalize_values=true)

Experimental RBS support (using type sampling data from the type_fusion project).

def partial_expand: (sample | gem_name | String | gem_version | String | receiver | String | method_name | String | application_name | String | location | String | type_fusion_version | String | parameters |  mapping, ?nil processor, ?true normalize_values) -> untyped

This signature was generated using 2 samples from 1 application.

Returns:
  • (Addressable::Template) - The partially expanded URI template.

Parameters:
  • normalize_values (Boolean) --
  • processor (#validate, #transform) --
  • mapping (Hash) -- The mapping that corresponds to the pattern.
def partial_expand(mapping, processor=nil, normalize_values=true)
  result = self.pattern.dup
  mapping = normalize_keys(mapping)
  result.gsub!( EXPRESSION ) do |capture|
    transform_partial_capture(mapping, capture, processor, normalize_values)
  end
  return Addressable::Template.new(result)
end

def source

Other tags:
    Api: - private

Returns:
  • (String) - The source of the `Regexp` given by {#to_regexp}.
def source
  self.to_regexp.source
end

def to_regexp

Returns:
  • (Regexp) - A regular expression which should match the template.
def to_regexp
  _, source = parse_template_pattern(pattern)
  Regexp.new(source)
end

def transform_capture(mapping, capture, processor=nil,

Returns:
  • (String) - The expanded expression

Parameters:
  • normalize_values (Boolean) --
  • processor (#validate, #transform) --
  • capture (String) --
  • mapping (Hash) -- The mapping to replace captures
def transform_capture(mapping, capture, processor=nil,
                      normalize_values=true)
  _, operator, varlist = *capture.match(EXPRESSION)
  return_value = varlist.split(',').inject([]) do |acc, varspec|
    _, name, modifier = *varspec.match(VARSPEC)
    value = mapping[name]
    unless value == nil || value == {}
      allow_reserved = %w(+ #).include?(operator)
      # Common primitives where the .to_s output is well-defined
      if Numeric === value || Symbol === value ||
          value == true || value == false
        value = value.to_s
      end
      length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/
      unless (Hash === value) ||
        value.respond_to?(:to_ary) || value.respond_to?(:to_str)
        raise TypeError,
          "Can't convert #{value.class} into String or Array."
      end
      value = normalize_value(value) if normalize_values
      if processor == nil || !processor.respond_to?(:transform)
        # Handle percent escaping
        if allow_reserved
          encode_map =
            Addressable::URI::CharacterClasses::RESERVED +
            Addressable::URI::CharacterClasses::UNRESERVED
        else
          encode_map = Addressable::URI::CharacterClasses::UNRESERVED
        end
        if value.kind_of?(Array)
          transformed_value = value.map do |val|
            if length
              Addressable::URI.encode_component(val[0...length], encode_map)
            else
              Addressable::URI.encode_component(val, encode_map)
            end
          end
          unless modifier == "*"
            transformed_value = transformed_value.join(',')
          end
        elsif value.kind_of?(Hash)
          transformed_value = value.map do |key, val|
            if modifier == "*"
              "#{
                Addressable::URI.encode_component( key, encode_map)
              }=#{
                Addressable::URI.encode_component( val, encode_map)
              }"
            else
              "#{
                Addressable::URI.encode_component( key, encode_map)
              },#{
                Addressable::URI.encode_component( val, encode_map)
              }"
            end
          end
          unless modifier == "*"
            transformed_value = transformed_value.join(',')
          end
        else
          if length
            transformed_value = Addressable::URI.encode_component(
              value[0...length], encode_map)
          else
            transformed_value = Addressable::URI.encode_component(
              value, encode_map)
          end
        end
      end
      # Process, if we've got a processor
      if processor != nil
        if processor.respond_to?(:validate)
          if !processor.validate(name, value)
            display_value = value.kind_of?(Array) ? value.inspect : value
            raise InvalidTemplateValueError,
              "#{name}=#{display_value} is an invalid template value."
          end
        end
        if processor.respond_to?(:transform)
          transformed_value = processor.transform(name, value)
          if normalize_values
            transformed_value = normalize_value(transformed_value)
          end
        end
      end
      acc << [name, transformed_value]
    end
    acc
  end
  return "" if return_value.empty?
  join_values(operator, return_value)
end

def transform_partial_capture(mapping, capture, processor = nil,

Returns:
  • (String) - The expanded expression

Parameters:
  • normalize_values (Boolean) --
  • processor (#validate, #transform) --
  • capture (String) --
  • mapping (Hash) --
def transform_partial_capture(mapping, capture, processor = nil,
                              normalize_values = true)
  _, operator, varlist = *capture.match(EXPRESSION)
  vars = varlist.split(",")
  if operator == "?"
    # partial expansion of form style query variables sometimes requires a
    # slight reordering of the variables to produce a valid url.
    first_to_expand = vars.find { |varspec|
      _, name, _ =  *varspec.match(VARSPEC)
      mapping.key?(name) && !mapping[name].nil?
    }
    vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand}  if first_to_expand
  end
  vars.
    inject("".dup) do |acc, varspec|
      _, name, _ =  *varspec.match(VARSPEC)
      next_val = if mapping.key? name
                   transform_capture(mapping, "{#{operator}#{varspec}}",
                                     processor, normalize_values)
                 else
                   "{#{operator}#{varspec}}"
                 end
      # If we've already expanded at least one '?' operator with non-empty
      # value, change to '&'
      operator = "&" if (operator == "?") && (next_val != "")
      acc << next_val
  end
end

def variable_defaults

Returns:
  • (Hash) - Mapping of template variables to their defaults
def variable_defaults
  @variable_defaults ||=
    Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten]
end

def variables

Experimental RBS support (using type sampling data from the type_fusion project).

def variables: () -> untyped

This signature was generated using 4 samples from 1 application.

Returns:
  • (Array) - The variables present in the template's pattern.
def variables
  @variables ||= ordered_variable_defaults.map { |var, val| var }.uniq
end