Class: Mustermann::Pattern Abstract

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

Overview

This class is abstract.

Superclass for all pattern implementations.

Direct Known Subclasses

Composite, Identity, RegexpBased, Shell

Constant Summary collapse

PATTERN_METHODS =
%w[expand to_templates].map(&:to_sym)

Constants included from Mustermann

DEFAULT_TYPE, VERSION

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Mustermann

[]

Constructor Details

#initialize(string, **options) ⇒ Pattern

Returns a new instance of Pattern

Raises:

  • (Mustermann::Error)

    if the pattern can't be generated from the string

See Also:


70
71
72
73
74
# File 'lib/mustermann/pattern.rb', line 70

def initialize(string, options = {})
  uri_decode = options.fetch(:uri_decode, true)
  @uri_decode = uri_decode
  @string     = string.to_s.dup
end

Class Method Details

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

Returns a new instance of Mustermann::Pattern

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 'lib/mustermann/pattern.rb', line 50

def self.new(string, options = {})
  ignore_unknown_options = options.fetch(:ignore_unknown_options, false)
  options.delete(:ignore_unknown_options)
  unless ignore_unknown_options
    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) }
end

.supported?(option, options = {}) ⇒ Boolean


40
41
42
# File '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_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.


24
25
26
27
28
29
# 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

Instance Method Details

#&(other) ⇒ Object


297
298
299
# File 'lib/mustermann/pattern.rb', line 297

def &(other)
  Mustermann.new(self, other, :operator => :&, :type => :identity)
end

#===(string) ⇒ Boolean

Note:

Needs to be overridden by subclass.

Returns Whether or not the pattern matches the given string

Raises:

  • (NotImplementedError)

See Also:


101
102
103
# File 'lib/mustermann/pattern.rb', line 101

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

#=~(string) ⇒ Integer?

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

See Also:


93
94
95
# File 'lib/mustermann/pattern.rb', line 93

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

#^(other) ⇒ Object


301
302
303
# File 'lib/mustermann/pattern.rb', line 301

def ^(other)
  Mustermann.new(self, other, :operator => :^, :type => :identity)
end

#expand(behavior = nil, values = {}) ⇒ String

Note:

This method is only implemented by certain subclasses.

Expanding is supported by almost all patterns (notable execptions 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

Raises:

  • (NotImplementedError)

    raised if expand is not supported.

  • (Mustermann::ExpandError)

    raised if a value is missing or unknown

See Also:


218
219
220
# File 'lib/mustermann/pattern.rb', line 218

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

#match(string) ⇒ MatchData, ...

Returns MatchData or similar object if the pattern matches.


86
87
88
# File 'lib/mustermann/pattern.rb', line 86

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

#named_capturesHash{String: Array<Integer>}

Returns capture names mapped to capture index.


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

def named_captures
  {}
end

#namesArray<String>

Returns capture names.

See Also:


173
174
175
# File 'lib/mustermann/pattern.rb', line 173

def names
  []
end

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


179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/mustermann/pattern.rb', line 179

def params(string = nil, options = {})
  options, string = string, nil if string.is_a?(Hash)
  captures = options.fetch(:captures, nil)
  offset   = options.fetch(: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"

128
129
130
131
# File 'lib/mustermann/pattern.rb', line 128

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

See Also:


143
144
145
146
# File 'lib/mustermann/pattern.rb', line 143

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!

160
161
162
163
# File 'lib/mustermann/pattern.rb', line 160

def peek_params(string)
  match = peek_match(string)
  [params(nil, :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

114
115
116
117
# File 'lib/mustermann/pattern.rb', line 114

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"

311
312
313
# File 'lib/mustermann/pattern.rb', line 311

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

#to_sString


77
78
79
# File 'lib/mustermann/pattern.rb', line 77

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 Sinatra, Rails, Template and Identity patterns. 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

Raises:

  • (NotImplementedError)

257
258
259
# File 'lib/mustermann/pattern.rb', line 257

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

#|(other) ⇒ Mustermann::Pattern #&(other) ⇒ Mustermann::Pattern #^(other) ⇒ Mustermann::Pattern

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
    

293
294
295
# File 'lib/mustermann/pattern.rb', line 293

def |(other)
  Mustermann.new(self, other, :operator => :|, :type => :identity)
end