mustermann-contrib 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1239 -0
  3. data/examples/highlighting.rb +35 -0
  4. data/highlighting.png +0 -0
  5. data/irb.png +0 -0
  6. data/lib/mustermann/cake.rb +18 -0
  7. data/lib/mustermann/express.rb +37 -0
  8. data/lib/mustermann/file_utils.rb +217 -0
  9. data/lib/mustermann/file_utils/glob_pattern.rb +39 -0
  10. data/lib/mustermann/fileutils.rb +1 -0
  11. data/lib/mustermann/flask.rb +198 -0
  12. data/lib/mustermann/grape.rb +35 -0
  13. data/lib/mustermann/pyramid.rb +28 -0
  14. data/lib/mustermann/rails.rb +46 -0
  15. data/lib/mustermann/shell.rb +56 -0
  16. data/lib/mustermann/simple.rb +50 -0
  17. data/lib/mustermann/string_scanner.rb +313 -0
  18. data/lib/mustermann/strscan.rb +1 -0
  19. data/lib/mustermann/template.rb +62 -0
  20. data/lib/mustermann/uri_template.rb +1 -0
  21. data/lib/mustermann/versions.rb +46 -0
  22. data/lib/mustermann/visualizer.rb +38 -0
  23. data/lib/mustermann/visualizer/highlight.rb +137 -0
  24. data/lib/mustermann/visualizer/highlighter.rb +37 -0
  25. data/lib/mustermann/visualizer/highlighter/ad_hoc.rb +94 -0
  26. data/lib/mustermann/visualizer/highlighter/ast.rb +102 -0
  27. data/lib/mustermann/visualizer/highlighter/composite.rb +45 -0
  28. data/lib/mustermann/visualizer/highlighter/dummy.rb +18 -0
  29. data/lib/mustermann/visualizer/highlighter/regular.rb +104 -0
  30. data/lib/mustermann/visualizer/pattern_extension.rb +68 -0
  31. data/lib/mustermann/visualizer/renderer/ansi.rb +23 -0
  32. data/lib/mustermann/visualizer/renderer/generic.rb +46 -0
  33. data/lib/mustermann/visualizer/renderer/hansi_template.rb +34 -0
  34. data/lib/mustermann/visualizer/renderer/html.rb +50 -0
  35. data/lib/mustermann/visualizer/renderer/sexp.rb +37 -0
  36. data/lib/mustermann/visualizer/tree.rb +63 -0
  37. data/lib/mustermann/visualizer/tree_renderer.rb +78 -0
  38. data/mustermann-contrib.gemspec +19 -0
  39. data/spec/cake_spec.rb +90 -0
  40. data/spec/express_spec.rb +209 -0
  41. data/spec/file_utils_spec.rb +119 -0
  42. data/spec/flask_spec.rb +361 -0
  43. data/spec/flask_subclass_spec.rb +368 -0
  44. data/spec/grape_spec.rb +747 -0
  45. data/spec/pattern_extension_spec.rb +49 -0
  46. data/spec/pyramid_spec.rb +101 -0
  47. data/spec/rails_spec.rb +647 -0
  48. data/spec/shell_spec.rb +147 -0
  49. data/spec/simple_spec.rb +268 -0
  50. data/spec/string_scanner_spec.rb +271 -0
  51. data/spec/template_spec.rb +841 -0
  52. data/spec/visualizer_spec.rb +199 -0
  53. data/theme.png +0 -0
  54. data/tree.png +0 -0
  55. metadata +126 -0
@@ -0,0 +1,35 @@
1
+ require 'bundler/setup'
2
+ require 'mustermann/visualizer'
3
+
4
+ Hansi.mode = ARGV[0].to_i if ARGV.any?
5
+
6
+ def self.example(type, *patterns)
7
+ print Hansi.render(:bold, " #{type}: ".ljust(14))
8
+ patterns.each do |pattern|
9
+ pattern = Mustermann.new(pattern, type: type)
10
+ space_after = pattern.to_s.size > 24 ? " " : " " * (25 - pattern.to_s.size)
11
+ highlight = Mustermann::Visualizer.highlight(pattern, inspect: true)
12
+ print highlight.to_ansi + space_after
13
+ end
14
+ puts
15
+ end
16
+
17
+ puts
18
+ example(:cake, '/:prefix/**')
19
+ example(:express, '/:prefix+/:id(\d+)', '/:page/:slug+')
20
+ example(:flask, '/<prefix>/<int:id>', '/user/<int(min=0):id>')
21
+ example(:identity, '/image.png')
22
+ example(:pyramid, '/{prefix:.*}/{id}', '/{page}/*slug')
23
+ example(:rails, '/:slug(.:ext)')
24
+ example(:regexp, '/(?<slug>[^/]+)', '/(?:page|user)/(\d+)')
25
+ example(:shell, '/**/*', '/\{a,b\}/{a,b}')
26
+ example(:simple, '/:page/*slug')
27
+ example(:sinatra, '/:page/*slug', '/users/{id}?')
28
+ example(:template, '/{+pre}/{page}{?q,p}', '/users/{id}?')
29
+ puts
30
+
31
+ example(:composition)
32
+ composite = Mustermann.new("/{a}", "/{b}/{c}")
33
+ puts " " + composite.to_ansi
34
+ puts " " + (Mustermann.new("/") ^ composite).to_ansi
35
+ puts
data/highlighting.png ADDED
Binary file
data/irb.png ADDED
Binary file
@@ -0,0 +1,18 @@
1
+ require 'mustermann'
2
+ require 'mustermann/ast/pattern'
3
+
4
+ module Mustermann
5
+ # CakePHP style pattern implementation.
6
+ #
7
+ # @example
8
+ # Mustermann.new('/:foo', type: :cake) === '/bar' # => true
9
+ #
10
+ # @see Mustermann::Pattern
11
+ # @see file:README.md#cake Syntax description in the README
12
+ class Cake < AST::Pattern
13
+ register :cake
14
+
15
+ on(?:) { |c| node(:capture) { scan(/\w+/) } }
16
+ on(?*) { |c| node(:splat, convert: (-> e { e.split('/') } unless scan(?*))) }
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ require 'mustermann'
2
+ require 'mustermann/ast/pattern'
3
+
4
+ module Mustermann
5
+ # Express style pattern implementation.
6
+ #
7
+ # @example
8
+ # Mustermann.new('/:foo', type: :express) === '/bar' # => true
9
+ #
10
+ # @see Mustermann::Pattern
11
+ # @see file:README.md#flask Syntax description in the README
12
+ class Express < AST::Pattern
13
+ register :express
14
+
15
+ on(nil, ??, ?+, ?*, ?)) { |c| unexpected(c) }
16
+ on(?:) { |c| node(:capture) { scan(/\w+/) } }
17
+ on(?() { |c| node(:splat, constraint: read_brackets(?(, ?))) }
18
+
19
+ suffix ??, after: :capture do |char, element|
20
+ unexpected(char) unless element.is_a? :capture
21
+ node(:optional, element)
22
+ end
23
+
24
+ suffix ?*, after: :capture do |match, element|
25
+ node(:named_splat, element.name)
26
+ end
27
+
28
+ suffix ?+, after: :capture do |match, element|
29
+ node(:named_splat, element.name, constraint: ".+")
30
+ end
31
+
32
+ suffix ?(, after: :capture do |match, element|
33
+ element.constraint = read_brackets(?(, ?))
34
+ element
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,217 @@
1
+ require 'mustermann'
2
+ require 'mustermann/file_utils/glob_pattern'
3
+ require 'mustermann/mapper'
4
+ require 'fileutils'
5
+
6
+ module Mustermann
7
+ # Implements handy file operations using patterns.
8
+ module FileUtils
9
+ extend self
10
+
11
+ # Turn a Mustermann pattern into glob pattern.
12
+ #
13
+ # @example
14
+ # require 'mustermann/file_utils'
15
+ #
16
+ # Mustermann::FileUtils.glob_pattern('/:name') # => '/*'
17
+ # Mustermann::FileUtils.glob_pattern('src/:path/:file.(js|rb)') # => 'src/**/*/*.{js,rb}'
18
+ # Mustermann::FileUtils.glob_pattern('{a,b}/*', type: :shell) # => '{a,b}/*'
19
+ #
20
+ # pattern = Mustermann.new('/foo/:page', '/bar/:page') # => #<Mustermann::Composite:...>
21
+ # Mustermann::FileUtils.glob_pattern(pattern) # => "{/foo/*,/bar/*}"
22
+ #
23
+ # @param [Object] pattern the object to turn into a glob pattern.
24
+ # @return [String] the glob pattern
25
+ def glob_pattern(*pattern, **options)
26
+ pattern_with_glob_pattern(*pattern, **options).last
27
+ end
28
+
29
+ # Uses the given pattern(s) to search for files and directories.
30
+ #
31
+ # @example
32
+ # require 'mustermann/file_utils'
33
+ # Mustermann::FileUtils.glob(':base.:ext') # => ['example.txt']
34
+ #
35
+ # Mustermann::FileUtils.glob(':base.:ext') do |file, params|
36
+ # file # => "example.txt"
37
+ # params # => {"base"=>"example", "ext"=>"txt"}
38
+ # end
39
+ def glob(*pattern, **options, &block)
40
+ raise ArgumentError, "no pattern given" if pattern.empty?
41
+ pattern, glob_pattern = pattern_with_glob_pattern(*pattern, **options)
42
+ results = [] unless block
43
+ Dir.glob(glob_pattern) do |result|
44
+ next unless params = pattern.params(result)
45
+ block ? block[result, params] : results << result
46
+ end
47
+ results
48
+ end
49
+
50
+ # Allows to search for files an map these onto other strings.
51
+ #
52
+ # @example
53
+ # require 'mustermann/file_utils'
54
+ #
55
+ # Mustermann::FileUtils.glob_map(':base.:ext' => ':base.bak.:ext') # => {'example.txt' => 'example.bak.txt'}
56
+ # Mustermann::FileUtils.glob_map(':base.:ext' => :base) { |file, mapped| mapped } # => ['example']
57
+ #
58
+ # @see Mustermann::Mapper
59
+ def glob_map(map = {}, **options, &block)
60
+ map = Mapper === map ? map : Mapper.new(map, **options)
61
+ mapped = glob(*map.to_h.keys).map { |f| [f, unescape(map[f])] }
62
+ block ? mapped.map(&block) : Hash[mapped]
63
+ end
64
+
65
+ # Copies files based on a pattern mapping.
66
+ #
67
+ # @example
68
+ # require 'mustermann/file_utils'
69
+ #
70
+ # # copies example.txt to example.bak.txt
71
+ # Mustermann::FileUtils.cp(':base.:ext' => ':base.bak.:ext')
72
+ #
73
+ # @see #glob_map
74
+ def cp(map = {}, recursive: false, **options)
75
+ utils_opts, opts = split_options(:preserve, :dereference_root, :remove_destination, **options)
76
+ cp_method = recursive ? :cp_r : :cp
77
+ glob_map(map, **opts) { |o,n| f.send(cp_method, o, n, **utils_opts) }
78
+ end
79
+
80
+
81
+ # Copies files based on a pattern mapping, recursively.
82
+ #
83
+ # @example
84
+ # require 'mustermann/file_utils'
85
+ #
86
+ # # copies Foo.app/example.txt to Foo.back.app/example.txt
87
+ # Mustermann::FileUtils.cp_r(':base.:ext' => ':base.bak.:ext')
88
+ #
89
+ # @see #glob_map
90
+ def cp_r(map = {}, **options)
91
+ cp(map, recursive: true, **options)
92
+ end
93
+
94
+ # Moves files based on a pattern mapping.
95
+ #
96
+ # @example
97
+ # require 'mustermann/file_utils'
98
+ #
99
+ # # moves example.txt to example.bak.txt
100
+ # Mustermann::FileUtils.mv(':base.:ext' => ':base.bak.:ext')
101
+ #
102
+ # @see #glob_map
103
+ def mv(map = {}, **options)
104
+ utils_opts, opts = split_options(**options)
105
+ glob_map(map, **opts) { |o,n| f.mv(o, n, **utils_opts) }
106
+ end
107
+
108
+
109
+ # Creates links based on a pattern mapping.
110
+ #
111
+ # @example
112
+ # require 'mustermann/file_utils'
113
+ #
114
+ # # creates a link from bin/example to lib/example.rb
115
+ # Mustermann::FileUtils.ln('lib/:name.rb' => 'bin/:name')
116
+ #
117
+ # @see #glob_map
118
+ def ln(map = {}, symbolic: false, **options)
119
+ utils_opts, opts = split_options(**options)
120
+ link_method = symbolic ? :ln_s : :ln
121
+ glob_map(map, **opts) { |o,n| f.send(link_method, o, n, **utils_opts) }
122
+ end
123
+
124
+ # Creates symbolic links based on a pattern mapping.
125
+ #
126
+ # @example
127
+ # require 'mustermann/file_utils'
128
+ #
129
+ # # creates a symbolic link from bin/example to lib/example.rb
130
+ # Mustermann::FileUtils.ln_s('lib/:name.rb' => 'bin/:name')
131
+ #
132
+ # @see #glob_map
133
+ def ln_s(map = {}, **options)
134
+ ln(map, symbolic: true, **options)
135
+ end
136
+
137
+ # Creates symbolic links based on a pattern mapping.
138
+ # Overrides potentailly existing files.
139
+ #
140
+ # @example
141
+ # require 'mustermann/file_utils'
142
+ #
143
+ # # creates a symbolic link from bin/example to lib/example.rb
144
+ # Mustermann::FileUtils.ln_sf('lib/:name.rb' => 'bin/:name')
145
+ #
146
+ # @see #glob_map
147
+ def ln_sf(map = {}, **options)
148
+ ln(map, symbolic: true, force: true, **options)
149
+ end
150
+
151
+
152
+ # Splits options into those meant for Mustermann and those
153
+ # meant for ::FileUtils.
154
+ #
155
+ # @!visibility private
156
+ def split_options(*utils_option_names, **options)
157
+ utils_options, pattern_options = {}, {}
158
+ utils_option_names += %i[force noop verbose]
159
+
160
+ options.each do |key, value|
161
+ list = utils_option_names.include?(key) ? utils_options : pattern_options
162
+ list[key] = value
163
+ end
164
+
165
+ [utils_options, pattern_options]
166
+ end
167
+
168
+ # Create a Mustermann pattern from whatever the input is and turn it into
169
+ # a glob pattern.
170
+ #
171
+ # @!visibility private
172
+ def pattern_with_glob_pattern(*pattern, **options)
173
+ options[:uri_decode] ||= false
174
+ pattern = Mustermann.new(*pattern.flatten, **options)
175
+ @glob_patterns ||= {}
176
+ @glob_patterns[pattern] ||= GlobPattern.generate(pattern)
177
+ [pattern, @glob_patterns[pattern]]
178
+ end
179
+
180
+ # The FileUtils method to use.
181
+ # @!visibility private
182
+ def f
183
+ ::FileUtils
184
+ end
185
+
186
+ # Unescape an URI escaped string.
187
+ # @!visibility private
188
+ def unescape(string)
189
+ @uri ||= URI::Parser.new
190
+ @uri.unescape(string)
191
+ end
192
+
193
+ # Create a new version of Mustermann::FileUtils using a different ::FileUtils module.
194
+ # @see DryRun
195
+ # @!visibility private
196
+ def with_file_utils(&block)
197
+ Module.new do
198
+ include Mustermann::FileUtils
199
+ define_method(:f, &block)
200
+ private(:f)
201
+ extend self
202
+ end
203
+ end
204
+
205
+ private :pattern_with_glob_pattern, :split_options, :f, :unescape
206
+
207
+ alias_method :copy, :cp
208
+ alias_method :move, :mv
209
+ alias_method :link, :ln
210
+ alias_method :symlink, :ln_s
211
+ alias_method :[], :glob
212
+
213
+ DryRun ||= with_file_utils { ::FileUtils::DryRun }
214
+ NoWrite ||= with_file_utils { ::FileUtils::NoWrite }
215
+ Verbose ||= with_file_utils { ::FileUtils::Verbose }
216
+ end
217
+ end
@@ -0,0 +1,39 @@
1
+ require 'mustermann/ast/translator'
2
+
3
+ module Mustermann
4
+ module FileUtils
5
+ # AST Translator to turn Mustermann patterns into glob patterns.
6
+ # @!visibility private
7
+ class GlobPattern < Mustermann::AST::Translator
8
+ # Character that need to be escaped in glob patterns.
9
+ # @!visibility private
10
+ ESCAPE = %w([ ] { } * ** \\)
11
+
12
+ # Turn a Mustermann pattern into glob pattern.
13
+ # @param [#to_glob, #to_ast, Object] pattern the object to turn into a glob pattern.
14
+ # @return [String] the glob pattern
15
+ # @!visibility private
16
+ def self.generate(pattern)
17
+ return pattern.to_glob if pattern.respond_to? :to_glob
18
+ return new.translate(pattern.to_ast) if pattern.respond_to? :to_ast
19
+ return "**/*" unless pattern.is_a? Mustermann::Composite
20
+ "{#{pattern.patterns.map { |p| generate(p) }.join(',')}}"
21
+ end
22
+
23
+ translate(:root, :group, :expression) { t(payload) || "" }
24
+ translate(:separator, :char) { t.escape(payload) }
25
+ translate(:capture) { constraint ? "**/*" : "*" }
26
+ translate(:optional) { "{#{t(payload)},}" }
27
+ translate(:named_splat, :splat) { "**/*" }
28
+ translate(:with_look_ahead) { t(head) + t(payload) }
29
+ translate(:union) { "{#{payload.map { |e| t(e) }.join(',')}}" }
30
+ translate(Array) { map { |e| t(e) }.join }
31
+
32
+ # Escape with a slash rather than URI escaping.
33
+ # @!visibility private
34
+ def escape(char)
35
+ ESCAPE.include?(char) ? "\\#{char}" : char
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1 @@
1
+ require 'mustermann/file_utils'
@@ -0,0 +1,198 @@
1
+ require 'mustermann'
2
+ require 'mustermann/ast/pattern'
3
+
4
+ module Mustermann
5
+ # Flask style pattern implementation.
6
+ #
7
+ # @example
8
+ # Mustermann.new('/<foo>', type: :flask) === '/bar' # => true
9
+ #
10
+ # @see Mustermann::Pattern
11
+ # @see file:README.md#flask Syntax description in the README
12
+ class Flask < AST::Pattern
13
+ include Concat::Native
14
+ register :flask
15
+
16
+ on(nil, ?>, ?:) { |c| unexpected(c) }
17
+
18
+ on(?<) do |char|
19
+ converter_name = expect(/\w+/, char: char)
20
+ args, opts = scan(?() ? read_args(?=, ?)) : [[], {}]
21
+
22
+ if scan(?:)
23
+ name = read_escaped(?>)
24
+ else
25
+ converter_name, name = 'default', converter_name
26
+ expect(?>)
27
+ end
28
+
29
+ converter = pattern.converters.fetch(converter_name) { unexpected("converter %p" % converter_name) }
30
+ converter = converter.new(*args, **opts) if converter.respond_to? :new
31
+ constraint = converter.constraint if converter.respond_to? :constraint
32
+ convert = converter.convert if converter.respond_to? :convert
33
+ qualifier = converter.qualifier if converter.respond_to? :qualifier
34
+ node_type = converter.node_type if converter.respond_to? :node_type
35
+ node_type ||= :capture
36
+
37
+ node(node_type, name, convert: convert, constraint: constraint, qualifier: qualifier)
38
+ end
39
+
40
+ # A class for easy creating of converters.
41
+ # @see Mustermann::Flask#register_converter
42
+ class Converter
43
+ # Constraint on the format used for the capture.
44
+ # Should be a regexp (or a string corresponding to a regexp)
45
+ # @see Mustermann::Flask#register_converter
46
+ attr_accessor :constraint
47
+
48
+ # Callback
49
+ # Should be a Proc.
50
+ # @see Mustermann::Flask#register_converter
51
+ attr_accessor :convert
52
+
53
+ # Constraint on the format used for the capture.
54
+ # Should be a regexp (or a string corresponding to a regexp)
55
+ # @see Mustermann::Flask#register_converter
56
+ # @!visibility private
57
+ attr_accessor :node_type
58
+
59
+ # Constraint on the format used for the capture.
60
+ # Should be a regexp (or a string corresponding to a regexp)
61
+ # @see Mustermann::Flask#register_converter
62
+ # @!visibility private
63
+ attr_accessor :qualifier
64
+
65
+ # @!visibility private
66
+ def self.create(&block)
67
+ Class.new(self) do
68
+ define_method(:initialize) { |*a, **o| block[self, *a, **o] }
69
+ end
70
+ end
71
+
72
+ # Makes sure a given value falls inbetween a min and a max.
73
+ # Uses the passed block to convert the value from a string to whatever
74
+ # format you'd expect.
75
+ #
76
+ # @example
77
+ # require 'mustermann/flask'
78
+ #
79
+ # class MyPattern < Mustermann::Flask
80
+ # register_converter(:x) { between(5, 15, &:to_i) }
81
+ # end
82
+ #
83
+ # pattern = MyPattern.new('<x:id>')
84
+ # pattern.params('/12') # => { 'id' => 12 }
85
+ # pattern.params('/16') # => { 'id' => 15 }
86
+ #
87
+ # @see Mustermann::Flask#register_converter
88
+ def between(min, max)
89
+ self.convert = proc do |input|
90
+ value = yield(input)
91
+ value = yield(min) if min and value < yield(min)
92
+ value = yield(max) if max and value > yield(max)
93
+ value
94
+ end
95
+ end
96
+ end
97
+
98
+ # Generally available converters.
99
+ # @!visibility private
100
+ def self.converters(inherited = true)
101
+ return @converters ||= {} unless inherited
102
+ defaults = superclass.respond_to?(:converters) ? superclass.converters : {}
103
+ defaults.merge(converters(false))
104
+ end
105
+
106
+ # Allows you to register your own converters.
107
+ #
108
+ # It is reommended to use this on a subclass, so to not influence other subsystems
109
+ # using flask templates.
110
+ #
111
+ # The object passed in as converter can implement #convert and/or #constraint.
112
+ #
113
+ # It can also instead implement #new, which will then return an object responding
114
+ # to some of these methods. Arguments from the flask pattern will be passed to #new.
115
+ #
116
+ # If passed a block, it will be yielded to with a {Mustermann::Flask::Converter}
117
+ # instance and any arguments in the flask pattern.
118
+ #
119
+ # @example with simple object
120
+ # require 'mustermann/flask'
121
+ #
122
+ # MyPattern = Class.new(Mustermann::Flask)
123
+ # up_converter = Struct.new(:convert).new(:upcase.to_proc)
124
+ # MyPattern.register_converter(:upper, up_converter)
125
+ #
126
+ # MyPattern.new("/<up:name>").params('/foo') # => { "name" => "FOO" }
127
+ #
128
+ # @example with block
129
+ # require 'mustermann/flask'
130
+ #
131
+ # MyPattern = Class.new(Mustermann::Flask)
132
+ # MyPattern.register_converter(:upper) { |c| c.convert = :upcase.to_proc }
133
+ #
134
+ # MyPattern.new("/<up:name>").params('/foo') # => { "name" => "FOO" }
135
+ #
136
+ # @example with converter class
137
+ # require 'mustermann/flasl'
138
+ #
139
+ # class MyPattern < Mustermann::Flask
140
+ # class Converter
141
+ # attr_reader :convert
142
+ # def initialize(send: :to_s)
143
+ # @convert = send.to_sym.to_proc
144
+ # end
145
+ # end
146
+ #
147
+ # register_converter(:t, Converter)
148
+ # end
149
+ #
150
+ # MyPattern.new("/<t(send=upcase):name>").params('/Foo') # => { "name" => "FOO" }
151
+ # MyPattern.new("/<t(send=downcase):name>").params('/Foo') # => { "name" => "foo" }
152
+ #
153
+ # @param [#to_s] name converter name
154
+ # @param [#new, #convert, #constraint, nil] converter
155
+ def self.register_converter(name, converter = nil, &block)
156
+ converter ||= Converter.create(&block)
157
+ converters(false)[name.to_s] = converter
158
+ end
159
+
160
+ register_converter(:string) do |converter, minlength: nil, maxlength: nil, length: nil|
161
+ converter.qualifier = "{%s,%s}" % [minlength || 1, maxlength] if minlength or maxlength
162
+ converter.qualifier = "{%s}" % length if length
163
+ end
164
+
165
+ register_converter(:int) do |converter, min: nil, max: nil, fixed_digits: false|
166
+ converter.constraint = /\d/
167
+ converter.qualifier = "{#{fixed_digits}}" if fixed_digits
168
+ converter.between(min, max) { |string| Integer(string) }
169
+ end
170
+
171
+ register_converter(:float) do |converter, min: nil, max: nil|
172
+ converter.constraint = /\d*\.?\d+/
173
+ converter.qualifier = ""
174
+ converter.between(min, max) { |string| Float(string) }
175
+ end
176
+
177
+ register_converter(:path) do |converter|
178
+ converter.node_type = :named_splat
179
+ end
180
+
181
+ register_converter(:any) do |converter, *strings|
182
+ strings = strings.map { |s| Regexp.escape(s) unless s == {} }.compact
183
+ converter.qualifier = ""
184
+ converter.constraint = Regexp.union(*strings)
185
+ end
186
+
187
+ register_converter(:default, converters['string'])
188
+
189
+ supported_options :converters
190
+ attr_reader :converters
191
+
192
+ def initialize(input, converters: {}, **options)
193
+ @converters = self.class.converters.dup
194
+ converters.each { |k,v| @converters[k.to_s] = v } if converters
195
+ super(input, **options)
196
+ end
197
+ end
198
+ end