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 50
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 75
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 34
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 40
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 24
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 336
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 119
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 106
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 98
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 391
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 126
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 240
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 112
def hash
  self.class.hash | @string.hash | options.hash
end
inspect() click to toggle source

@!visibility private

# File lib/mustermann/pattern.rb, line 366
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 91
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 192
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 198
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 204
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 153
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 168
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 185
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 139
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 353
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 371
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 346
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 82
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 279
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 315
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 377
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 361
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 382
def unescape(string, decode = uri_decode)
  return string unless decode and string
  @@uri.unescape(string)
end