mustermann 3.1.1 → 4.0.0.alpha

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.
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ require 'mustermann/set/match'
3
+
4
+ module Mustermann
5
+ class Set
6
+ class Linear
7
+ def initialize(set, patterns = [])
8
+ @set = set
9
+ @patterns = patterns
10
+ end
11
+
12
+ def add(pattern)
13
+ @patterns << pattern
14
+ end
15
+
16
+ def match(string, all: false, peek: false)
17
+ result = [] if all
18
+ @patterns.each do |pattern|
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
21
+ values = @set.values_for_pattern(pattern) || [nil]
22
+ values.each { |value| result << Match.new(match:, value:) }
23
+ end
24
+ result
25
+ end
26
+ end
27
+
28
+ private_constant :Linear
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ require 'mustermann/match'
3
+ require 'delegate'
4
+
5
+ module Mustermann
6
+ class Set
7
+ class Match < DelegateClass(Mustermann::Match)
8
+ attr_reader :value
9
+
10
+ def initialize(*args, value: nil, match: nil, **options)
11
+ @value = value
12
+ super(match || Mustermann::Match.new(*args, **options))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+ require 'mustermann/ast/translator'
3
+ require 'mustermann/set/match'
4
+
5
+ module Mustermann
6
+ class Set
7
+ class Trie
8
+ class Translator < AST::Translator
9
+ translate(:node) { |trie, **o| trie[t.compile(node)] }
10
+ translate(:separator) { |trie, **options| trie[payload] }
11
+
12
+ translate(:root) do |trie, **options|
13
+ leaves = t(payload, trie, **options)
14
+ if leaves.is_a? Array
15
+ leaves.each { |leaf| leaf.patterns << t.pattern }
16
+ else
17
+ leaves.patterns << t.pattern
18
+ end
19
+ leaves
20
+ end
21
+
22
+ translate(:char) do |trie, **options|
23
+ strings = t.possible_strings(payload)
24
+ return trie if strings.empty?
25
+ primary_node = trie[strings.first]
26
+ strings[1..-1].each { |s| trie.wire(s, primary_node) }
27
+ primary_node
28
+ end
29
+
30
+ translate(:optional) do |trie, **options|
31
+ [*t(payload, trie, **options), trie]
32
+ end
33
+
34
+ translate(Array) do |trie, **options|
35
+ i = 0
36
+ while i < size
37
+ element = self[i]
38
+ if element.is_a? :char or element.is_a? :separator
39
+ trie = t(element, trie, **options)
40
+ i += 1
41
+ elsif element.is_a? :splat and self[i + 1]&.is_a? :separator
42
+ # Compile splat+separator together so the splat is bounded by the separator,
43
+ # then continue building the trie for the remaining elements.
44
+ trie = trie[t.compile(self[i..i + 1])]
45
+ i += 2
46
+ elsif element.is_a? :splat or !self[i + 1]&.is_a? :separator
47
+ return trie[t.compile(self[i..-1])]
48
+ else
49
+ trie = t(element, trie, **options)
50
+ return trie.flat_map { |node| t(self[i + 1..-1], node, **options) } if trie.is_a? Array
51
+ i += 1
52
+ end
53
+ end
54
+ trie
55
+ end
56
+
57
+ attr_reader :pattern
58
+
59
+ def initialize(pattern)
60
+ @pattern = pattern
61
+ @compiler = pattern.compiler.new
62
+ @options = pattern.options
63
+ super()
64
+ end
65
+
66
+ def compile(node, **options) = /\A#{@compiler.translate(node, **@options, **options)}/
67
+
68
+ def possible_strings(char)
69
+ return [] if char.empty?
70
+ @compiler.class.char_representations(char, **@options.slice(:uri_decode, :space_matches_plus))
71
+ end
72
+ end
73
+
74
+ attr_reader :patterns, :set, :static, :dynamic
75
+
76
+ def initialize(set, patterns = [])
77
+ @set = set
78
+ @patterns = []
79
+ @dynamic = {}
80
+ @static = {}
81
+ patterns.each { |pattern| add(pattern) }
82
+ end
83
+
84
+ def [](key)
85
+ case key
86
+ when String then @static[key] ||= Trie.new(@set)
87
+ when Regexp then @dynamic[key] ||= Trie.new(@set)
88
+ end
89
+ end
90
+
91
+ def wire(string, target)
92
+ return if string.empty?
93
+ if string.size == 1
94
+ @static[string] ||= target
95
+ else
96
+ (@static[string[0]] ||= Trie.new(@set)).wire(string[1..-1], target)
97
+ end
98
+ end
99
+
100
+ def match(string, all: false, peek: false, position: 0, params: {})
101
+ return build_matches(string, params, all:) if position >= string.size
102
+ result = [] if all
103
+
104
+ if node = @static[string[position]]
105
+ if nested_result = node.match(string, all:, peek:, position: position + 1, params:)
106
+ return nested_result unless all
107
+ result.concat(nested_result)
108
+ end
109
+ end
110
+
111
+ anchored = {}
112
+ @dynamic.each do |matcher, node|
113
+ remaining = string[position..-1]
114
+ regexp_match = matcher.match(remaining)
115
+ # Non-greedy patterns (e.g. splat .*?) can match 0 chars on non-empty input, making
116
+ # no progress. Retry with an end-of-string anchor so they consume the full remainder.
117
+ if regexp_match&.to_s&.empty? && !remaining.empty?
118
+ anchored_matcher = anchored[matcher] ||= Regexp.new(matcher.source + '\z')
119
+ regexp_match = anchored_matcher.match(remaining)
120
+ end
121
+ next unless regexp_match
122
+
123
+ regexp_match.named_captures.each do |name, value|
124
+ params = params.dup
125
+ params[name] = params[name]&.dup || []
126
+ params[name] << value
127
+ end
128
+
129
+ nested_result = node.match(string, all:, params:, peek:, position: position + regexp_match.to_s.size)
130
+ return nested_result unless all
131
+ result.concat(nested_result)
132
+ end
133
+
134
+ if peek
135
+ matches = build_matches(string[0, position], params, all:, post_match: string[position..])
136
+ return matches unless all
137
+ result.concat(matches)
138
+ end
139
+
140
+ result
141
+ end
142
+
143
+ def build_matches(string, params, all: false, **options)
144
+ result = [] if all
145
+
146
+ @patterns.each do |pattern|
147
+ next if pattern.except_regexp&.match?(string)
148
+
149
+ pattern_params = params.to_h do |key, value|
150
+ value = value.flat_map { |v| pattern.map_param(key, v) }
151
+ value = value.first if value.size < 2 and not pattern.always_array?(key)
152
+ [key, value]
153
+ end
154
+
155
+ values = @set.values_for_pattern(pattern) || [nil]
156
+ values.each do |value|
157
+ match = Set::Match.new(pattern, string, pattern_params, value:, **options)
158
+ return match unless all
159
+ result << match
160
+ end
161
+ end
162
+
163
+ result
164
+ end
165
+
166
+ def add(pattern)
167
+ Translator.new(pattern).translate(pattern.to_ast, self)
168
+ end
169
+ end
170
+
171
+ private_constant :Trie
172
+ end
173
+ end
@@ -0,0 +1,327 @@
1
+ # frozen_string_literal: true
2
+ require 'mustermann'
3
+ require 'mustermann/expander'
4
+ require 'mustermann/set/cache'
5
+ require 'mustermann/set/linear'
6
+ require 'mustermann/set/trie'
7
+
8
+ module Mustermann
9
+ # A collection of patterns that can be matched against strings efficiently.
10
+ #
11
+ # Each pattern in the set may be associated with one or more arbitrary values,
12
+ # such as handler objects or route actions. A single {#match} call returns a
13
+ # {Set::Match} that provides both the captured parameters and the associated
14
+ # value for the matched pattern. When the set contains many patterns, an
15
+ # internal trie (prefix tree) is used to dispatch requests in sub-linear time.
16
+ #
17
+ # @example Building a routing table
18
+ # require 'mustermann/set'
19
+ #
20
+ # set = Mustermann::Set.new
21
+ # set.add('/users/:id', :users_show)
22
+ # set.add('/posts/:id', :posts_show)
23
+ #
24
+ # m = set.match('/users/42')
25
+ # m.value # => :users_show
26
+ # m.params['id'] # => '42'
27
+ #
28
+ # @example Constructor shorthand with a hash
29
+ # set = Mustermann::Set.new('/users/:id' => :users_show, '/posts/:id' => :posts_show)
30
+ #
31
+ # @example Block syntax
32
+ # set = Mustermann::Set.new do |s|
33
+ # s.add('/users/:id', :users_show)
34
+ # s.add('/posts/:id', :posts_show)
35
+ # end
36
+ #
37
+ # @note Adding patterns via {#add}, {#update}, or {#[]=} is not thread-safe, but matching and expanding is.
38
+ class Set
39
+ # Pattern options forwarded to {Mustermann.new} when patterns are created from strings.
40
+ # @return [Hash]
41
+ attr_reader :options
42
+
43
+ # Creates a new set, optionally pre-populated with patterns.
44
+ #
45
+ # Patterns can be supplied as a Hash (pattern → value), a plain String or
46
+ # Pattern, an Array of any of these, or an existing {Set}. The same forms
47
+ # are accepted by {#update} and {#add}.
48
+ #
49
+ # @example Empty set
50
+ # Mustermann::Set.new
51
+ #
52
+ # @example Pre-populated from a hash
53
+ # Mustermann::Set.new('/users/:id' => :users, '/posts/:id' => :posts)
54
+ #
55
+ # @example Imperative block
56
+ # Mustermann::Set.new do |s|
57
+ # s.add('/users/:id', :users)
58
+ # end
59
+ #
60
+ # @example Zero-argument block returning a mapping hash
61
+ # Mustermann::Set.new { { '/users/:id' => :users } }
62
+ #
63
+ # @param mapping [Array] initial patterns or mappings to add
64
+ # @param additional_values [:raise, :ignore, :append] behavior when extra keys are passed to {#expand};
65
+ # defaults to +:raise+
66
+ # @param options [Hash] pattern options forwarded to {Mustermann.new} (e.g. +type: :rails+)
67
+ # @raise [ArgumentError] if +additional_values+ is not a recognized behavior symbol
68
+ def initialize(*mapping, additional_values: :raise, use_trie: 50, use_cache: true, **options, &block)
69
+ raise ArgumentError, "Illegal value %p for additional_values" % additional_values unless Expander::ADDITIONAL_VALUES.include? additional_values
70
+ raise ArgumentError, "Illegal value %p for use_trie" % use_trie unless [true, false].include?(use_trie) or use_trie.is_a? Integer
71
+
72
+ @use_trie = use_trie
73
+ @use_cache = use_cache
74
+ @matcher = nil
75
+ @mapping = {}
76
+ @reverse_mapping = {}
77
+ @options = {}
78
+ @expanders = {}
79
+ @additional_values = additional_values
80
+
81
+ options.each do |key, value|
82
+ if key.is_a? Symbol
83
+ @options[key] = value
84
+ else
85
+ mapping << { key => value }
86
+ end
87
+ end
88
+
89
+ update(mapping)
90
+
91
+ block.arity == 0 ? update(yield) : yield(self) if block
92
+ end
93
+
94
+ # Adds a pattern to the set, optionally associated with one or more values.
95
+ #
96
+ # If the pattern is given as a String it will be compiled via {Mustermann.new}
97
+ # using the set's own options. The pattern must be AST-based (Sinatra, Rails,
98
+ # and similar types). Plain regexp patterns are not supported.
99
+ #
100
+ # Calling +add+ more than once for the same pattern appends additional values
101
+ # without creating duplicates.
102
+ #
103
+ # @example
104
+ # set.add('/users/:id', :users)
105
+ # set.add('/users/:id', :admin) # same pattern, second value
106
+ #
107
+ # @param pattern [String, Pattern] the pattern to add
108
+ # @param values [Array] zero or more values to associate with the pattern
109
+ # @return [self]
110
+ # @raise [ArgumentError] if the pattern is not AST-based, or if a reserved symbol is used as a value
111
+ def add(pattern, *values)
112
+ pattern = Mustermann.new(pattern, **options)
113
+ raise ArgumentError, "Non-AST patterns are not supported" unless pattern.respond_to? :to_ast
114
+
115
+ if @mapping.key? pattern
116
+ current = @mapping[pattern]
117
+ else
118
+ add_pattern(pattern)
119
+ current = @mapping[pattern] = []
120
+ end
121
+
122
+ values = [nil] if values.empty?
123
+
124
+ values.each do |value|
125
+ raise ArgumentError, "%p may not be used as a value" % value if Expander::ADDITIONAL_VALUES.include? value
126
+ raise ArgumentError, "the set itself may not be used as value" if value == self
127
+ next if current.include? value
128
+ current << value
129
+ @reverse_mapping[value] ||= []
130
+ @reverse_mapping[value] << pattern unless @reverse_mapping[value].include? pattern
131
+ @expanders[value]&.add(pattern)
132
+ end
133
+
134
+ self
135
+ end
136
+
137
+ # Adds a pattern associated with a value using hash-assignment syntax.
138
+ # @see #add
139
+ alias []= add
140
+
141
+ # Looks up a value by string or retrieves the first value for a known pattern object.
142
+ #
143
+ # When given a String, it is matched against the set and the associated value of the
144
+ # first matching pattern is returned. When given a {Pattern}, the first value
145
+ # registered for that exact pattern is returned without matching.
146
+ #
147
+ # @example String lookup
148
+ # set['/users/42'] # => :users_show (or nil)
149
+ #
150
+ # @example Pattern lookup
151
+ # pat = Mustermann.new('/users/:id')
152
+ # set[pat] # => :users_show (or nil)
153
+ #
154
+ # @param pattern_or_string [String, Pattern]
155
+ # @return [Object, nil] the associated value, or +nil+ if not found
156
+ # @raise [ArgumentError] for unsupported argument types
157
+ def [](pattern_or_string)
158
+ case pattern_or_string
159
+ when String then match(pattern_or_string)&.value
160
+ when Pattern then values_for_pattern(pattern_or_string)&.first
161
+ else raise ArgumentError, "unsupported pattern type #{pattern_or_string.class}"
162
+ end
163
+ end
164
+
165
+ # Matches the string against all patterns in the set and returns the first match.
166
+ #
167
+ # @param string [String] the string to match
168
+ # @return [Set::Match, nil] the first match, or +nil+ if none of the patterns match
169
+ def match(string) = @matcher&.match(string)
170
+
171
+ # Matches the beginning of the string against all patterns and returns the
172
+ # first prefix match. The unmatched remainder of the string is available via
173
+ # {Set::Match#post_match}.
174
+ #
175
+ # @param string [String]
176
+ # @return [Set::Match, nil] the first prefix match, or +nil+
177
+ def peek_match(string) = @matcher&.match(string, peek: true)
178
+
179
+ # Matches the string against all patterns and returns every match, one per
180
+ # (pattern, value) pair, in insertion order.
181
+ #
182
+ # @param string [String]
183
+ # @return [Array<Set::Match>] all matches, or an empty array if none
184
+ def match_all(string) = @matcher&.match(string, all: true)
185
+
186
+ # Matches the beginning of the string against all patterns and returns every
187
+ # prefix match, one per (pattern, value) pair. The unmatched remainder is
188
+ # available as {Set::Match#post_match} on each result.
189
+ #
190
+ # @param string [String]
191
+ # @return [Array<Set::Match>] all prefix matches, or an empty array if none
192
+ def peek_match_all(string) = @matcher&.match(string, all: true, peek: true)
193
+
194
+ # Returns a new set that includes all patterns from the receiver plus those
195
+ # from +mapping+. The receiver is not modified.
196
+ #
197
+ # @param mapping [Hash, String, Pattern, Array, Set] patterns to merge in
198
+ # @return [Set] a new set
199
+ def merge(mapping) = dup.update(mapping)
200
+
201
+ # @!visibility private
202
+ def initialize_copy(other)
203
+ @mapping = other.mapping.transform_values(&:dup)
204
+ @reverse_mapping = @mapping.each_with_object({}) do |(pattern, values), h|
205
+ values.each { |value| (h[value] ||= []) << pattern }
206
+ end
207
+ @expanders = {}
208
+ @matcher = nil
209
+ @mapping.each_key { |pattern| add_pattern(pattern) }
210
+ end
211
+
212
+ # Adds all patterns from +mapping+ to the set in place and returns +self+.
213
+ # Aliased as +merge!+.
214
+ #
215
+ # Accepts the same argument forms as {#initialize}: a Hash, a String, a
216
+ # {Pattern}, an Array, or another {Set}.
217
+ #
218
+ # @param mapping [Hash, String, Pattern, Array, Set]
219
+ # @return [self]
220
+ # @raise [ArgumentError] for unsupported mapping types
221
+ def update(mapping)
222
+ case mapping
223
+ when Set then mapping.mapping.each { |pattern, values| add(pattern, *values) }
224
+ when Hash then mapping.each { |k, v| add(k, v) }
225
+ when String, Pattern then add(mapping)
226
+ when Array then mapping.each { |item| update(item) }
227
+ else raise ArgumentError, "unsupported mapping type #{mapping.class}"
228
+ end
229
+ self
230
+ end
231
+
232
+ alias merge! update
233
+
234
+ # Returns all patterns that have been added to the set, in insertion order.
235
+ # @return [Array<Pattern>]
236
+ def patterns = @mapping.keys
237
+
238
+ # Returns an {Expander} that can generate strings from parameter hashes.
239
+ #
240
+ # When called without arguments (or with the set itself as the value) the
241
+ # expander covers all patterns in the set. Pass a specific value to get an
242
+ # expander limited to the patterns associated with that value.
243
+ #
244
+ # @param value [Object] restricts the expander to patterns associated with
245
+ # this value; defaults to the set itself (all patterns)
246
+ # @return [Mustermann::Expander]
247
+ def expander(value = self)
248
+ @expanders[value] ||= begin
249
+ patterns = value == self ? @mapping.keys : @reverse_mapping[value] || []
250
+ Mustermann::Expander.new(patterns, additional_values: @additional_values, **options)
251
+ end
252
+ end
253
+
254
+ # Generates a string from a parameter hash using the patterns in the set.
255
+ #
256
+ # When called with just a parameter hash, the first pattern that can be fully
257
+ # expanded with those keys is used. Pass a value as the first argument to
258
+ # restrict expansion to the patterns associated with that value. You may also
259
+ # pass an +additional_values+ behavior symbol (+:raise+, +:ignore+, or
260
+ # +:append+) as the first argument to override the set's default behavior for
261
+ # that call.
262
+ #
263
+ # @example Expand using any pattern
264
+ # set.expand(id: '5')
265
+ #
266
+ # @example Expand patterns for a specific value
267
+ # set.expand(:users, id: '5')
268
+ #
269
+ # @example Override additional_values behavior for one call
270
+ # set.expand(:ignore, id: '5', extra: 'ignored')
271
+ #
272
+ # @param value [Object, :raise, :ignore, :append] the value whose patterns
273
+ # should be used, or an additional_values behavior symbol; defaults to all
274
+ # patterns
275
+ # @param behavior [:raise, :ignore, :append, nil] how to handle extra keys;
276
+ # defaults to the set's +additional_values+ setting
277
+ # @param values [Hash, nil] the parameters to expand
278
+ # @return [String]
279
+ # @raise [Mustermann::ExpandError] if no pattern can be expanded with the given keys
280
+ def expand(value = self, behavior = nil, values = nil)
281
+ if Expander::ADDITIONAL_VALUES.include? value
282
+ if behavior.is_a? Hash
283
+ values = values ? values.merge(behavior) : behavior
284
+ behavior = nil
285
+ elsif behavior and behavior != value
286
+ raise ArgumentError, "behavior specified multiple times" if behavior
287
+ end
288
+ behavior = value
289
+ value = self
290
+ elsif value.is_a? Hash and behavior.nil? and values.nil?
291
+ values = value
292
+ value = self unless @reverse_mapping.key? values
293
+ end
294
+ expander(value).expand(behavior || @additional_values, values || {})
295
+ end
296
+
297
+ # @return [Boolean] whether the set contains any pattern associated with the given value
298
+ def has_value?(value) = @reverse_mapping[value]&.any?
299
+
300
+ # @!visibility private
301
+ def values_for_pattern(pattern) = @mapping[pattern] # :nodoc:
302
+
303
+ protected
304
+
305
+ attr_reader :mapping
306
+
307
+ private
308
+
309
+ def add_pattern(pattern)
310
+ case @use_trie
311
+ when true
312
+ @matcher ||= Trie.new(self, @mapping.keys)
313
+ when Integer
314
+ if @mapping.size >= @use_trie
315
+ @matcher = Trie.new(self, @mapping.keys)
316
+ @use_trie = true
317
+ end
318
+ end
319
+
320
+ @matcher ||= Linear.new(self, @mapping.keys)
321
+ @matcher = Cache.new(@matcher) if @use_cache and not @matcher.is_a? Cache
322
+ @matcher.add(pattern)
323
+
324
+ @expanders[self]&.add(pattern)
325
+ end
326
+ end
327
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Mustermann
3
- VERSION ||= '3.1.1'
3
+ VERSION ||= '4.0.0.alpha'
4
4
  end
data/lib/mustermann.rb CHANGED
@@ -115,10 +115,4 @@ module Mustermann
115
115
  def self.normalized_type(type)
116
116
  type.to_s.gsub('-', '_').downcase
117
117
  end
118
-
119
- # @!visibility private
120
- def self.extend_object(object)
121
- return super unless defined? ::Sinatra::Base and object.is_a? Class and object < ::Sinatra::Base
122
- require 'mustermann/extension'
123
- end
124
118
  end
metadata CHANGED
@@ -1,16 +1,24 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mustermann
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.1
4
+ version: 4.0.0.alpha
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Haase
8
+ - Kunpei Sakai
9
+ - Patrik Ragnarsson
10
+ - Jordan Owens
8
11
  - Zachary Scott
9
12
  bindir: bin
10
13
  cert_chain: []
11
14
  date: 1980-01-02 00:00:00.000000000 Z
12
15
  dependencies: []
13
- description: A library implementing patterns that behave like regular expressions.
16
+ description: |
17
+ Mustermann is your personal string matching expert. As an expert in the field of strings and patterns,
18
+ Mustermann keeps its runtime dependencies to a minimum and is fully covered with specs and documentation.
19
+
20
+ Given a string pattern, Mustermann will turn it into an object that behaves like a regular expression
21
+ and has comparable performance characteristics.
14
22
  email: sinatrarb@googlegroups.com
15
23
  executables: []
16
24
  extensions: []
@@ -36,27 +44,34 @@ files:
36
44
  - lib/mustermann/equality_map.rb
37
45
  - lib/mustermann/error.rb
38
46
  - lib/mustermann/expander.rb
39
- - lib/mustermann/extension.rb
47
+ - lib/mustermann/hybrid.rb
40
48
  - lib/mustermann/identity.rb
41
- - lib/mustermann/mapper.rb
49
+ - lib/mustermann/match.rb
42
50
  - lib/mustermann/pattern.rb
43
- - lib/mustermann/pattern_cache.rb
44
51
  - lib/mustermann/rails.rb
45
52
  - lib/mustermann/regexp.rb
46
53
  - lib/mustermann/regexp_based.rb
47
54
  - lib/mustermann/regular.rb
48
- - lib/mustermann/simple_match.rb
55
+ - lib/mustermann/router.rb
56
+ - lib/mustermann/set.rb
57
+ - lib/mustermann/set/cache.rb
58
+ - lib/mustermann/set/linear.rb
59
+ - lib/mustermann/set/match.rb
60
+ - lib/mustermann/set/trie.rb
49
61
  - lib/mustermann/sinatra.rb
50
62
  - lib/mustermann/sinatra/parser.rb
51
63
  - lib/mustermann/sinatra/safe_renderer.rb
52
64
  - lib/mustermann/sinatra/try_convert.rb
53
- - lib/mustermann/to_pattern.rb
54
65
  - lib/mustermann/version.rb
55
66
  - lib/mustermann/versions.rb
56
67
  homepage: https://github.com/sinatra/mustermann
57
68
  licenses:
58
69
  - MIT
59
- metadata: {}
70
+ metadata:
71
+ bug_tracker_uri: https://github.com/sinatra/mustermann/issues
72
+ changelog_uri: https://github.com/sinatra/mustermann/blob/main/CHANGELOG.md
73
+ documentation_uri: https://github.com/sinatra/mustermann/tree/main/mustermann#readme
74
+ source_code_uri: https://github.com/sinatra/mustermann/tree/main/mustermann
60
75
  rdoc_options: []
61
76
  require_paths:
62
77
  - lib
@@ -64,7 +79,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
64
79
  requirements:
65
80
  - - ">="
66
81
  - !ruby/object:Gem::Version
67
- version: 2.7.0
82
+ version: 3.3.0
68
83
  required_rubygems_version: !ruby/object:Gem::Requirement
69
84
  requirements:
70
85
  - - ">="
@@ -1,3 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- fail "Mustermann extension for Sinatra has been extracted into its own gem. More information at https://github.com/sinatra/mustermann-sinatra-extension"