mustermann-contrib 1.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +1239 -0
- data/examples/highlighting.rb +35 -0
- data/highlighting.png +0 -0
- data/irb.png +0 -0
- data/lib/mustermann/cake.rb +18 -0
- data/lib/mustermann/express.rb +37 -0
- data/lib/mustermann/file_utils.rb +217 -0
- data/lib/mustermann/file_utils/glob_pattern.rb +39 -0
- data/lib/mustermann/fileutils.rb +1 -0
- data/lib/mustermann/flask.rb +198 -0
- data/lib/mustermann/grape.rb +35 -0
- data/lib/mustermann/pyramid.rb +28 -0
- data/lib/mustermann/rails.rb +46 -0
- data/lib/mustermann/shell.rb +56 -0
- data/lib/mustermann/simple.rb +50 -0
- data/lib/mustermann/string_scanner.rb +313 -0
- data/lib/mustermann/strscan.rb +1 -0
- data/lib/mustermann/template.rb +62 -0
- data/lib/mustermann/uri_template.rb +1 -0
- data/lib/mustermann/versions.rb +46 -0
- data/lib/mustermann/visualizer.rb +38 -0
- data/lib/mustermann/visualizer/highlight.rb +137 -0
- data/lib/mustermann/visualizer/highlighter.rb +37 -0
- data/lib/mustermann/visualizer/highlighter/ad_hoc.rb +94 -0
- data/lib/mustermann/visualizer/highlighter/ast.rb +102 -0
- data/lib/mustermann/visualizer/highlighter/composite.rb +45 -0
- data/lib/mustermann/visualizer/highlighter/dummy.rb +18 -0
- data/lib/mustermann/visualizer/highlighter/regular.rb +104 -0
- data/lib/mustermann/visualizer/pattern_extension.rb +68 -0
- data/lib/mustermann/visualizer/renderer/ansi.rb +23 -0
- data/lib/mustermann/visualizer/renderer/generic.rb +46 -0
- data/lib/mustermann/visualizer/renderer/hansi_template.rb +34 -0
- data/lib/mustermann/visualizer/renderer/html.rb +50 -0
- data/lib/mustermann/visualizer/renderer/sexp.rb +37 -0
- data/lib/mustermann/visualizer/tree.rb +63 -0
- data/lib/mustermann/visualizer/tree_renderer.rb +78 -0
- data/mustermann-contrib.gemspec +19 -0
- data/spec/cake_spec.rb +90 -0
- data/spec/express_spec.rb +209 -0
- data/spec/file_utils_spec.rb +119 -0
- data/spec/flask_spec.rb +361 -0
- data/spec/flask_subclass_spec.rb +368 -0
- data/spec/grape_spec.rb +747 -0
- data/spec/pattern_extension_spec.rb +49 -0
- data/spec/pyramid_spec.rb +101 -0
- data/spec/rails_spec.rb +647 -0
- data/spec/shell_spec.rb +147 -0
- data/spec/simple_spec.rb +268 -0
- data/spec/string_scanner_spec.rb +271 -0
- data/spec/template_spec.rb +841 -0
- data/spec/visualizer_spec.rb +199 -0
- data/theme.png +0 -0
- data/tree.png +0 -0
- 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
|