Class: Mustermann::Pattern Abstract

Inherits:
Object
  • Object
show all
Includes:
Mustermann
Defined in:
mustermann/lib/mustermann/pattern.rb

Overview

This class is abstract.

Superclass for all pattern implementations.

Direct Known Subclasses

Composite, Identity, RegexpBased, Shell

Constant Summary

Constants included from Mustermann

CompileError, DEFAULT_TYPE, Error, ExpandError, ParseError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Mustermann

[]

Constructor Details

#initialize(string, **options) ⇒ Pattern

Returns a new instance of Pattern

Parameters:

  • string (String)

    the string representation of the pattern

  • options (Hash)

    options for fine-tuning the pattern behavior

Raises:

See Also:


75
76
77
78
79
# File 'mustermann/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

Instance Attribute Details

#uri_decodeObject (readonly)

Returns the value of attribute uri_decode


63
64
65
# File 'mustermann/lib/mustermann/pattern.rb', line 63

def uri_decode
  @uri_decode
end

Class Method Details

.new(string, **options) ⇒ Mustermann::Pattern

Returns a new instance of Mustermann::Pattern

Parameters:

  • string (String)

    the string representation of the pattern

  • options (Hash)

    options for fine-tuning the pattern behavior

Returns:

Raises:

  • (ArgumentError)

    if some option is not supported

  • (Mustermann::Error)

    if the pattern can't be generated from the string

See Also:


50
51
52
53
54
55
56
57
58
59
60
# File 'mustermann/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

.supported?(option, **options) ⇒ Boolean

Returns Whether or not option is supported.

Parameters:

  • option (Symbol)

    The option to check.

Returns:

  • (Boolean)

    Whether or not option is supported.


40
41
42
# File 'mustermann/lib/mustermann/pattern.rb', line 40

def self.supported?(option, **options)
  supported_options.include? option
end

.supported_optionsArray<Symbol> .supported_options(*list) ⇒ Array<Symbol>

List of supported options.

Overloads:

  • .supported_optionsArray<Symbol>

    Returns list of supported options

    Returns:

    • (Array<Symbol>)

      list of supported options

  • .supported_options(*list) ⇒ Array<Symbol>

    This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

    Adds options to the list.

    Parameters:

    • *list (Symbol)

      adds options to the list of supported options

    Returns:

    • (Array<Symbol>)

      list of supported options


24
25
26
27
28
29
# File 'mustermann/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

Instance Method Details

#+(other) ⇒ Mustermann::Pattern

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.

Examples:

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

Parameters:

Returns:


336
337
338
# File 'mustermann/lib/mustermann/pattern.rb', line 336

def +(other)
   Concat.new(self, other, type: :identity)
end

#==(other) ⇒ true, false

Two patterns are considered equal if they are of the same type, have the same pattern string and the same options.

Returns:

  • (true, false)

119
120
121
# File 'mustermann/lib/mustermann/pattern.rb', line 119

def ==(other)
  other.class == self.class and other.to_s == @string and other.options == options
end

#===(string) ⇒ Boolean

Note:

Needs to be overridden by subclass.

Returns Whether or not the pattern matches the given string

Parameters:

  • string (String)

    The string to match against

Returns:

  • (Boolean)

    Whether or not the pattern matches the given string

Raises:

  • (NotImplementedError)

See Also:


106
107
108
# File 'mustermann/lib/mustermann/pattern.rb', line 106

def ===(string)
  raise NotImplementedError, 'subclass responsibility'
end

#=~(string) ⇒ Integer?

Returns nil if pattern does not match the string, zero if it does.

Parameters:

  • string (String)

    The string to match against

Returns:

  • (Integer, nil)

    nil if pattern does not match the string, zero if it does.

See Also:


98
99
100
# File 'mustermann/lib/mustermann/pattern.rb', line 98

def =~(string)
  0 if self === string
end

#eql?(other) ⇒ true, false

Two patterns are considered equal if they are of the same type, have the same pattern string and the same options.

Returns:

  • (true, false)

126
127
128
# File 'mustermann/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 = {}) ⇒ String

Note:

This method is only implemented by certain subclasses.

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

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

Examples:

Expanding a pattern

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

Checking if a pattern supports expanding

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

Parameters:

  • behavior (Symbol) (defaults to: nil)

    What to do with additional key/value pairs not present in the values hash. Possible options: :raise, :ignore, :append.

  • values (Hash{Symbol: #to_s, Array<#to_s>}) (defaults to: {})

    Values to use for expansion.

Returns:

  • (String)

    expanded string

Raises:

  • (NotImplementedError)

    raised if expand is not supported.

  • (Mustermann::ExpandError)

    raised if a value is missing or unknown

See Also:


240
241
242
# File 'mustermann/lib/mustermann/pattern.rb', line 240

def expand(behavior = nil, values = {})
  raise NotImplementedError, "expanding not supported by #{self.class}"
end

#hashInteger

Used by Ruby internally for hashing.

Returns:

  • (Integer)

    same has value for patterns that are equal


112
113
114
# File 'mustermann/lib/mustermann/pattern.rb', line 112

def hash
  self.class.hash | @string.hash | options.hash
end

#match(string) ⇒ MatchData, ...

Returns MatchData or similar object if the pattern matches.

Parameters:

  • string (String)

    The string to match against

Returns:

See Also:


91
92
93
# File 'mustermann/lib/mustermann/pattern.rb', line 91

def match(string)
  SimpleMatch.new(string) if self === string
end

#named_capturesHash{String: Array<Integer>}

Returns capture names mapped to capture index.

Returns:

  • (Hash{String: Array<Integer>})

    capture names mapped to capture index.

See Also:


192
193
194
# File 'mustermann/lib/mustermann/pattern.rb', line 192

def named_captures
  {}
end

#namesArray<String>

Returns capture names.

Returns:

  • (Array<String>)

    capture names.

See Also:


198
199
200
# File 'mustermann/lib/mustermann/pattern.rb', line 198

def names
  []
end

#params(string = nil, captures: nil, offset: 0) ⇒ Hash{String: String, Array<String>}?

Returns Sinatra style params if pattern matches.

Parameters:

  • string (String) (defaults to: nil)

    the string to match against

Returns:

  • (Hash{String: String, Array<String>}, nil)

    Sinatra style params if pattern matches.


204
205
206
207
208
209
210
211
212
213
# File 'mustermann/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) ⇒ String?

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

Examples:

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

Parameters:

  • string (String)

    The string to match against

Returns:

  • (String, nil)

    matched subsctring


153
154
155
156
# File 'mustermann/lib/mustermann/pattern.rb', line 153

def peek(string)
  size = peek_size(string)
  string[0, size] if size
end

#peek_match(string) ⇒ MatchData, ...

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.

Examples:

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

Parameters:

  • string (String)

    The string to match against

Returns:

See Also:


168
169
170
171
# File 'mustermann/lib/mustermann/pattern.rb', line 168

def peek_match(string)
  matched = peek(string)
  match(matched) if matched
end

#peek_params(string) ⇒ Array<Hash, Integer>?

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.

Examples:

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

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

Parameters:

  • string (String)

    The string to match against

Returns:

  • (Array<Hash, Integer>, nil)

    Array with params hash and length of substing if matched, nil otherwise


185
186
187
188
# File 'mustermann/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) ⇒ Integer?

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.

Examples:

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

Parameters:

  • string (String)

    The string to match against

Returns:

  • (Integer, nil)

    the number of characters that match


139
140
141
142
# File 'mustermann/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

#to_procProc

Returns proc wrapping #===

Examples:

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

Returns:

  • (Proc)

    proc wrapping #===


346
347
348
# File 'mustermann/lib/mustermann/pattern.rb', line 346

def to_proc
  @to_proc ||= method(:===).to_proc
end

#to_sString

Returns the string representation of the pattern

Returns:

  • (String)

    the string representation of the pattern


82
83
84
# File 'mustermann/lib/mustermann/pattern.rb', line 82

def to_s
  @string.dup
end

#to_templatesArray<String>

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.

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

Examples:

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}"]

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}"]

Checking if a pattern supports expanding

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

Returns:

  • (Array<String>)

    list of URI templates

Raises:

  • (NotImplementedError)

279
280
281
# File 'mustermann/lib/mustermann/pattern.rb', line 279

def to_templates
  raise NotImplementedError, "template generation not supported by #{self.class}"
end

#|(other) ⇒ Mustermann::Pattern #&(other) ⇒ Mustermann::Pattern #^(other) ⇒ Mustermann::Pattern Also known as: &, ^

Returns a composite pattern

Overloads:

  • #|(other) ⇒ Mustermann::Pattern

    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.

    Examples:

    pattern = Mustermann.new('/foo/:name') | Mustermann.new('/:first/:second')
    pattern === '/foo/bar' # => true
    pattern === '/fox/bar' # => true
    pattern === '/foo'     # => false
  • #&(other) ⇒ Mustermann::Pattern

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

    Examples:

    pattern = Mustermann.new('/foo/:name') & Mustermann.new('/:first/:second')
    pattern === '/foo/bar' # => true
    pattern === '/fox/bar' # => false
    pattern === '/foo'     # => false
  • #^(other) ⇒ Mustermann::Pattern

    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.

    Examples:

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

Parameters:

Returns:


315
316
317
# File 'mustermann/lib/mustermann/pattern.rb', line 315

def |(other)
  Mustermann::Composite.new(self, other, operator: __callee__, type: :identity)
end