Class: Mustermann::Set
- Inherits:
-
Object
- Object
- Mustermann::Set
- Defined in:
- lib/mustermann/set.rb,
lib/mustermann/set/trie.rb,
lib/mustermann/set/cache.rb,
lib/mustermann/set/match.rb,
lib/mustermann/set/linear.rb,
lib/mustermann/set/strict_order.rb
Overview
A collection of patterns that can be matched against strings efficiently.
Each pattern in the set may be associated with one or more arbitrary values, such as handler objects or route actions. A single #match call returns a Match that provides both the captured parameters and the associated value for the matched pattern. When the set contains many patterns, an internal trie (prefix tree) is used to dispatch requests in sub-linear time.
Defined Under Namespace
Classes: Match
Instance Attribute Summary collapse
-
#options ⇒ Hash
readonly
Pattern options forwarded to new when patterns are created from strings.
Instance Method Summary collapse
-
#[](pattern_or_string) ⇒ Object?
Looks up a value by string or retrieves the first value for a known pattern object.
-
#add(pattern, *values) ⇒ self
(also: #[]=)
Adds a pattern to the set, optionally associated with one or more values.
-
#expand(value = self, behavior = nil, values = nil) ⇒ String
Generates a string from a parameter hash using the patterns in the set.
-
#expander(value = self) ⇒ Mustermann::Expander
Returns an Expander that can generate strings from parameter hashes.
-
#has_value?(value) ⇒ Boolean
Whether the set contains any pattern associated with the given value.
-
#initialize(*mapping, additional_values: :raise, use_trie: 50, use_cache: true, strict_order: false, **options, &block) ⇒ Set
constructor
Creates a new set, optionally pre-populated with patterns.
-
#match(string) ⇒ Set::Match?
Matches the string against all patterns in the set and returns the first match.
-
#match_all(string) ⇒ Array<Set::Match>
Matches the string against all patterns and returns every match, one per (pattern, value) pair, in insertion order.
-
#merge(mapping) ⇒ Set
Returns a new set that includes all patterns from the receiver plus those from
mapping. -
#optimize! ⇒ Object
Runs trie optimizations pro-actively and explicitly rather than at match time.
-
#patterns ⇒ Array<Pattern>
Returns all patterns that have been added to the set, in insertion order.
-
#peek_match(string) ⇒ Set::Match?
Matches the beginning of the string against all patterns and returns the first prefix match.
-
#peek_match_all(string) ⇒ Array<Set::Match>
Matches the beginning of the string against all patterns and returns every prefix match, one per (pattern, value) pair.
-
#strict_order? ⇒ Boolean
A set can match patterns and values in loose or strict insertion order.
-
#update(mapping) ⇒ self
(also: #merge!)
Adds all patterns from
mappingto the set in place and returnsself. -
#use_cache? ⇒ Boolean
Whether caching is enabled.
-
#use_trie? ⇒ Boolean
Whether trie optimization is enabled.
Constructor Details
#initialize(*mapping, additional_values: :raise, use_trie: 50, use_cache: true, strict_order: false, **options, &block) ⇒ Set
Creates a new set, optionally pre-populated with patterns.
Patterns can be supplied as a Hash (pattern → value), a plain String or Pattern, an Array of any of these, or an existing Mustermann::Set. The same forms are accepted by #update and #add.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/mustermann/set.rb', line 85 def initialize(*mapping, additional_values: :raise, use_trie: 50, use_cache: true, strict_order: false, **, &block) raise ArgumentError, "Illegal value %p for additional_values" % additional_values unless Expander::ADDITIONAL_VALUES.include? additional_values raise ArgumentError, "Illegal value %p for use_trie" % use_trie unless [true, false].include?(use_trie) or use_trie.is_a? Integer @use_trie = use_trie @use_cache = use_cache @matcher = nil @mapping = {} @reverse_mapping = {} @options = {} @expanders = {} @additional_values = additional_values @strict_order = strict_order .each do |key, value| if key.is_a? Symbol @options[key] = value else mapping << { key => value } end end update(mapping) block.arity == 0 ? update(yield) : yield(self) if block optimize! end |
Instance Attribute Details
#options ⇒ Hash (readonly)
Pattern options forwarded to Mustermann.new when patterns are created from strings.
42 43 44 |
# File 'lib/mustermann/set.rb', line 42 def @options end |
Instance Method Details
#[](pattern_or_string) ⇒ Object?
Looks up a value by string or retrieves the first value for a known pattern object.
When given a String, it is matched against the set and the associated value of the first matching pattern is returned. When given a Pattern, the first value registered for that exact pattern is returned without matching.
234 235 236 237 238 239 240 |
# File 'lib/mustermann/set.rb', line 234 def [](pattern_or_string) case pattern_or_string when String then match(pattern_or_string)&.value when Pattern then values_for_pattern(pattern_or_string)&.first else raise ArgumentError, "unsupported pattern type #{pattern_or_string.class}" end end |
#add(pattern, *values) ⇒ self Also known as: []=
Adds a pattern to the set, optionally associated with one or more values.
If the pattern is given as a String it will be compiled via Mustermann.new using the set’s own options. The pattern must be AST-based (Sinatra, Rails, and similar types). Plain regexp patterns are not supported.
Calling add more than once for the same pattern appends additional values without creating duplicates.
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/mustermann/set.rb', line 187 def add(pattern, *values) pattern = Mustermann.new(pattern, **) raise ArgumentError, "Non-AST patterns are not supported" unless pattern.respond_to? :to_ast if @mapping.key? pattern current = @mapping[pattern] else add_pattern(pattern) current = @mapping[pattern] = [] end values = [nil] if values.empty? values.each do |value| raise ArgumentError, "%p may not be used as a value" % value if Expander::ADDITIONAL_VALUES.include? value raise ArgumentError, "the set itself may not be used as value" if value == self next if current.include? value current << value @reverse_mapping[value] ||= [] @reverse_mapping[value] << pattern unless @reverse_mapping[value].include? pattern @expanders[value]&.add(pattern) @matcher.track(pattern, value) if strict_order? end self end |
#expand(value = self, behavior = nil, values = nil) ⇒ String
Generates a string from a parameter hash using the patterns in the set.
When called with just a parameter hash, the first pattern that can be fully expanded with those keys is used. Pass a value as the first argument to restrict expansion to the patterns associated with that value. You may also pass an additional_values behavior symbol (:raise, :ignore, or :append) as the first argument to override the set’s default behavior for that call.
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
# File 'lib/mustermann/set.rb', line 357 def (value = self, behavior = nil, values = nil) if Expander::ADDITIONAL_VALUES.include? value if behavior.is_a? Hash values = values ? values.merge(behavior) : behavior behavior = nil elsif behavior and behavior != value raise ArgumentError, "behavior specified multiple times" if behavior end behavior = value value = self elsif value.is_a? Hash and behavior.nil? and values.nil? values = value value = self unless @reverse_mapping.key? values end (value).(behavior || @additional_values, values || {}) end |
#expander(value = self) ⇒ Mustermann::Expander
Returns an Expander that can generate strings from parameter hashes.
When called without arguments (or with the set itself as the value) the expander covers all patterns in the set. Pass a specific value to get an expander limited to the patterns associated with that value.
324 325 326 327 328 329 |
# File 'lib/mustermann/set.rb', line 324 def (value = self) @expanders[value] ||= begin patterns = value == self ? @mapping.keys : @reverse_mapping[value] || [] Mustermann::Expander.new(patterns, additional_values: @additional_values, **) end end |
#has_value?(value) ⇒ Boolean
Returns whether the set contains any pattern associated with the given value.
375 |
# File 'lib/mustermann/set.rb', line 375 def has_value?(value) = @reverse_mapping[value]&.any? |
#match(string) ⇒ Set::Match?
Matches the string against all patterns in the set and returns the first match.
246 |
# File 'lib/mustermann/set.rb', line 246 def match(string) = @matcher&.match(string) |
#match_all(string) ⇒ Array<Set::Match>
Matches the string against all patterns and returns every match, one per (pattern, value) pair, in insertion order.
261 |
# File 'lib/mustermann/set.rb', line 261 def match_all(string) = @matcher&.match(string, all: true) |
#merge(mapping) ⇒ Set
Returns a new set that includes all patterns from the receiver plus those from mapping. The receiver is not modified.
276 |
# File 'lib/mustermann/set.rb', line 276 def merge(mapping) = dup.update(mapping) |
#optimize! ⇒ Object
Runs trie optimizations pro-actively and explicitly rather than at match time.
381 |
# File 'lib/mustermann/set.rb', line 381 def optimize! = @matcher&.optimize! |
#patterns ⇒ Array<Pattern>
Returns all patterns that have been added to the set, in insertion order.
313 |
# File 'lib/mustermann/set.rb', line 313 def patterns = @mapping.keys |
#peek_match(string) ⇒ Set::Match?
Matches the beginning of the string against all patterns and returns the first prefix match. The unmatched remainder of the string is available via Match#post_match.
254 |
# File 'lib/mustermann/set.rb', line 254 def peek_match(string) = @matcher&.match(string, peek: true) |
#peek_match_all(string) ⇒ Array<Set::Match>
Matches the beginning of the string against all patterns and returns every prefix match, one per (pattern, value) pair. The unmatched remainder is available as Match#post_match on each result.
269 |
# File 'lib/mustermann/set.rb', line 269 def peek_match_all(string) = @matcher&.match(string, all: true, peek: true) |
#strict_order? ⇒ Boolean
A set can match patterns and values in loose or strict insertion order.
You have the following guarantees without strict ordering:
-
Patterns with dynamic segments in the same position and equal static parts will always match in the order they were added.
-
Multiple values for the same pattern will retain their insertion order in regards to that pattern.
Trade-offs without strict ordering:
-
Static segments may be favored over dynamic segments. If you want to guarantee this behavior, enable trie-mode proactively.
-
When a pattern has multiple values, these will follow each other directly when using #match_all or #peek_match_all.
Strict ordering comes with both a performance overhead and marginally increased memory usage. How big the performance overhead is depends on the number of patterns that overlap in the strings they successfully match against. It does use Ruby’s built-in sorting, which on MRI is based on quicksort. The memory overhead grows linear with the number of pattern and value combinations, but is generally small compared to the memory used by the patterns and values themselves.
With strict ordering enabled, patterns and values are guaranteed to occur in insertion order.
162 |
# File 'lib/mustermann/set.rb', line 162 def strict_order? = @strict_order |
#update(mapping) ⇒ self Also known as: merge!
Adds all patterns from mapping to the set in place and returns self. Aliased as merge!.
Accepts the same argument forms as #initialize: a Hash, a String, a Pattern, an Array, or another Mustermann::Set.
298 299 300 301 302 303 304 305 306 307 |
# File 'lib/mustermann/set.rb', line 298 def update(mapping) case mapping when Set then mapping.mapping.each { |pattern, values| add(pattern, *values) } when Hash then mapping.each { |k, v| add(k, v) } when String, Pattern then add(mapping) when Array then mapping.each { |item| update(item) } else raise ArgumentError, "unsupported mapping type #{mapping.class}" end self end |
#use_cache? ⇒ Boolean
Returns whether caching is enabled.
165 |
# File 'lib/mustermann/set.rb', line 165 def use_cache? = @use_cache |
#use_trie? ⇒ Boolean
Returns whether trie optimization is enabled.
168 |
# File 'lib/mustermann/set.rb', line 168 def use_trie? = @use_trie == true |