class Mustermann::Pattern

Superclass for all pattern implementations. @abstract

Constants

ALWAYS_ARRAY

@!visibility private

Attributes

options[R]

options hash passed to new (with unsupported options removed) @!visibility private

uri_decode[R]

Public Class Methods

new(string, ignore_unknown_options: false, **options) click to toggle source

@overload new(string, **options) @param (see initialize) @raise (see initialize) @raise [ArgumentError] if some option is not supported @return [Mustermann::Pattern] a new instance of Mustermann::Pattern @see initialize

Calls superclass method Mustermann.new
# File lib/mustermann/pattern.rb, line 49
def self.new(string, ignore_unknown_options: false, **options)
  if ignore_unknown_options
    options = options.select { |key, value| supported?(key, **options) if key != :ignore_unknown_options }
  else
    unsupported = options.keys.detect { |key| not supported?(key, **options) }
    raise ArgumentError, "unsupported option %p for %p" % [unsupported, self] if unsupported
  end

  @map ||= EqualityMap.new
  @map.fetch(string, options) { super(string, options) { options } }
end
new(string, uri_decode: true, **options) { || ... } click to toggle source

@overload initialize(string, **options) @param [String] string the string representation of the pattern @param [Hash] options options for fine-tuning the pattern behavior @raise [Mustermann::Error] if the pattern can't be generated from the string @see file:README.md#Types_and_Options “Types and Options” in the README @see Mustermann.new

# File lib/mustermann/pattern.rb, line 74
def initialize(string, uri_decode: true, **options)
  @uri_decode = uri_decode
  @string     = string.to_s.dup
  @options    = yield.freeze if block_given?
end
register(*names) click to toggle source

Registers the pattern with Mustermann. @see Mustermann.register @!visibility private

# File lib/mustermann/pattern.rb, line 33
def self.register(*names)
  names.each { |name| Mustermann.register(name, self) }
end
supported?(option, **options) click to toggle source

@param [Symbol] option The option to check. @return [Boolean] Whether or not option is supported.

# File lib/mustermann/pattern.rb, line 39
def self.supported?(option, **options)
  supported_options.include? option
end
supported_options(*list) click to toggle source

List of supported options.

@overload ::supported_options

@return [Array<Symbol>] list of supported options

@overload ::supported_options(*list)

Adds options to the list.

@api private
@param [Symbol] *list adds options to the list of supported options
@return [Array<Symbol>] list of supported options
# File lib/mustermann/pattern.rb, line 23
def self.supported_options(*list)
  @supported_options ||= []
  options = @supported_options.concat(list)
  options += superclass.supported_options if self < Pattern
  options
end

Public Instance Methods

&(other)
Alias for: |
+(other) click to toggle source

@example

require 'mustermann'
prefix = Mustermann.new("/:prefix")
about  = prefix + "/about"
about.params("/main/about") # => {"prefix" => "main"}

Creates a concatenated pattern by combingin self with the other pattern supplied. Patterns of different types can be mixed. The availability of `to_templates` and `expand` depends on the patterns being concatenated.

String input is treated as identity pattern.

@param [Mustermann::Pattern, String] other pattern to be appended @return [Mustermann::Pattern] concatenated pattern

# File lib/mustermann/pattern.rb, line 335
def +(other)
   Concat.new(self, other, type: :identity)
end
==(other) click to toggle source

Two patterns are considered equal if they are of the same type, have the same pattern string and the same options. @return [true, false]

# File lib/mustermann/pattern.rb, line 118
def ==(other)
  other.class == self.class and other.to_s == @string and other.options == options
end
===(string) click to toggle source

@param [String] string The string to match against @return [Boolean] Whether or not the pattern matches the given string @note Needs to be overridden by subclass. @see ruby-doc.org/core-2.0/Regexp.html#method-i-3D-3D-3D Regexp#===

# File lib/mustermann/pattern.rb, line 105
def ===(string)
  raise NotImplementedError, 'subclass responsibility'
end
=~(string) click to toggle source

@param [String] string The string to match against @return [Integer, nil] nil if pattern does not match the string, zero if it does. @see ruby-doc.org/core-2.0/Regexp.html#method-i-3D-7E Regexp#=~

# File lib/mustermann/pattern.rb, line 97
def =~(string)
  0 if self === string
end
^(other)
Alias for: |
always_array?(key) click to toggle source

@!visibility private

# File lib/mustermann/pattern.rb, line 390
def always_array?(key)
  ALWAYS_ARRAY.include? key
end
eql?(other) click to toggle source

Two patterns are considered equal if they are of the same type, have the same pattern string and the same options. @return [true, false]

# File lib/mustermann/pattern.rb, line 125
def eql?(other)
  other.class.eql?(self.class) and other.to_s.eql?(@string) and other.options.eql?(options)
end
expand(behavior = nil, values = {}) click to toggle source

@note This method is only implemented by certain subclasses.

@example Expanding a pattern

pattern = Mustermann.new('/:name(.:ext)?')
pattern.expand(name: 'hello')             # => "/hello"
pattern.expand(name: 'hello', ext: 'png') # => "/hello.png"

@example Checking if a pattern supports expanding

if pattern.respond_to? :expand
  pattern.expand(name: "foo")
else
  warn "does not support expanding"
end

Expanding is supported by almost all patterns (notable exceptions are {Mustermann::Shell}, {Mustermann::Regular} and {Mustermann::Simple}).

Union {Mustermann::Composite} patterns (with the | operator) support expanding if all patterns they are composed of also support it.

@param (see Mustermann::Expander#expand) @return [String] expanded string @raise [NotImplementedError] raised if expand is not supported. @raise [Mustermann::ExpandError] raised if a value is missing or unknown @see Mustermann::Expander

# File lib/mustermann/pattern.rb, line 239
def expand(behavior = nil, values = {})
  raise NotImplementedError, "expanding not supported by #{self.class}"
end
hash() click to toggle source

Used by Ruby internally for hashing. @return [Integer] same has value for patterns that are equal

# File lib/mustermann/pattern.rb, line 111
def hash
  self.class.hash | @string.hash | options.hash
end
inspect() click to toggle source

@!visibility private

# File lib/mustermann/pattern.rb, line 365
def inspect
  "#<%p:%p>" % [self.class, @string]
end
match(string) click to toggle source

@param [String] string The string to match against @return [MatchData, Mustermann::SimpleMatch, nil] MatchData or similar object if the pattern matches. @see ruby-doc.org/core-2.0/Regexp.html#method-i-match Regexp#match @see ruby-doc.org/core-2.0/MatchData.html MatchData @see Mustermann::SimpleMatch

# File lib/mustermann/pattern.rb, line 90
def match(string)
  SimpleMatch.new(string) if self === string
end
named_captures() click to toggle source

@return [Hash{String: Array<Integer>}] capture names mapped to capture index. @see ruby-doc.org/core-2.0/Regexp.html#method-i-named_captures Regexp#named_captures

# File lib/mustermann/pattern.rb, line 191
def named_captures
  {}
end
names() click to toggle source

@return [Array<String>] capture names. @see ruby-doc.org/core-2.0/Regexp.html#method-i-names Regexp#names

# File lib/mustermann/pattern.rb, line 197
def names
  []
end
params(string = nil, captures: nil, offset: 0) click to toggle source

@param [String] string the string to match against @return [Hash{String: String, Array<String>}, nil] Sinatra style params if pattern matches.

# File lib/mustermann/pattern.rb, line 203
def params(string = nil, captures: nil, offset: 0)
  return unless captures ||= match(string)
  params   = named_captures.map do |name, positions|
    values = positions.map { |pos| map_param(name, captures[pos + offset]) }.flatten
    values = values.first if values.size < 2 and not always_array? name
    [name, values]
  end

  Hash[params]
end
peek(string) click to toggle source

Tries to match the pattern against the beginning of the string (as opposed to the full string). Will return the substring if it matches.

@example

pattern = Mustermann.new('/:name')
pattern.peek("/Frank/Sinatra") # => "/Frank"

@param [String] string The string to match against @return [String, nil] matched subsctring

# File lib/mustermann/pattern.rb, line 152
def peek(string)
  size = peek_size(string)
  string[0, size] if size
end
peek_match(string) click to toggle source

Tries to match the pattern against the beginning of the string (as opposed to the full string). Will return a MatchData or similar instance for the matched substring.

@example

pattern = Mustermann.new('/:name')
pattern.peek("/Frank/Sinatra") # => #<MatchData "/Frank" name:"Frank">

@param [String] string The string to match against @return [MatchData, Mustermann::SimpleMatch, nil] MatchData or similar object if the pattern matches. @see peek_params

# File lib/mustermann/pattern.rb, line 167
def peek_match(string)
  matched = peek(string)
  match(matched) if matched
end
peek_params(string) click to toggle source

Tries to match the pattern against the beginning of the string (as opposed to the full string). Will return a two element Array with the params parsed from the substring as first entry and the length of the substring as second.

@example

pattern   = Mustermann.new('/:name')
params, _ = pattern.peek_params("/Frank/Sinatra")

puts "Hello, #{params['name']}!" # Hello, Frank!

@param [String] string The string to match against @return [Array<Hash, Integer>, nil] Array with params hash and length of substing if matched, nil otherwise

# File lib/mustermann/pattern.rb, line 184
def peek_params(string)
  match = peek_match(string)
  [params(captures: match), match.to_s.size] if match
end
peek_size(string) click to toggle source

Tries to match the pattern against the beginning of the string (as opposed to the full string). Will return the count of the matching characters if it matches.

@example

pattern = Mustermann.new('/:name')
pattern.size("/Frank/Sinatra") # => 6

@param [String] string The string to match against @return [Integer, nil] the number of characters that match

# File lib/mustermann/pattern.rb, line 138
def peek_size(string)
  # this is a very naive, unperformant implementation
  string.size.downto(0).detect { |s| self === string[0, s] }
end
respond_to?(method, *args) click to toggle source

@!visibility private @return [Boolean] @see Object#respond_to?

Calls superclass method
# File lib/mustermann/pattern.rb, line 352
def respond_to?(method, *args)
  return super unless %i[expand to_templates].include? method
  respond_to_special?(method)
end
simple_inspect() click to toggle source

@!visibility private

# File lib/mustermann/pattern.rb, line 370
def simple_inspect
  type = self.class.name[/[^:]+$/].downcase
  "%s:%p" % [type, @string]
end
to_proc() click to toggle source

@example

pattern = Mustermann.new('/:a/:b')
strings = ["foo/bar", "/foo/bar", "/foo/bar/"]
strings.detect(&pattern) # => "/foo/bar"

@return [Proc] proc wrapping {#===}

# File lib/mustermann/pattern.rb, line 345
def to_proc
  @to_proc ||= method(:===).to_proc
end
to_s() click to toggle source

@return [String] the string representation of the pattern

# File lib/mustermann/pattern.rb, line 81
def to_s
  @string.dup
end
to_templates() click to toggle source

@note This method is only implemented by certain subclasses.

Generates a list of URI template strings representing the pattern.

Note that this transformation is lossy and the strings matching these templates might not match the pattern (and vice versa).

This comes in quite handy since URI templates are not made for pattern matching. That way you can easily use a more precise template syntax and have it automatically generate hypermedia links for you.

@example generating templates

Mustermann.new("/:name").to_templates                   # => ["/{name}"]
Mustermann.new("/:foo(@:bar)?/*baz").to_templates       # => ["/{foo}@{bar}/{+baz}", "/{foo}/{+baz}"]
Mustermann.new("/{name}", type: :template).to_templates # => ["/{name}"]

@example generating templates from composite patterns

pattern  = Mustermann.new('/:name')
pattern |= Mustermann.new('/{name}', type: :template)
pattern |= Mustermann.new('/example/*nested')
pattern.to_templates # => ["/{name}", "/example/{+nested}"]

Template generation is supported by almost all patterns (notable exceptions are {Mustermann::Shell}, {Mustermann::Regular} and {Mustermann::Simple}). Union {Mustermann::Composite} patterns (with the | operator) support template generation if all patterns they are composed of also support it.

@example Checking if a pattern supports expanding

if pattern.respond_to? :to_templates
  pattern.to_templates
else
  warn "does not support template generation"
end

@return [Array<String>] list of URI templates

# File lib/mustermann/pattern.rb, line 278
def to_templates
  raise NotImplementedError, "template generation not supported by #{self.class}"
end
|(other) click to toggle source

@overload |(other)

Creates a pattern that matches any string matching either one of the patterns.
If a string is supplied, it is treated as an identity pattern.

@example
  pattern = Mustermann.new('/foo/:name') | Mustermann.new('/:first/:second')
  pattern === '/foo/bar' # => true
  pattern === '/fox/bar' # => true
  pattern === '/foo'     # => false

@overload &(other)

Creates a pattern that matches any string matching both of the patterns.
If a string is supplied, it is treated as an identity pattern.

@example
  pattern = Mustermann.new('/foo/:name') & Mustermann.new('/:first/:second')
  pattern === '/foo/bar' # => true
  pattern === '/fox/bar' # => false
  pattern === '/foo'     # => false

@overload ^(other)

Creates a pattern that matches any string matching exactly one of the patterns.
If a string is supplied, it is treated as an identity pattern.

@example
  pattern = Mustermann.new('/foo/:name') ^ Mustermann.new('/:first/:second')
  pattern === '/foo/bar' # => false
  pattern === '/fox/bar' # => true
  pattern === '/foo'     # => false

@param [Mustermann::Pattern, String] other the other pattern @return [Mustermann::Pattern] a composite pattern

# File lib/mustermann/pattern.rb, line 314
def |(other)
  Mustermann::Composite.new(self, other, operator: __callee__, type: :identity)
end
Also aliased as: &, ^

Private Instance Methods

map_param(key, value) click to toggle source

@!visibility private

# File lib/mustermann/pattern.rb, line 376
def map_param(key, value)
  unescape(value, true)
end
respond_to_special?(method) click to toggle source

@!visibility private @return [Boolean] @see respond_to?

# File lib/mustermann/pattern.rb, line 360
def respond_to_special?(method)
  method(method).owner != Mustermann::Pattern
end
unescape(string, decode = uri_decode) click to toggle source

@!visibility private

# File lib/mustermann/pattern.rb, line 381
def unescape(string, decode = uri_decode)
  return string unless decode and string
  @@uri.unescape(string)
end