mustermann 4.0.0.alpha3 → 4.0.0.alpha4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6a0beea1e6d365356c6444910ec10200dcaf98f1968e88ac2fe91e0a7b23b93
4
- data.tar.gz: 1be43d10761af60bbd4cdd5d087214073984825b05f397de8d1475a7f35686d6
3
+ metadata.gz: c45d32932b0745c20363ab0fb0084eb861754482e3c47ed68684cf245f10100f
4
+ data.tar.gz: edb3fbe54217319a9a059b2a57c74b4e8f7d84cc4721fdb9a78bbf5ed6afa113
5
5
  SHA512:
6
- metadata.gz: 710a7afa55bdbd0f6fb17845458572ea9adaf8149674c9b1e54ddba333382be9e05136d37d7680595bead97b21bee76334d129cbf96c320b0f975688b20ad29c
7
- data.tar.gz: 64e15b87da6f731fce7d0e50eb4ee13511c175b979765d9a233a066c026026c0c82300e22b8492db8220d2f89995cadb471c78ac00da60ea8be360a22b8290be
6
+ metadata.gz: 39c217af2266a0dc8f647dafd1c5772cf10e6f921a9d8ccabdcd29357a1a3dc9991065221f12eacc473af1ca183ac8bbd14dc8eae2d83834b21c81ac55e8ffc4
7
+ data.tar.gz: f9a2c84244115819b62737f222b3c181ca5c5192e83243387473b3bf07666be8e019a78e591861e55a93579c28ab9e0f63725feddc0b6a61ff01f6f736c29735
data/README.md CHANGED
@@ -455,27 +455,6 @@ set.expand(id: '5') # => '/users/5' (first applicable pattern)
455
455
  set.expand(:posts, id: '5') # => '/posts/5' (patterns for a specific value)
456
456
  ```
457
457
 
458
- <a name="-duck-typing"></a>
459
- ## Duck Typing
460
-
461
- <a name="-duck-typing-to-pattern"></a>
462
- ### `to_pattern`
463
-
464
- All methods converting string input to pattern objects will also accept any arbitrary object that implements `to_pattern`:
465
-
466
- ``` ruby
467
- require 'mustermann'
468
-
469
- class MyObject
470
- def to_pattern(**options)
471
- Mustermann.new("/foo", **options)
472
- end
473
- end
474
-
475
- object = MyObject.new
476
- Mustermann.new(object, type: :rails) # => #<Mustermann::Rails:"/foo">
477
- ```
478
-
479
458
  ### Match order
480
459
 
481
460
  A set can match patterns and values in loose or strict insertion order.
@@ -536,6 +515,27 @@ set.match("/static").value # => :first
536
515
  set.match_all("/static").map(&:value) # => [:first, :second, :third]
537
516
  ```
538
517
 
518
+ <a name="-duck-typing"></a>
519
+ ## Duck Typing
520
+
521
+ <a name="-duck-typing-to-pattern"></a>
522
+ ### `to_pattern`
523
+
524
+ All methods converting string input to pattern objects will also accept any arbitrary object that implements `to_pattern`:
525
+
526
+ ``` ruby
527
+ require 'mustermann'
528
+
529
+ class MyObject
530
+ def to_pattern(**options)
531
+ Mustermann.new("/foo", **options)
532
+ end
533
+ end
534
+
535
+ object = MyObject.new
536
+ Mustermann.new(object, type: :rails) # => #<Mustermann::Rails:"/foo">
537
+ ```
538
+
539
539
  <a name="-duck-typing-respond-to"></a>
540
540
  ### `respond_to?`
541
541
 
@@ -34,7 +34,7 @@ module Mustermann
34
34
  return unless match = @regexp.match(string)
35
35
  params = match.named_captures
36
36
  params.transform_values! { |v| unescape(v) } if string.include?('%')
37
- Match.new(self, string, params)
37
+ Match.new(self, match, params:)
38
38
  end
39
39
 
40
40
  # Public override: fast path for simple patterns, falls through to super otherwise.
@@ -75,16 +75,21 @@ module Mustermann
75
75
 
76
76
  # @see Mustermann::Pattern#peek_match
77
77
  def peek_match(string)
78
- substring = string
79
- params = {}
78
+ post_match = string
79
+ params = {}
80
+ captures = []
81
+ named_captures = {}
80
82
 
81
83
  patterns.each do |pattern|
82
- return unless part = pattern.peek_match(substring)
84
+ return unless part = pattern.peek_match(post_match)
83
85
  params.merge!(part.params)
84
- substring = substring[part.to_s.size..-1]
86
+ named_captures.merge!(part.named_captures)
87
+ captures.concat(part.captures)
88
+ post_match = post_match[part.to_s.size..-1]
85
89
  end
86
90
 
87
- Match.new(self, string[0, string.size - substring.size], params, post_match: substring)
91
+ matched = string[0, string.size - post_match.size]
92
+ Match.new(self, string, matched:, params:, captures:, named_captures:, post_match:)
88
93
  end
89
94
 
90
95
  # @see Mustermann::Pattern#peek_params
@@ -1,39 +1,133 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mustermann
4
+ # The return value of {Mustermann::Pattern#match}, {Mustermann::Pattern#peek_match}, {Mustermann::Set#match}, and similar methods.
5
+ # Mimics large parts of the MatchData API, but also provides access to the pattern and params hash.
4
6
  class Match
5
- attr_reader :pattern, :string, :params, :post_match, :pre_match
6
-
7
- def initialize(pattern, string, params = {}, post_match: '', pre_match: '')
8
- @pattern = pattern
9
- @string = string.freeze
10
- @params = params.freeze
11
- @post_match = post_match.freeze
12
- @pre_match = pre_match.freeze
7
+ # @return [Mustermann::Pattern] the pattern that produced the match
8
+ attr_reader :pattern
9
+
10
+ # @return [String] the string that was matched
11
+ attr_reader :string
12
+
13
+ # @return [Hash] the params hash
14
+ attr_reader :params
15
+
16
+ # @return [Array] the captures array
17
+ attr_reader :captures
18
+
19
+ # @return [Hash] the named captures hash, usually identical to {#params}
20
+ attr_reader :named_captures
21
+
22
+ # @return [String] the post match string
23
+ attr_reader :post_match
24
+
25
+ # @return [String] the pre match string
26
+ attr_reader :pre_match
27
+
28
+ # @return [Regexp, nil] the regular expression that produced the match, if available
29
+ attr_reader :regexp
30
+
31
+ # @overload initialize(pattern, string, **options)
32
+ # @param pattern [Mustermann::Pattern] the pattern that produced the match
33
+ # @param string [String] the string that was matched
34
+ #
35
+ # @overload initialize(match, **options)
36
+ # @param match [Mustermann::Match] the match to copy pattern and string from
37
+ #
38
+ # @overload initialize(pattern, match, **options)
39
+ # @param match [Mustermann::Match, MatchData] the match to copy string from
40
+ #
41
+ # @option options [Array] :captures the captures array
42
+ # @option options [Hash] :named_captures the named captures hash
43
+ # @option options [String] :matched the matched substring (defaults to string for full matches)
44
+ # @option options [Hash] :params the params hash
45
+ # @option options [Regexp] :regexp the regular expression that produced the match
46
+ # @option options [String] :post_match the post match string
47
+ # @option options [String] :pre_match the pre match string
48
+ def initialize(pattern_or_match, string_or_match = nil, matched: nil, params: nil, post_match: nil, pre_match: nil, captures: nil, named_captures: nil, regexp: nil)
49
+ case pattern_or_match
50
+ when Mustermann::Match, MatchData then match = pattern_or_match
51
+ when Mustermann::Pattern then pattern = pattern_or_match
52
+ else raise ArgumentError, "first argument must be a Mustermann::Pattern or a MatchData, not #{pattern_or_match.class}"
53
+ end
54
+
55
+ case string_or_match
56
+ when Mustermann::Match, MatchData then match ||= string_or_match
57
+ when String then string = string_or_match
58
+ when nil # ignore
59
+ else raise ArgumentError, "second argument must be a String or a MatchData, not #{string_or_match.class}"
60
+ end
61
+
62
+ @pattern = pattern || match&.pattern
63
+ @string = string || match&.string || ''
64
+ @params = params || match&.params || {}
65
+ @post_match = post_match || match&.post_match || ''
66
+ @pre_match = pre_match || match&.pre_match || ''
67
+ @captures = captures || match&.captures || @params.values
68
+ @named_captures = named_captures || match&.named_captures || @params
69
+ @matched = matched || match&.to_s || @string
70
+
71
+ unless @regexp = regexp
72
+ @regexp = match.regexp if match.respond_to?(:regexp)
73
+ @regexp ||= pattern.respond_to?(:regexp) ? pattern.regexp : nil
74
+ end
13
75
  end
14
76
 
15
- def [](key)
77
+ # @overload [](key)
78
+ # Access params by key.
79
+ # @param key [String, Symbol] the key to access
80
+ # @return the value of the param, or nil if not found
81
+ #
82
+ # @overload [](index)
83
+ # Access captures by index.
84
+ # @param index [Integer] the index to access
85
+ # @return the value of the capture, or nil if not found
86
+ #
87
+ # @overload [](start, length)
88
+ # Access multiple captures by index and length.
89
+ # @param start [Integer] the starting index to access
90
+ # @param length [Integer] the number of captures to access
91
+ # @return [Array] the values of the captures
92
+ #
93
+ # @overload [](range)
94
+ # Access multiple captures by range.
95
+ # @param range [Range] the range of indices to access
96
+ # @return [Array] the values of the captures
97
+ def [](key, length = nil)
16
98
  case key
17
99
  when String then params[key]
18
100
  when Symbol then params[key.to_s]
19
- else raise ArgumentError, "key must be a String or Symbol, not #{key.class}"
101
+ when Integer then length ? captures[key, length] : captures[key]
102
+ when Range then captures[key]
103
+ else raise ArgumentError, "key must be a String, Symbol, Integer, or Range, not #{key.class}"
20
104
  end
21
105
  end
22
-
106
+
107
+ # Deconstructs the match into a hash of the given keys. Useful for pattern matching.
108
+ # @param keys [Array] the keys to deconstruct
109
+ # @return [Hash] a hash of the given keys and their corresponding values
110
+ # @see https://docs.ruby-lang.org/en/4.0/syntax/pattern_matching_rdoc.html
23
111
  def deconstruct_keys(keys) = keys.to_h { |key| [key, self[key]] }
24
112
 
113
+ # @see Object#hash
25
114
  def hash = pattern.hash ^ string.hash ^ params.hash
26
115
 
116
+ # @see Object#eql?
27
117
  def eql?(other)
28
118
  return false unless other.is_a? self.class
29
119
  pattern == other.pattern && string == other.string && params == other.params
30
120
  end
31
121
 
122
+ # Returns the values of the given keys as an array.
123
+ # @params keys [Array<Symbol, String>] the keys to access
124
+ # @return [Array] the values of the given keys
32
125
  def values_at(*keys) = keys.map { |key| self[key] }
33
126
 
127
+ # @return [String] the matched substring (like MatchData#to_s)
128
+ def to_s = @matched
129
+
34
130
  alias == eql?
35
- alias to_s string
36
131
  alias to_h params
37
-
38
132
  end
39
133
  end
@@ -165,7 +165,7 @@ module Mustermann
165
165
  # @see #peek_params
166
166
  def peek_match(string)
167
167
  matched = peek(string)
168
- Match.new(self, matched, {}, post_match: string[matched.size..-1]) if matched
168
+ Match.new(self, string, matched:, params: {}, post_match: string[matched.size..-1]) if matched
169
169
  end
170
170
 
171
171
  # Tries to match the pattern against the beginning of the string (as opposed to the full string).
@@ -182,7 +182,7 @@ module Mustermann
182
182
  # @return [Array<Hash, Integer>, nil] Array with params hash and length of substing if matched, nil otherwise
183
183
  def peek_params(string)
184
184
  match = peek_match(string)
185
- match ? [match.params, match.string.size] : nil
185
+ match ? [match.params, match.to_s.size] : nil
186
186
  end
187
187
 
188
188
  # @param [String] string the string to match against
@@ -35,10 +35,10 @@ module Mustermann
35
35
  # @see (see Mustermann::Pattern#peek_match)
36
36
  def peek_match(string) = build_match(@peek_regexp.match(string))
37
37
 
38
- def match(string)
39
- return unless match = @regexp.match(string)
40
- Match.new(self, string, build_params(match))
41
- end
38
+ # @param (see Mustermann::Pattern#match)
39
+ # @return (see Mustermann::Pattern#match)
40
+ # @see (see Mustermann::Pattern#match)
41
+ def match(string) = build_match(@regexp.match(string))
42
42
 
43
43
  extend Forwardable
44
44
  def_delegators :regexp, :===, :=~, :names
@@ -47,7 +47,7 @@ module Mustermann
47
47
 
48
48
  def build_match(match)
49
49
  return unless match
50
- Match.new(self, match.to_s, build_params(match), post_match: match.post_match, pre_match: match.pre_match)
50
+ Match.new(self, match, params: build_params(match))
51
51
  end
52
52
 
53
53
  def build_params(match)
@@ -17,9 +17,9 @@ module Mustermann
17
17
  result = [] if all
18
18
  @patterns.each do |pattern|
19
19
  next unless match = peek ? pattern.peek_match(string) : pattern.match(string)
20
- return Match.new(match:, value: @set.values_for_pattern(pattern)&.first) unless all
20
+ return Match.new(match, value: @set.values_for_pattern(pattern)&.first) unless all
21
21
  values = @set.values_for_pattern(pattern) || [nil]
22
- values.each { |value| result << Match.new(match:, value:) }
22
+ values.each { |value| result << Match.new(match, value:) }
23
23
  end
24
24
  result
25
25
  end
@@ -3,21 +3,21 @@ require 'mustermann/match'
3
3
 
4
4
  module Mustermann
5
5
  class Set
6
+
7
+ # Subclass of {Mustermann::Match} that also includes the value associated with the pattern that produced the match.
6
8
  class Match < Mustermann::Match
9
+ # @return the value associated with the pattern that produced the match, if any
7
10
  attr_reader :value
8
11
 
9
- def initialize(pattern = nil, string = nil, params = {}, value: nil, match: nil, post_match: '', pre_match: '')
12
+ # (see Mustermann::Match#initialize)
13
+ # @option options [Object] :value the value associated with the pattern that produced the match, if any
14
+ def initialize(*, value: nil, **)
10
15
  @value = value
11
- if match
12
- @pattern = match.pattern
13
- @string = match.string
14
- @params = match.params
15
- @post_match = match.post_match
16
- @pre_match = match.pre_match
17
- else
18
- super(pattern, string, params, post_match:, pre_match:)
19
- end
16
+ super(*, **)
20
17
  end
18
+
19
+ # @see Mustermann::Match#eql?
20
+ def eql?(other) = super && value == other.value
21
21
  end
22
22
  end
23
23
  end
@@ -148,7 +148,7 @@ module Mustermann
148
148
  end
149
149
 
150
150
  if peek
151
- matches = build_matches(string[0, position], params, all:, post_match: string[position..], pre_match: '')
151
+ matches = build_matches(string, params, all:, matched_length: position, post_match: string[position..], pre_match: '')
152
152
  return matches unless all
153
153
  result.concat(matches)
154
154
  end
@@ -158,17 +158,18 @@ module Mustermann
158
158
 
159
159
  NIL_VALUES = [nil].freeze
160
160
 
161
- def build_matches(string, params, all: false, post_match: '', pre_match: '')
161
+ def build_matches(string, params, all: false, matched_length: string.size, post_match: '', pre_match: '')
162
162
  result = [] if all
163
+ matched = string[0, matched_length]
163
164
 
164
165
  @patterns.each do |pattern|
165
- next if pattern.except_regexp&.match?(string)
166
+ next if pattern.except_regexp&.match?(matched)
166
167
 
167
168
  pattern_params = build_pattern_params(pattern, params)
168
169
 
169
170
  values = @set.values_for_pattern(pattern) || NIL_VALUES
170
171
  values.each do |value|
171
- match = Set::Match.new(pattern, string, pattern_params, value:, post_match:, pre_match:)
172
+ match = Set::Match.new(pattern, string, matched:, params: pattern_params, value:, post_match:, pre_match:)
172
173
  return match unless all
173
174
  result << match
174
175
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Mustermann
3
- VERSION ||= '4.0.0.alpha3'
3
+ VERSION ||= '4.0.0.alpha4'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mustermann
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0.alpha3
4
+ version: 4.0.0.alpha4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Haase