mustermann 1.0.2.rc1 → 1.0.2.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +799 -230
- data/{mustermann/bench → bench}/capturing.rb +0 -0
- data/{mustermann/bench → bench}/regexp.rb +0 -0
- data/{mustermann/bench → bench}/simple_vs_sinatra.rb +0 -0
- data/{mustermann/bench → bench}/template_vs_addressable.rb +0 -0
- data/{mustermann/lib → lib}/mustermann.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/ast/boundaries.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/ast/compiler.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/ast/expander.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/ast/node.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/ast/param_scanner.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/ast/parser.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/ast/pattern.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/ast/template_generator.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/ast/transformer.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/ast/translator.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/ast/validation.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/caster.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/composite.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/concat.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/equality_map.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/error.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/expander.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/extension.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/identity.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/mapper.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/pattern.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/pattern_cache.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/regexp.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/regexp_based.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/regular.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/simple_match.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/sinatra.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/sinatra/parser.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/sinatra/safe_renderer.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/sinatra/try_convert.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/to_pattern.rb +0 -0
- data/{mustermann/lib → lib}/mustermann/version.rb +1 -1
- data/{mustermann/mustermann.gemspec → mustermann.gemspec} +0 -0
- data/{mustermann/spec → spec}/ast_spec.rb +0 -0
- data/{mustermann/spec → spec}/composite_spec.rb +0 -0
- data/{mustermann/spec → spec}/concat_spec.rb +0 -0
- data/{mustermann/spec → spec}/equality_map_spec.rb +0 -0
- data/{mustermann/spec → spec}/expander_spec.rb +0 -0
- data/{mustermann/spec → spec}/extension_spec.rb +0 -0
- data/{mustermann/spec → spec}/identity_spec.rb +0 -0
- data/{mustermann/spec → spec}/mapper_spec.rb +0 -0
- data/{mustermann/spec → spec}/mustermann_spec.rb +0 -0
- data/{mustermann/spec → spec}/pattern_spec.rb +0 -0
- data/{mustermann/spec → spec}/regexp_based_spec.rb +0 -0
- data/{mustermann/spec → spec}/regular_spec.rb +0 -0
- data/{mustermann/spec → spec}/simple_match_spec.rb +0 -0
- data/{mustermann/spec → spec}/sinatra_spec.rb +0 -0
- data/{mustermann/spec → spec}/to_pattern_spec.rb +0 -0
- metadata +71 -126
- data/.gitignore +0 -18
- data/.rspec +0 -5
- data/.travis.yml +0 -25
- data/.yardopts +0 -3
- data/Gemfile +0 -7
- data/Rakefile +0 -27
- data/mustermann-contrib/LICENSE +0 -23
- data/mustermann-contrib/README.md +0 -1155
- data/mustermann-contrib/examples/highlighting.rb +0 -35
- data/mustermann-contrib/highlighting.png +0 -0
- data/mustermann-contrib/irb.png +0 -0
- data/mustermann-contrib/lib/mustermann/cake.rb +0 -19
- data/mustermann-contrib/lib/mustermann/express.rb +0 -38
- data/mustermann-contrib/lib/mustermann/file_utils.rb +0 -218
- data/mustermann-contrib/lib/mustermann/file_utils/glob_pattern.rb +0 -40
- data/mustermann-contrib/lib/mustermann/fileutils.rb +0 -1
- data/mustermann-contrib/lib/mustermann/flask.rb +0 -199
- data/mustermann-contrib/lib/mustermann/pyramid.rb +0 -29
- data/mustermann-contrib/lib/mustermann/rails.rb +0 -47
- data/mustermann-contrib/lib/mustermann/shell.rb +0 -57
- data/mustermann-contrib/lib/mustermann/simple.rb +0 -51
- data/mustermann-contrib/lib/mustermann/string_scanner.rb +0 -314
- data/mustermann-contrib/lib/mustermann/strscan.rb +0 -1
- data/mustermann-contrib/lib/mustermann/template.rb +0 -63
- data/mustermann-contrib/lib/mustermann/uri_template.rb +0 -1
- data/mustermann-contrib/lib/mustermann/versions.rb +0 -47
- data/mustermann-contrib/lib/mustermann/visualizer.rb +0 -39
- data/mustermann-contrib/lib/mustermann/visualizer/highlight.rb +0 -138
- data/mustermann-contrib/lib/mustermann/visualizer/highlighter.rb +0 -38
- data/mustermann-contrib/lib/mustermann/visualizer/highlighter/ad_hoc.rb +0 -95
- data/mustermann-contrib/lib/mustermann/visualizer/highlighter/ast.rb +0 -103
- data/mustermann-contrib/lib/mustermann/visualizer/highlighter/composite.rb +0 -46
- data/mustermann-contrib/lib/mustermann/visualizer/highlighter/dummy.rb +0 -19
- data/mustermann-contrib/lib/mustermann/visualizer/highlighter/regular.rb +0 -105
- data/mustermann-contrib/lib/mustermann/visualizer/pattern_extension.rb +0 -69
- data/mustermann-contrib/lib/mustermann/visualizer/renderer/ansi.rb +0 -24
- data/mustermann-contrib/lib/mustermann/visualizer/renderer/generic.rb +0 -47
- data/mustermann-contrib/lib/mustermann/visualizer/renderer/hansi_template.rb +0 -35
- data/mustermann-contrib/lib/mustermann/visualizer/renderer/html.rb +0 -51
- data/mustermann-contrib/lib/mustermann/visualizer/renderer/sexp.rb +0 -38
- data/mustermann-contrib/lib/mustermann/visualizer/tree.rb +0 -64
- data/mustermann-contrib/lib/mustermann/visualizer/tree_renderer.rb +0 -79
- data/mustermann-contrib/mustermann-contrib.gemspec +0 -19
- data/mustermann-contrib/spec/cake_spec.rb +0 -91
- data/mustermann-contrib/spec/express_spec.rb +0 -210
- data/mustermann-contrib/spec/file_utils_spec.rb +0 -120
- data/mustermann-contrib/spec/flask_spec.rb +0 -362
- data/mustermann-contrib/spec/flask_subclass_spec.rb +0 -369
- data/mustermann-contrib/spec/pattern_extension_spec.rb +0 -50
- data/mustermann-contrib/spec/pyramid_spec.rb +0 -102
- data/mustermann-contrib/spec/rails_spec.rb +0 -648
- data/mustermann-contrib/spec/shell_spec.rb +0 -148
- data/mustermann-contrib/spec/simple_spec.rb +0 -269
- data/mustermann-contrib/spec/string_scanner_spec.rb +0 -272
- data/mustermann-contrib/spec/template_spec.rb +0 -842
- data/mustermann-contrib/spec/visualizer_spec.rb +0 -199
- data/mustermann-contrib/theme.png +0 -0
- data/mustermann-contrib/tree.png +0 -0
- data/mustermann/LICENSE +0 -23
- data/mustermann/README.md +0 -853
- data/support/lib/support.rb +0 -7
- data/support/lib/support/coverage.rb +0 -23
- data/support/lib/support/env.rb +0 -19
- data/support/lib/support/expand_matcher.rb +0 -28
- data/support/lib/support/generate_template_matcher.rb +0 -27
- data/support/lib/support/match_matcher.rb +0 -39
- data/support/lib/support/pattern.rb +0 -42
- data/support/lib/support/projects.rb +0 -20
- data/support/lib/support/scan_matcher.rb +0 -63
- data/support/support.gemspec +0 -27
@@ -1,199 +0,0 @@
|
|
1
|
-
require 'support'
|
2
|
-
require 'mustermann/visualizer'
|
3
|
-
|
4
|
-
describe Mustermann::Visualizer do
|
5
|
-
subject(:highlight) { Mustermann::Visualizer.highlight(pattern) }
|
6
|
-
before { Hansi.mode = 256 }
|
7
|
-
after { Hansi.mode = nil }
|
8
|
-
|
9
|
-
describe :highlight do
|
10
|
-
context :sinatra do
|
11
|
-
context "/a" do
|
12
|
-
let(:pattern) { Mustermann.new("/a") }
|
13
|
-
its(:to_ansi) { should be == "\e[0m\e[38;5;246m\e[38;5;246m\e[38;5;247m/\e[0m\e[38;5;246m\e[38;5;246m\e[38;5;246ma\e[0m" }
|
14
|
-
its(:to_html) { should be == '<span style="color: #839496;"><span style="color: #93a1a1;">/</span><span style="color: #839496;">a</span></span></span>' }
|
15
|
-
its(:to_sexp) { should be == '(root (separator /) (char a))' }
|
16
|
-
its(:to_pattern) { should be == pattern }
|
17
|
-
its(:to_s) { should be == "/a" }
|
18
|
-
its(:stylesheet) { should include(".mustermann_pattern .mustermann_illegal {\n color: #8b0000;") }
|
19
|
-
|
20
|
-
example do
|
21
|
-
highlight.to_html(css: false).should be ==
|
22
|
-
'<span class="mustermann_pattern"><span class="mustermann_root"><span class="mustermann_separator">/</span><span class="mustermann_char">a</span></span></span>'
|
23
|
-
end
|
24
|
-
|
25
|
-
example do
|
26
|
-
renderer = Mustermann::Visualizer::Renderer::Generic
|
27
|
-
result = highlight.render_with(renderer)
|
28
|
-
result.should be == pattern.to_s
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
context '/:name' do
|
33
|
-
let(:pattern) { Mustermann.new("/:name") }
|
34
|
-
its(:to_sexp) { should be == "(root (separator /) (capture : (name name)))" }
|
35
|
-
end
|
36
|
-
|
37
|
-
context '/{name}' do
|
38
|
-
let(:pattern) { Mustermann.new("/{name}") }
|
39
|
-
its(:to_sexp) { should be == "(root (separator /) (capture { (name name) }))" }
|
40
|
-
end
|
41
|
-
|
42
|
-
context '/{+name}' do
|
43
|
-
let(:pattern) { Mustermann.new("/{+name}") }
|
44
|
-
its(:to_sexp) { should be == "(root (separator /) (named_splat {+ (name name) }))" }
|
45
|
-
end
|
46
|
-
|
47
|
-
context ':user(@:host)?' do
|
48
|
-
let(:pattern) { Mustermann.new(':user(@:host)?') }
|
49
|
-
its(:to_sexp) { should be == '(root (capture : (name user)) (optional (group "(" (char @) (capture : (name host)) ")") ?))' }
|
50
|
-
end
|
51
|
-
|
52
|
-
context 'a b' do
|
53
|
-
let(:pattern) { Mustermann.new('a b') }
|
54
|
-
its(:to_sexp) { should be == '(root (char a) (char " ") (char b))' }
|
55
|
-
end
|
56
|
-
|
57
|
-
context 'a|b' do
|
58
|
-
let(:pattern) { Mustermann.new('a|b') }
|
59
|
-
its(:to_sexp) { should be == '(root (union (char a) | (char b)))' }
|
60
|
-
end
|
61
|
-
|
62
|
-
context '(a|b)' do
|
63
|
-
let(:pattern) { Mustermann.new('(a|b)c') }
|
64
|
-
its(:to_sexp) { should be == '(root (union "(" (char a) | (char b) ")") (char c))' }
|
65
|
-
end
|
66
|
-
|
67
|
-
context '\:a' do
|
68
|
-
let(:pattern) { Mustermann.new('\:a') }
|
69
|
-
its(:to_sexp) { should be == '(root (escaped "\\\\" (escaped_char :)) (char a))' }
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
context :regexp do
|
74
|
-
context 'a' do
|
75
|
-
let(:pattern) { Mustermann.new('a', type: :regexp) }
|
76
|
-
its(:to_sexp) { should be == '(root (char a))' }
|
77
|
-
end
|
78
|
-
|
79
|
-
context '/(\d+)' do
|
80
|
-
let(:pattern) { Mustermann.new('/(\d+)', type: :regexp) }
|
81
|
-
its(:to_sexp) { should be == '(root (separator /) (capture "(" (special "\\\\d") (special +))))' }
|
82
|
-
end
|
83
|
-
|
84
|
-
context '\A' do
|
85
|
-
let(:pattern) { Mustermann.new('\A', type: :regexp, check_anchors: false) }
|
86
|
-
its(:to_sexp) { should be == '(root (illegal "\\\\A"))' }
|
87
|
-
end
|
88
|
-
|
89
|
-
context '(?<name>.)\g<name>' do
|
90
|
-
let(:pattern) { Mustermann.new('(?<name>.)\g<name>', type: :regexp) }
|
91
|
-
its(:to_sexp) { should be == '(root (capture "(?<" (name name) >(special .))) (special "\\\\g<name>"))' }
|
92
|
-
end
|
93
|
-
|
94
|
-
context '\p{Ll}' do
|
95
|
-
let(:pattern) { Mustermann.new('\p{Ll}', type: :regexp) }
|
96
|
-
its(:to_sexp) { should be == '(root (special "\\\\p{Ll}"))' }
|
97
|
-
end
|
98
|
-
|
99
|
-
context '\/' do
|
100
|
-
let(:pattern) { Mustermann.new('\/', type: :regexp) }
|
101
|
-
its(:to_sexp) { should be == '(root (separator /))' }
|
102
|
-
end
|
103
|
-
|
104
|
-
context '\[' do
|
105
|
-
let(:pattern) { Mustermann.new('\[', type: :regexp) }
|
106
|
-
its(:to_sexp) { should be == '(root (escaped "\\\\" (escaped_char [)))' }
|
107
|
-
end
|
108
|
-
|
109
|
-
context '^' do
|
110
|
-
let(:pattern) { Mustermann.new('^', type: :regexp, check_anchors: false) }
|
111
|
-
its(:to_sexp) { should be == '(root (illegal ^))' }
|
112
|
-
end
|
113
|
-
|
114
|
-
context '(?-mix:.)' do
|
115
|
-
let(:pattern) { Mustermann.new('(?-mix:.)', type: :regexp) }
|
116
|
-
its(:to_sexp) { should be == '(root (special "(?-mix:") (special .) (special ")"))' }
|
117
|
-
end
|
118
|
-
|
119
|
-
context '[a\d]' do
|
120
|
-
let(:pattern) { Mustermann.new('[a\d]', type: :regexp) }
|
121
|
-
its(:to_sexp) { should be == '(root (special [) (char a) (special "\\\\d") (special ]))' }
|
122
|
-
end
|
123
|
-
|
124
|
-
context '[^a-z]' do
|
125
|
-
let(:pattern) { Mustermann.new('[^a-z]', type: :regexp) }
|
126
|
-
its(:to_sexp) { should be == '(root (special [) (special ^) (char a) (special -) (char z) (special ]))' }
|
127
|
-
end
|
128
|
-
|
129
|
-
context '[[:digit:]]' do
|
130
|
-
let(:pattern) { Mustermann.new('[[:digit:]]', type: :regexp) }
|
131
|
-
its(:to_sexp) { should be == '(root (special [[:digit:]]))' }
|
132
|
-
end
|
133
|
-
|
134
|
-
context 'a{1,}' do
|
135
|
-
let(:pattern) { Mustermann.new('a{1,}', type: :regexp) }
|
136
|
-
its(:to_sexp) { should be == "(root (char a) (special {1,}))" }
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
context :template do
|
141
|
-
context '/{name}' do
|
142
|
-
let(:pattern) { Mustermann.new("/{+foo,bar*}", type: :template) }
|
143
|
-
its(:to_sexp) { should be == "(root (separator /) (expression {+ (variable (name foo)) , (variable (name bar) *) }))" }
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
context "custom AST based pattern" do
|
148
|
-
let(:my_type) { Class.new(Mustermann::AST::Pattern) { on('x') { |*| node(:char, "o") } }}
|
149
|
-
let(:pattern) { Mustermann.new("fxx", type: my_type) }
|
150
|
-
its(:to_sexp) { should be == "(root (char f) (escaped x) (escaped x))" }
|
151
|
-
end
|
152
|
-
|
153
|
-
context "without known highlighter" do
|
154
|
-
let(:pattern) { Mustermann::Pattern.new("foo") }
|
155
|
-
its(:to_sexp) { should be == "(root (unknown foo))" }
|
156
|
-
end
|
157
|
-
|
158
|
-
context :composite do
|
159
|
-
let(:pattern) { Mustermann.new(":a", ":b") ^ Mustermann.new(":c") }
|
160
|
-
its(:to_sexp) do
|
161
|
-
should be == '(composite (quote "(") (composite (type sinatra:) (quote "\\"") ' \
|
162
|
-
'(root (capture : (name a))) (quote "\\"") (quote " | ") (type sinatra:) (quote ' \
|
163
|
-
'"\\"") (root (capture : (name b))) (quote "\\"")) (quote ")") (quote " ^ ") (type ' \
|
164
|
-
'sinatra:) (quote "\\"") (root (capture : (name c))) (quote "\\""))'
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
describe :tree do
|
170
|
-
subject(:tree) { Mustermann::Visualizer.tree(pattern) }
|
171
|
-
|
172
|
-
context :sinatra do
|
173
|
-
context "/:a(@:b)" do
|
174
|
-
let(:pattern) { Mustermann.new("/:a(@:b)") }
|
175
|
-
let(:tree_data) do
|
176
|
-
<<-TREE.gsub(/^\s+/, '')
|
177
|
-
\e[38;5;61m\e[0m\e[38;5;100mroot\e[0m \e[0m\e[38;5;66m\"\e[0m\e[38;5;66m\e[38;5;242m\e[0m\e[38;5;66m\e[4m\e[38;5;100m/:a(@:b)\e[0m\e[38;5;66m\e[38;5;242m\e[0m\e[38;5;66m\" \e[0m
|
178
|
-
\e[38;5;61m└ \e[0m\e[38;5;166mpayload\e[0m
|
179
|
-
\e[38;5;61m ├ \e[0m\e[38;5;100mseparator\e[0m \e[0m\e[38;5;66m\"\e[0m\e[38;5;66m\e[38;5;242m\e[0m\e[38;5;66m\e[4m\e[38;5;100m/\e[0m\e[38;5;66m\e[38;5;242m:a(@:b)\e[0m\e[38;5;66m\" \e[0m
|
180
|
-
\e[38;5;61m ├ \e[0m\e[38;5;100mcapture\e[0m \e[0m\e[38;5;66m\"\e[0m\e[38;5;66m\e[38;5;242m/\e[0m\e[38;5;66m\e[4m\e[38;5;100m:a\e[0m\e[38;5;66m\e[38;5;242m(@:b)\e[0m\e[38;5;66m\" \e[0m
|
181
|
-
\e[38;5;61m └ \e[0m\e[38;5;100mgroup\e[0m \e[0m\e[38;5;66m\"\e[0m\e[38;5;66m\e[38;5;242m/:a\e[0m\e[38;5;66m\e[4m\e[38;5;100m(@:b)\e[0m\e[38;5;66m\e[38;5;242m\e[0m\e[38;5;66m\" \e[0m
|
182
|
-
\e[38;5;61m └ \e[0m\e[38;5;166mpayload\e[0m
|
183
|
-
\e[38;5;61m ├ \e[0m\e[38;5;100mchar\e[0m \e[0m\e[38;5;66m\"\e[0m\e[38;5;66m\e[38;5;242m/:a(\e[0m\e[38;5;66m\e[4m\e[38;5;100m@\e[0m\e[38;5;66m\e[38;5;242m:b)\e[0m\e[38;5;66m\" \e[0m
|
184
|
-
\e[38;5;61m └ \e[0m\e[38;5;100mcapture\e[0m \e[0m\e[38;5;66m\"\e[0m\e[38;5;66m\e[38;5;242m/:a(@\e[0m\e[38;5;66m\e[4m\e[38;5;100m:b\e[0m\e[38;5;66m\e[38;5;242m)\e[0m\e[38;5;66m\" \e[0m
|
185
|
-
TREE
|
186
|
-
end
|
187
|
-
its(:to_s) { should be == tree_data }
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
context :shell do
|
192
|
-
context "/**/*" do
|
193
|
-
let(:pattern) { Mustermann.new("/**/*", type: :shell) }
|
194
|
-
let(:tree_data) { "\e[38;5;61m\e[0m\e[38;5;100mpattern (not AST based)\e[0m \e[0m\e[38;5;66m\"\e[0m\e[38;5;66m\e[38;5;242m\e[0m\e[38;5;66m\e[4m\e[38;5;100m/**/*\e[0m\e[38;5;66m\e[38;5;242m\e[0m\e[38;5;66m\" \e[0m\n" }
|
195
|
-
its(:to_s) { should be == tree_data }
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
Binary file
|
data/mustermann-contrib/tree.png
DELETED
Binary file
|
data/mustermann/LICENSE
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
Copyright (c) 2013-2017 Konstantin Haase
|
2
|
-
Copyright (c) 2016-2017 Zachary Scott
|
3
|
-
|
4
|
-
Permission is hereby granted, free of charge, to any person
|
5
|
-
obtaining a copy of this software and associated documentation
|
6
|
-
files (the "Software"), to deal in the Software without
|
7
|
-
restriction, including without limitation the rights to use,
|
8
|
-
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
copies of the Software, and to permit persons to whom the
|
10
|
-
Software is furnished to do so, subject to the following
|
11
|
-
conditions:
|
12
|
-
|
13
|
-
The above copyright notice and this permission notice shall be
|
14
|
-
included in all copies or substantial portions of the Software.
|
15
|
-
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
18
|
-
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
20
|
-
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
21
|
-
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
22
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
23
|
-
OTHER DEALINGS IN THE SOFTWARE.
|
data/mustermann/README.md
DELETED
@@ -1,853 +0,0 @@
|
|
1
|
-
# The Amazing Mustermann
|
2
|
-
|
3
|
-
*Make sure you view the correct docs: [latest release](http://rubydoc.info/gems/mustermann/frames), [master](http://rubydoc.info/github/rkh/mustermann/master/frames).*
|
4
|
-
|
5
|
-
Welcome to [Mustermann](http://en.wikipedia.org/wiki/List_of_placeholder_names_by_language#German). Mustermann is your personal string matching expert. As an expert in the field of strings and patterns, Mustermann keeps its runtime dependencies to a minimum and is fully covered with specs and documentation.
|
6
|
-
|
7
|
-
Given a string pattern, Mustermann will turn it into an object that behaves like a regular expression and has comparable performance characteristics.
|
8
|
-
|
9
|
-
``` ruby
|
10
|
-
if '/foo/bar' =~ Mustermann.new('/foo/*')
|
11
|
-
puts 'it works!'
|
12
|
-
end
|
13
|
-
|
14
|
-
case 'something.png'
|
15
|
-
when Mustermann.new('foo/*') then puts "prefixed with foo"
|
16
|
-
when Mustermann.new('*.pdf') then puts "it's a PDF"
|
17
|
-
when Mustermann.new('*.png') then puts "it's an image"
|
18
|
-
end
|
19
|
-
|
20
|
-
pattern = Mustermann.new('/:prefix/*.*')
|
21
|
-
pattern.params('/a/b.c') # => { "prefix" => "a", splat => ["b", "c"] }
|
22
|
-
```
|
23
|
-
|
24
|
-
## Overview
|
25
|
-
|
26
|
-
### Features
|
27
|
-
|
28
|
-
* **[Pattern Types](#-pattern-types):** Mustermann supports a wide variety of different pattern types, making it compatible with a large variety of existing software.
|
29
|
-
* **[Fine Grained Control](#-available-options):** You can easily adjust matching behavior and add constraints to the placeholders and capture groups.
|
30
|
-
* **[Binary Operators](#-binary-operators) and [Concatenation](#-concatenation):** Patterns can be combined into composite patterns using binary operators.
|
31
|
-
* **[Regexp Look Alike](#-regexp-look-alike):** Mustermann patterns can be used as a replacement for regular expressions.
|
32
|
-
* **[Parameter Parsing](#-parameter-parsing):** Mustermann can parse matched parameters into a Sinatra-style "params" hash, including type casting.
|
33
|
-
* **[Peeking](#-peeking):** Lets you check if the beginning of a string matches a pattern.
|
34
|
-
* **[Expanding](#-expanding):** Besides parsing a parameters from an input string, a pattern object can also be used to generate a string from a set of parameters.
|
35
|
-
* **[Generating Templates](#-generating-templates):** This comes in handy when wanting to hand on patterns rather than fully expanded strings as part of an external API.
|
36
|
-
* **[Proc Look Alike](#-proc-look-alike):** Pass on a pattern instead of a block.
|
37
|
-
* **[Duck Typing](#-duck-typing):** You can create your own pattern-like objects by implementing `to_pattern`.
|
38
|
-
* **[Performance](#-performance):** Patterns are implemented with both performance and a low memory footprint in mind.
|
39
|
-
|
40
|
-
### Additional Tooling
|
41
|
-
|
42
|
-
These features are included in the library, but not loaded by default
|
43
|
-
|
44
|
-
* **[Mapper](#-mapper):** A simple tool for mapping one string to another based on patterns.
|
45
|
-
* **[Sinatra Integration](#-sinatra-integration):** Mustermann can be used as a [Sinatra](http://www.sinatrarb.com/) extension. Sinatra 2.0 and beyond will use Mustermann by default.
|
46
|
-
|
47
|
-
<a name="-pattern-types"></a>
|
48
|
-
## Pattern Types
|
49
|
-
|
50
|
-
Mustermann support multiple pattern types. A pattern type defines the syntax, matching semantics and whether certain features, like [expanding](#-expanding) and [generating templates](#-generating-templates), are available.
|
51
|
-
|
52
|
-
You can create a pattern of a certain type by passing `type` option to `Mustermann.new`:
|
53
|
-
|
54
|
-
``` ruby
|
55
|
-
require 'mustermann'
|
56
|
-
pattern = Mustermann.new('/*/**', type: :shell)
|
57
|
-
```
|
58
|
-
|
59
|
-
Note that this will use the type as suggestion: When passing in a string argument, it will create a pattern of the given type, but it might choose a different type for other objects (a regular expression argument will always result in a [regexp](#-pattern-details-regexp) pattern, a symbol always in a [sinatra](#-pattern-details-sinatra) pattern, etc).
|
60
|
-
|
61
|
-
Alternatively, you can also load and instantiate the pattern type directly:
|
62
|
-
|
63
|
-
``` ruby
|
64
|
-
require 'mustermann/shell'
|
65
|
-
pattern = Mustermann::Shell.new('/*/**')
|
66
|
-
```
|
67
|
-
|
68
|
-
Mustermann itself includes the [sinatra](#-sinatra-pattern), [identity](#-identity-pattern) and [regexp](#-regexp-pattern) pattern types. Other pattern types are available as separate gems.
|
69
|
-
|
70
|
-
<a name="-binary-operators"></a>
|
71
|
-
## Binary Operators
|
72
|
-
|
73
|
-
Patterns can be combined via binary operators. These are:
|
74
|
-
|
75
|
-
* `|` (or): Resulting pattern matches if at least one of the input pattern matches.
|
76
|
-
* `&` (and): Resulting pattern matches if all input patterns match.
|
77
|
-
* `^` (xor): Resulting pattern matches if exactly one of the input pattern matches.
|
78
|
-
|
79
|
-
``` ruby
|
80
|
-
require 'mustermann'
|
81
|
-
|
82
|
-
first = Mustermann.new('/foo/:input')
|
83
|
-
second = Mustermann.new('/:input/bar')
|
84
|
-
|
85
|
-
first | second === "/foo/foo" # => true
|
86
|
-
first | second === "/foo/bar" # => true
|
87
|
-
|
88
|
-
first & second === "/foo/foo" # => false
|
89
|
-
first & second === "/foo/bar" # => true
|
90
|
-
|
91
|
-
first ^ second === "/foo/foo" # => true
|
92
|
-
first ^ second === "/foo/bar" # => false
|
93
|
-
```
|
94
|
-
|
95
|
-
These resulting objects are fully functional pattern objects, allowing you to call methods like `params` or `to_proc` on them. Moreover, *or* patterns created solely from expandable patterns will also be expandable. The same logic also applies to generating templates from *or* patterns.
|
96
|
-
|
97
|
-
<a name="-concatenation"></a>
|
98
|
-
## Concatenation
|
99
|
-
|
100
|
-
Similar to [Binary Operators](#-binary-operators), two patterns can be concatenated using `+`.
|
101
|
-
|
102
|
-
``` ruby
|
103
|
-
require 'mustermann'
|
104
|
-
|
105
|
-
prefix = Mustermann.new("/:prefix")
|
106
|
-
about = prefix + "/about"
|
107
|
-
|
108
|
-
about.params("/main/about") # => {"prefix" => "main"}
|
109
|
-
```
|
110
|
-
|
111
|
-
Patterns of different types can be mixed. The availability of `to_templates` and `expand` depends on the patterns being concatenated.
|
112
|
-
|
113
|
-
<a name="-regexp-look-alike"></a>
|
114
|
-
## Regexp Look Alike
|
115
|
-
|
116
|
-
Pattern objects mimic Ruby's `Regexp` class by implementing `match`, `=~`, `===`, `names` and `named_captures`.
|
117
|
-
|
118
|
-
``` ruby
|
119
|
-
require 'mustermann'
|
120
|
-
|
121
|
-
pattern = Mustermann.new('/:page')
|
122
|
-
pattern.match('/') # => nil
|
123
|
-
pattern.match('/home') # => #<MatchData "/home" page:"home">
|
124
|
-
pattern =~ '/home' # => 0
|
125
|
-
pattern === '/home' # => true (this allows using it in case statements)
|
126
|
-
pattern.names # => ['page']
|
127
|
-
pattern.names # => {"page"=>[1]}
|
128
|
-
|
129
|
-
pattern = Mustermann.new('/home', type: :identity)
|
130
|
-
pattern.match('/') # => nil
|
131
|
-
pattern.match('/home') # => #<Mustermann::SimpleMatch "/home">
|
132
|
-
pattern =~ '/home' # => 0
|
133
|
-
pattern === '/home' # => true (this allows using it in case statements)
|
134
|
-
pattern.names # => []
|
135
|
-
pattern.names # => {}
|
136
|
-
```
|
137
|
-
|
138
|
-
Moreover, patterns based on regular expressions (all but `identity` and `shell`) automatically convert to regular expressions when needed:
|
139
|
-
|
140
|
-
``` ruby
|
141
|
-
require 'mustermann'
|
142
|
-
|
143
|
-
pattern = Mustermann.new('/:page')
|
144
|
-
union = Regexp.union(pattern, /^$/)
|
145
|
-
|
146
|
-
union =~ "/foo" # => 0
|
147
|
-
union =~ "" # => 0
|
148
|
-
|
149
|
-
Regexp.try_convert(pattern) # => /.../
|
150
|
-
```
|
151
|
-
|
152
|
-
This way, unless some code explicitly checks the class for a regular expression, you should be able to pass in a pattern object instead even if the code in question was not written with Mustermann in mind.
|
153
|
-
|
154
|
-
<a name="-parameter-parsing"></a>
|
155
|
-
## Parameter Parsing
|
156
|
-
|
157
|
-
Besides being a `Regexp` look-alike, Mustermann also adds a `params` method, that will give you a Sinatra-style hash:
|
158
|
-
|
159
|
-
``` ruby
|
160
|
-
require 'mustermann'
|
161
|
-
|
162
|
-
pattern = Mustermann.new('/:prefix/*.*')
|
163
|
-
pattern.params('/a/b.c') # => { "prefix" => "a", splat => ["b", "c"] }
|
164
|
-
```
|
165
|
-
|
166
|
-
For patterns with typed captures, it will also automatically convert them:
|
167
|
-
|
168
|
-
``` ruby
|
169
|
-
require 'mustermann'
|
170
|
-
|
171
|
-
pattern = Mustermann.new('/<prefix>/<int:id>', type: :flask)
|
172
|
-
pattern.params('/page/10') # => { "prefix" => "page", "id" => 10 }
|
173
|
-
```
|
174
|
-
|
175
|
-
<a name="-peeking"></a>
|
176
|
-
## Peeking
|
177
|
-
|
178
|
-
Peeking gives the option to match a pattern against the beginning of a string rather the full string. Patterns come with four methods for peeking:
|
179
|
-
|
180
|
-
* `peek` returns the matching substring.
|
181
|
-
* `peek_size` returns the number of characters matching.
|
182
|
-
* `peek_match` will return a `MatchData` or `Mustermann::SimpleMatch` (just like `match` does for the full string)
|
183
|
-
* `peek_params` will return the `params` hash parsed from the substring and the number of characters.
|
184
|
-
|
185
|
-
All of the above will turn `nil` if there was no match.
|
186
|
-
|
187
|
-
``` ruby
|
188
|
-
require 'mustermann'
|
189
|
-
|
190
|
-
pattern = Mustermann.new('/:prefix')
|
191
|
-
pattern.peek('/foo/bar') # => '/foo'
|
192
|
-
pattern.peek_size('/foo/bar') # => 4
|
193
|
-
|
194
|
-
path_info = '/foo/bar'
|
195
|
-
params, size = patter.peek_params(path_info) # params == { "prefix" => "foo" }
|
196
|
-
rest = path_info[size..-1] # => "/bar"
|
197
|
-
```
|
198
|
-
|
199
|
-
<a name="-expanding"></a>
|
200
|
-
## Expanding
|
201
|
-
|
202
|
-
Similarly to parsing, it is also possible to generate a string from a pattern by expanding it with a hash.
|
203
|
-
For simple expansions, you can use `Pattern#expand`.
|
204
|
-
|
205
|
-
``` ruby
|
206
|
-
pattern = Mustermann.new('/:file(.:ext)?')
|
207
|
-
pattern.expand(file: 'pony') # => "/pony"
|
208
|
-
pattern.expand(file: 'pony', ext: 'jpg') # => "/pony.jpg"
|
209
|
-
pattern.expand(ext: 'jpg') # raises Mustermann::ExpandError
|
210
|
-
```
|
211
|
-
|
212
|
-
Expanding can be useful for instance when implementing link helpers.
|
213
|
-
|
214
|
-
### Expander Objects
|
215
|
-
|
216
|
-
To get fine-grained control over expansion, you can use `Mustermann::Expander` directly.
|
217
|
-
|
218
|
-
You can create an expander object directly from a string:
|
219
|
-
|
220
|
-
``` ruby
|
221
|
-
require 'mustermann/expander'
|
222
|
-
expander = Mustermann::Expander("/:file.jpg")
|
223
|
-
expander.expand(file: 'pony') # => "/pony.jpg"
|
224
|
-
|
225
|
-
expander = Mustermann::Expander(":file(.:ext)", type: :rails)
|
226
|
-
expander.expand(file: 'pony', ext: 'jpg') # => "/pony.jpg"
|
227
|
-
```
|
228
|
-
|
229
|
-
Or you can pass it a pattern instance:
|
230
|
-
|
231
|
-
``` ruby
|
232
|
-
require 'mustermann'
|
233
|
-
pattern = Mustermann.new("/:file")
|
234
|
-
|
235
|
-
require 'mustermann/expander'
|
236
|
-
expander = Mustermann::Expander.new(pattern)
|
237
|
-
```
|
238
|
-
|
239
|
-
### Expanding Multiple Patterns
|
240
|
-
|
241
|
-
You can add patterns to an expander object via `<<`:
|
242
|
-
|
243
|
-
``` ruby
|
244
|
-
require 'mustermann'
|
245
|
-
|
246
|
-
expander = Mustermann::Expander.new
|
247
|
-
expander << "/users/:user_id"
|
248
|
-
expander << "/pages/:page_id"
|
249
|
-
|
250
|
-
expander.expand(user_id: 15) # => "/users/15"
|
251
|
-
expander.expand(page_id: 58) # => "/pages/58"
|
252
|
-
```
|
253
|
-
|
254
|
-
You can set pattern options when creating the expander:
|
255
|
-
|
256
|
-
``` ruby
|
257
|
-
require 'mustermann'
|
258
|
-
|
259
|
-
expander = Mustermann::Expander.new(type: :template)
|
260
|
-
expander << "/users/{user_id}"
|
261
|
-
expander << "/pages/{page_id}"
|
262
|
-
```
|
263
|
-
|
264
|
-
Additionally, it is possible to combine patterns of different types:
|
265
|
-
|
266
|
-
``` ruby
|
267
|
-
require 'mustermann'
|
268
|
-
|
269
|
-
expander = Mustermann::Expander.new
|
270
|
-
expander << Mustermann.new("/users/{user_id}", type: :template)
|
271
|
-
expander << Mustermann.new("/pages/:page_id", type: :rails)
|
272
|
-
```
|
273
|
-
|
274
|
-
### Handling Additional Values
|
275
|
-
|
276
|
-
The handling of additional values passed in to `expand` can be changed by setting the `additional_values` option:
|
277
|
-
|
278
|
-
``` ruby
|
279
|
-
require 'mustermann'
|
280
|
-
|
281
|
-
expander = Mustermann::Expander.new("/:slug", additional_values: :raise)
|
282
|
-
expander.expand(slug: "foo", value: "bar") # raises Mustermann::ExpandError
|
283
|
-
|
284
|
-
expander = Mustermann::Expander.new("/:slug", additional_values: :ignore)
|
285
|
-
expander.expand(slug: "foo", value: "bar") # => "/foo"
|
286
|
-
|
287
|
-
expander = Mustermann::Expander.new("/:slug", additional_values: :append)
|
288
|
-
expander.expand(slug: "foo", value: "bar") # => "/foo?value=bar"
|
289
|
-
```
|
290
|
-
|
291
|
-
It is also possible to pass this directly to the `expand` call:
|
292
|
-
|
293
|
-
``` ruby
|
294
|
-
require 'mustermann'
|
295
|
-
|
296
|
-
pattern = Mustermann.new('/:slug')
|
297
|
-
pattern.expand(:append, slug: "foo", value: "bar") # => "/foo?value=bar"
|
298
|
-
```
|
299
|
-
|
300
|
-
<a name="-generating-templates"></a>
|
301
|
-
## Generating Templates
|
302
|
-
|
303
|
-
You can generate a list of URI templates that correspond to a Mustermann pattern (it is a list rather than a single template, as most pattern types are significantly more expressive than URI templates).
|
304
|
-
|
305
|
-
This comes in quite handy since URI templates are not made for pattern matching. That way you can easily use a more precise template syntax and have it automatically generate hypermedia links for you.
|
306
|
-
|
307
|
-
Template generation is supported by almost all patterns (notable exceptions are `shell`, `regexp` and `simple` patterns).
|
308
|
-
|
309
|
-
``` ruby
|
310
|
-
require 'mustermann'
|
311
|
-
|
312
|
-
Mustermann.new("/:name").to_templates # => ["/{name}"]
|
313
|
-
Mustermann.new("/:foo(@:bar)?/*baz").to_templates # => ["/{foo}@{bar}/{+baz}", "/{foo}/{+baz}"]
|
314
|
-
Mustermann.new("/{name}", type: :template).to_templates # => ["/{name}"
|
315
|
-
```
|
316
|
-
|
317
|
-
Union Composite patterns (with the | operator) support template generation if all patterns they are composed of also support it.
|
318
|
-
|
319
|
-
``` ruby
|
320
|
-
require 'mustermann'
|
321
|
-
|
322
|
-
pattern = Mustermann.new('/:name')
|
323
|
-
pattern |= Mustermann.new('/{name}', type: :template)
|
324
|
-
pattern |= Mustermann.new('/example/*nested')
|
325
|
-
pattern.to_templates # => ["/{name}", "/example/{+nested}"]
|
326
|
-
```
|
327
|
-
|
328
|
-
If accepting arbitrary patterns, you can and should use `respond_to?` to check feature availability.
|
329
|
-
|
330
|
-
``` ruby
|
331
|
-
if pattern.respond_to? :to_templates
|
332
|
-
pattern.to_templates
|
333
|
-
else
|
334
|
-
warn "does not support template generation"
|
335
|
-
end
|
336
|
-
```
|
337
|
-
|
338
|
-
<a name="-proc-look-alike"></a>
|
339
|
-
## Proc Look Alike
|
340
|
-
|
341
|
-
Patterns implement `to_proc`:
|
342
|
-
|
343
|
-
``` ruby
|
344
|
-
require 'mustermann'
|
345
|
-
pattern = Mustermann.new('/foo')
|
346
|
-
callback = pattern.to_proc # => #<Proc>
|
347
|
-
|
348
|
-
callback.call('/foo') # => true
|
349
|
-
callback.call('/bar') # => false
|
350
|
-
```
|
351
|
-
|
352
|
-
They can therefore be easily passed to methods expecting a block:
|
353
|
-
|
354
|
-
``` ruby
|
355
|
-
require 'mustermann'
|
356
|
-
|
357
|
-
list = ["foo", "example@email.com", "bar"]
|
358
|
-
pattern = Mustermann.new(":name@:domain.:tld")
|
359
|
-
email = list.detect(&pattern) # => "example@email.com"
|
360
|
-
```
|
361
|
-
|
362
|
-
<a name="-mapper"></a>
|
363
|
-
## Mapper
|
364
|
-
|
365
|
-
|
366
|
-
You can use a mapper to transform strings according to two or more mappings:
|
367
|
-
|
368
|
-
``` ruby
|
369
|
-
require 'mustermann/mapper'
|
370
|
-
|
371
|
-
mapper = Mustermann::Mapper.new("/:page(.:format)?" => ["/:page/view.:format", "/:page/view.html"])
|
372
|
-
mapper['/foo'] # => "/foo/view.html"
|
373
|
-
mapper['/foo.xml'] # => "/foo/view.xml"
|
374
|
-
mapper['/foo/bar'] # => "/foo/bar"
|
375
|
-
```
|
376
|
-
|
377
|
-
<a name="-sinatra-integration"></a>
|
378
|
-
## Sinatra Integration
|
379
|
-
|
380
|
-
All patterns implement `match`, which means they can be dropped into Sinatra and other Rack routers:
|
381
|
-
|
382
|
-
``` ruby
|
383
|
-
require 'sinatra'
|
384
|
-
require 'mustermann'
|
385
|
-
|
386
|
-
get Mustermann.new('/:foo') do
|
387
|
-
params[:foo]
|
388
|
-
end
|
389
|
-
```
|
390
|
-
|
391
|
-
In fact, since using this with Sinatra is the main use case, it comes with a build-in extension for **Sinatra 1.x**.
|
392
|
-
|
393
|
-
``` ruby
|
394
|
-
require 'sinatra'
|
395
|
-
require 'mustermann'
|
396
|
-
|
397
|
-
register Mustermann
|
398
|
-
|
399
|
-
# this will use Mustermann rather than the built-in pattern matching
|
400
|
-
get '/:slug(.ext)?' do
|
401
|
-
params[:slug]
|
402
|
-
end
|
403
|
-
```
|
404
|
-
|
405
|
-
### Configuration
|
406
|
-
|
407
|
-
You can change what pattern type you want to use for your app via the `pattern` option:
|
408
|
-
|
409
|
-
``` ruby
|
410
|
-
require 'sinatra/base'
|
411
|
-
require 'mustermann'
|
412
|
-
|
413
|
-
class MyApp < Sinatra::Base
|
414
|
-
register Mustermann
|
415
|
-
set :pattern, type: :shell
|
416
|
-
|
417
|
-
get '/images/*.png' do
|
418
|
-
send_file request.path_info
|
419
|
-
end
|
420
|
-
|
421
|
-
get '/index{.htm,.html,}' do
|
422
|
-
erb :index
|
423
|
-
end
|
424
|
-
end
|
425
|
-
```
|
426
|
-
|
427
|
-
You can use the same setting for options:
|
428
|
-
|
429
|
-
``` ruby
|
430
|
-
require 'sinatra'
|
431
|
-
require 'mustermann'
|
432
|
-
|
433
|
-
register Mustermann
|
434
|
-
set :pattern, capture: { ext: %w[png jpg html txt] }
|
435
|
-
|
436
|
-
get '/:slug(.:ext)?' do
|
437
|
-
# slug will be 'foo' for '/foo.png'
|
438
|
-
# slug will be 'foo.bar' for '/foo.bar'
|
439
|
-
# slug will be 'foo.bar' for '/foo.bar.html'
|
440
|
-
params[:slug]
|
441
|
-
end
|
442
|
-
```
|
443
|
-
|
444
|
-
It is also possible to pass in options to a specific route:
|
445
|
-
|
446
|
-
``` ruby
|
447
|
-
require 'sinatra'
|
448
|
-
require 'mustermann'
|
449
|
-
|
450
|
-
register Mustermann
|
451
|
-
|
452
|
-
get '/:slug(.:ext)?', pattern: { greedy: false } do
|
453
|
-
# slug will be 'foo' for '/foo.png'
|
454
|
-
# slug will be 'foo' for '/foo.bar'
|
455
|
-
# slug will be 'foo' for '/foo.bar.html'
|
456
|
-
params[:slug]
|
457
|
-
end
|
458
|
-
```
|
459
|
-
|
460
|
-
Of course, all of the above can be combined.
|
461
|
-
Moreover, the `capture` and the `except` option can be passed to route directly.
|
462
|
-
And yes, this also works with `before` and `after` filters.
|
463
|
-
|
464
|
-
``` ruby
|
465
|
-
require 'sinatra/base'
|
466
|
-
require 'sinatra/respond_with'
|
467
|
-
require 'mustermann'
|
468
|
-
|
469
|
-
class MyApp < Sinatra::Base
|
470
|
-
register Mustermann, Sinatra::RespondWith
|
471
|
-
set :pattern, capture: { id: /\d+/ } # id will only match digits
|
472
|
-
|
473
|
-
# only capture extensions known to Rack
|
474
|
-
before '*:ext', capture: Rack::Mime::MIME_TYPES.keys do
|
475
|
-
content_type params[:ext] # set Content-Type
|
476
|
-
request.path_info = params[:splat].first # drop the extension
|
477
|
-
end
|
478
|
-
|
479
|
-
get '/:id' do
|
480
|
-
not_found unless page = Page.find params[:id]
|
481
|
-
respond_with(page)
|
482
|
-
end
|
483
|
-
end
|
484
|
-
```
|
485
|
-
|
486
|
-
### Why would I want this?
|
487
|
-
|
488
|
-
* It gives you fine grained control over the pattern matching
|
489
|
-
* Allows you to use different pattern styles in your app
|
490
|
-
* The default is more robust and powerful than the built-in patterns
|
491
|
-
* Sinatra 2.0 will use Mustermann internally
|
492
|
-
* Better exceptions for broken route syntax
|
493
|
-
|
494
|
-
### Why not include this in Sinatra 1.x?
|
495
|
-
|
496
|
-
* It would introduce breaking changes, even though these would be minor
|
497
|
-
* Like Sinatra 2.0, Mustermann requires Ruby 2.0 or newer
|
498
|
-
|
499
|
-
<a name="-duck-typing"></a>
|
500
|
-
## Duck Typing
|
501
|
-
|
502
|
-
<a name="-duck-typing-to-pattern"></a>
|
503
|
-
### `to_pattern`
|
504
|
-
|
505
|
-
All methods converting string input to pattern objects will also accept any arbitrary object that implements `to_pattern`:
|
506
|
-
|
507
|
-
``` ruby
|
508
|
-
require 'mustermann'
|
509
|
-
|
510
|
-
class MyObject
|
511
|
-
def to_pattern(**options)
|
512
|
-
Mustermann.new("/foo", **options)
|
513
|
-
end
|
514
|
-
end
|
515
|
-
|
516
|
-
object = MyObject.new
|
517
|
-
Mustermann.new(object, type: :rails) # => #<Mustermann::Rails:"/foo">
|
518
|
-
```
|
519
|
-
|
520
|
-
It might also be that you want to call `to_pattern` yourself instead of `Mustermann.new`. You can load `mustermann/to_pattern` to implement this method for strings, regular expressions and pattern objects:
|
521
|
-
|
522
|
-
``` ruby
|
523
|
-
require 'mustermann/to_pattern'
|
524
|
-
|
525
|
-
"/foo".to_pattern # => #<Mustermann::Sinatra:"/foo">
|
526
|
-
"/foo".to_pattern(type: :rails) # => #<Mustermann::Rails:"/foo">
|
527
|
-
%r{/foo}.to_pattern # => #<Mustermann::Regular:"\\/foo">
|
528
|
-
"/foo".to_pattern.to_pattern # => #<Mustermann::Sinatra:"/foo">
|
529
|
-
```
|
530
|
-
|
531
|
-
You can also use the `Mustermann::ToPattern` mixin to easily add `to_pattern` to your own objects:
|
532
|
-
|
533
|
-
``` ruby
|
534
|
-
require 'mustermann/to_pattern'
|
535
|
-
|
536
|
-
class MyObject
|
537
|
-
include Mustermann::ToPattern
|
538
|
-
|
539
|
-
def to_s
|
540
|
-
"/foo"
|
541
|
-
end
|
542
|
-
end
|
543
|
-
|
544
|
-
MyObject.new.to_pattern # => #<Mustermann::Sinatra:"/foo">
|
545
|
-
```
|
546
|
-
|
547
|
-
<a name="-duck-typing-respond-to"></a>
|
548
|
-
### `respond_to?`
|
549
|
-
|
550
|
-
You can and should use `respond_to?` to check if a pattern supports certain features.
|
551
|
-
|
552
|
-
``` ruby
|
553
|
-
require 'mustermann'
|
554
|
-
pattern = Mustermann.new("/")
|
555
|
-
|
556
|
-
puts "supports expanding" if pattern.respond_to? :expand
|
557
|
-
puts "supports generating templates" if pattern.respond_to? :to_templates
|
558
|
-
```
|
559
|
-
|
560
|
-
Alternatively, you can handle a `NotImplementedError` raised from such a method.
|
561
|
-
|
562
|
-
``` ruby
|
563
|
-
require 'mustermann'
|
564
|
-
pattern = Mustermann.new("/")
|
565
|
-
|
566
|
-
begin
|
567
|
-
p pattern.to_templates
|
568
|
-
rescue NotImplementedError
|
569
|
-
puts "does not support generating templates"
|
570
|
-
end
|
571
|
-
```
|
572
|
-
|
573
|
-
This behavior corresponds to what Ruby does, for instance for [`fork`](http://ruby-doc.org/core-2.1.1/NotImplementedError.html).
|
574
|
-
|
575
|
-
<a name="-available-options"></a>
|
576
|
-
## Available Options
|
577
|
-
|
578
|
-
<a name="-available-options--capture"></a>
|
579
|
-
### `capture`
|
580
|
-
|
581
|
-
Supported by: All types except `identity`, `shell` and `simple` patterns.
|
582
|
-
|
583
|
-
Most pattern types support changing the strings named captures will match via the `capture` options.
|
584
|
-
|
585
|
-
Possible values for a capture:
|
586
|
-
|
587
|
-
``` ruby
|
588
|
-
# String: Matches the given string (or any URI encoded version of it)
|
589
|
-
Mustermann.new('/index.:ext', capture: 'png')
|
590
|
-
|
591
|
-
# Regexp: Matches the Regular expression
|
592
|
-
Mustermann.new('/:id', capture: /\d+/)
|
593
|
-
|
594
|
-
# Symbol: Matches POSIX character class
|
595
|
-
Mustermann.new('/:id', capture: :digit)
|
596
|
-
|
597
|
-
# Array of the above: Matches anything in the array
|
598
|
-
Mustermann.new('/:id_or_slug', capture: [/\d+/, :word])
|
599
|
-
|
600
|
-
# Hash of the above: Looks up the hash entry by capture name and uses value for matching
|
601
|
-
Mustermann.new('/:id.:ext', capture: { id: /\d+/, ext: ['png', 'jpg'] })
|
602
|
-
```
|
603
|
-
|
604
|
-
Available POSIX character classes are: `:alnum`, `:alpha`, `:blank`, `:cntrl`, `:digit`, `:graph`, `:lower`, `:print`, `:punct`, `:space`, `:upper`, `:xdigit`, `:word` and `:ascii`.
|
605
|
-
|
606
|
-
<a name="-available-options--except"></a>
|
607
|
-
### `except`
|
608
|
-
|
609
|
-
Supported by: All types except `identity`, `shell` and `simple` patterns.
|
610
|
-
|
611
|
-
Given you supply a second pattern via the except option. Any string that would match the primary pattern but also matches the except pattern will not result in a successful match. Feel free to read that again. Or just take a look at this example:
|
612
|
-
|
613
|
-
``` ruby
|
614
|
-
pattern = Mustermann.new('/auth/*', except: '/auth/login')
|
615
|
-
pattern === '/auth/dunno' # => true
|
616
|
-
pattern === '/auth/login' # => false
|
617
|
-
```
|
618
|
-
|
619
|
-
Now, as said above, `except` treats the value as a pattern:
|
620
|
-
|
621
|
-
``` ruby
|
622
|
-
pattern = Mustermann.new('/*anything', type: :rails, except: '/*anything.png')
|
623
|
-
pattern === '/foo.jpg' # => true
|
624
|
-
pattern === '/foo.png' # => false
|
625
|
-
```
|
626
|
-
|
627
|
-
<a name="-available-options--greedy"></a>
|
628
|
-
### `greedy`
|
629
|
-
|
630
|
-
Supported by: All types except `identity` and `shell` patterns.
|
631
|
-
Default value: `true`
|
632
|
-
|
633
|
-
**Simple** patterns are greedy, meaning that for the pattern `:foo:bar?`, everything will be captured as `foo`, `bar` will always be `nil`. By setting `greedy` to `false`, `foo` will capture as little as possible (which in this case would only be the first letter), leaving the rest to `bar`.
|
634
|
-
|
635
|
-
**All other** supported patterns are semi-greedy. This means `:foo(.:bar)?` (`:foo(.:bar)` for Rails patterns) will capture everything before the *last* dot as `foo`. For these two pattern types, you can switch into non-greedy mode by setting the `greedy` option to false. In that case `foo` will only capture the part before the *first* dot.
|
636
|
-
|
637
|
-
Semi-greedy behavior is not specific to dots, it works with all characters or strings. For instance, `:a(foo:b)` will capture everything before the *last* `foo` as `a`, and `:foo(bar)?` will not capture a `bar` at the end.
|
638
|
-
|
639
|
-
``` ruby
|
640
|
-
pattern = Mustermann.new(':a.:b', greedy: true)
|
641
|
-
pattern.match('a.b.c.d') # => #<MatchData a:"a.b.c" b:"d">
|
642
|
-
|
643
|
-
pattern = Mustermann.new(':a.:b', greedy: false)
|
644
|
-
pattern.match('a.b.c.d') # => #<MatchData a:"a" b:"b.c.d">
|
645
|
-
```
|
646
|
-
|
647
|
-
<a name="-available-options--space_matches_plus"></a>
|
648
|
-
### `space_matches_plus`
|
649
|
-
|
650
|
-
Supported by: All types except `identity`, `regexp` and `shell` patterns.
|
651
|
-
Default value: `true`
|
652
|
-
|
653
|
-
Most pattern types will by default also match a plus sign for a space in the pattern:
|
654
|
-
|
655
|
-
``` ruby
|
656
|
-
Mustermann.new('a b') === 'a+b' # => true
|
657
|
-
```
|
658
|
-
|
659
|
-
You can disable this behavior via `space_matches_plus`:
|
660
|
-
|
661
|
-
``` ruby
|
662
|
-
Mustermann.new('a b', space_matches_plus: false) === 'a+b' # => false
|
663
|
-
```
|
664
|
-
|
665
|
-
**Important:** This setting has no effect on captures, captures will always keep plus signs as plus sings and spaces as spaces:
|
666
|
-
|
667
|
-
``` ruby
|
668
|
-
pattern = Mustermann.new(':x')
|
669
|
-
pattern.match('a b')[:x] # => 'a b'
|
670
|
-
pattern.match('a+b')[:x] # => 'a+b'
|
671
|
-
````
|
672
|
-
|
673
|
-
<a name="-available-options--uri_decode"></a>
|
674
|
-
### `uri_decode`
|
675
|
-
|
676
|
-
Supported by all pattern types.
|
677
|
-
Default value: `true`
|
678
|
-
|
679
|
-
Usually, characters in the pattern will also match the URI encoded version of these characters:
|
680
|
-
|
681
|
-
``` ruby
|
682
|
-
Mustermann.new('a b') === 'a b' # => true
|
683
|
-
Mustermann.new('a b') === 'a%20b' # => true
|
684
|
-
```
|
685
|
-
|
686
|
-
You can avoid this by setting `uri_decode` to `false`:
|
687
|
-
|
688
|
-
``` ruby
|
689
|
-
Mustermann.new('a b', uri_decode: false) === 'a b' # => true
|
690
|
-
Mustermann.new('a b', uri_decode: false) === 'a%20b' # => false
|
691
|
-
```
|
692
|
-
|
693
|
-
<a name="-available-options--ignore_unknown_options"></a>
|
694
|
-
### `ignore_unknown_options`
|
695
|
-
|
696
|
-
Supported by all patterns.
|
697
|
-
Default value: `false`
|
698
|
-
|
699
|
-
If you pass an option in that is not supported by the specific pattern type, Mustermann will raise an `ArgumentError`.
|
700
|
-
By setting `ignore_unknown_options` to `true`, it will happily ignore the option.
|
701
|
-
|
702
|
-
<a name="-performance"></a>
|
703
|
-
## Performance
|
704
|
-
|
705
|
-
It's generally a good idea to reuse pattern objects, since as much computation as possible is happening during object creation, so that the actual matching or expanding is quite fast.
|
706
|
-
|
707
|
-
Pattern objects should be treated as immutable. Their internals have been designed for both performance and low memory usage. To reduce pattern compilation, `Mustermann.new` and `Mustermann::Pattern.new` might return the same instance when given the same arguments, if that instance has not yet been garbage collected. However, this is not guaranteed, so do not rely on object identity.
|
708
|
-
|
709
|
-
### String Matching
|
710
|
-
|
711
|
-
When using a pattern instead of a regular expression for string matching, performance will usually be comparable.
|
712
|
-
|
713
|
-
In certain cases, Mustermann might outperform naive, equivalent regular expressions. It achieves this by using look-ahead and atomic groups in ways that work well with a backtracking, NFA-based regular expression engine (such as the Oniguruma/Onigmo engine used by Ruby). It can be difficult and error prone to construct complex regular expressions using these techniques by hand. This only applies to patterns generating an AST internally (all but [identity](#-pattern-details-identity), [shell](#-pattern-details-shell), [simple](#-pattern-details-simple) and [regexp](#-pattern-details-regexp) patterns).
|
714
|
-
|
715
|
-
When using a Mustermann pattern as a direct Regexp replacement (ie, via methods like `=~`, `match` or `===`), the overhead will be a single method dispatch, which some Ruby implementations might even eliminate with method inlining. This only applies to patterns using a regular expression internally (all but [identity](#-pattern-details-identity) and [shell](#-pattern-details-shell) patterns).
|
716
|
-
|
717
|
-
### Expanding
|
718
|
-
|
719
|
-
Pattern expansion significantly outperforms other, widely used Ruby tools for generating URLs from URL patterns in most use cases.
|
720
|
-
|
721
|
-
This comes with a few trade-offs:
|
722
|
-
|
723
|
-
* As with pattern compilation, as much computation as possible has been shifted to compiling expansion rules. This will add compilation overhead, which is why patterns only generate these rules on the first invocation to `Mustermann::Pattern#expand`. Create a `Mustermann::Expander` instance yourself to get better control over the point in time this computation should happen.
|
724
|
-
* Memory is sacrificed in favor of performance: The size of the expander object will grow linear with the number of possible combination for expansion keys ("/:foo/:bar" has one such combination, but "/(:foo/)?:bar?" has four)
|
725
|
-
* Parsing a params hash from a string generated from another params hash might not result in two identical hashes, and vice versa. Specifically, expanding ignores capture constraints, type casting and greediness.
|
726
|
-
* Partial expansion is (currently) not supported.
|
727
|
-
|
728
|
-
## Details on Pattern Types
|
729
|
-
|
730
|
-
<a name="-identity-pattern"></a>
|
731
|
-
### `identity`
|
732
|
-
|
733
|
-
**Supported options:**
|
734
|
-
[`uri_decode`](#-available-options--uri_decode),
|
735
|
-
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
736
|
-
|
737
|
-
<table>
|
738
|
-
<thead>
|
739
|
-
<tr>
|
740
|
-
<th>Syntax Element</th>
|
741
|
-
<th>Description</th>
|
742
|
-
</tr>
|
743
|
-
</thead>
|
744
|
-
<tbody>
|
745
|
-
<tr>
|
746
|
-
<td><i>any character</i></td>
|
747
|
-
<td>Matches exactly that character or a URI escaped version of it.</td>
|
748
|
-
</tr>
|
749
|
-
</tbody>
|
750
|
-
</table>
|
751
|
-
|
752
|
-
<a name="-regexp-pattern"></a>
|
753
|
-
### `regexp`
|
754
|
-
|
755
|
-
**Supported options:**
|
756
|
-
[`uri_decode`](#-available-options--uri_decode),
|
757
|
-
[`ignore_unknown_options`](#-available-options--ignore_unknown_options), `check_anchors`.
|
758
|
-
|
759
|
-
The pattern string (or actual Regexp instance) should not contain anchors (`^` outside of square brackets, `$`, `\A`, `\z`, or `\Z`).
|
760
|
-
Anchors will be injected where necessary by Mustermann.
|
761
|
-
|
762
|
-
By default, Mustermann will raise a `Mustermann::CompileError` if an anchor is encountered.
|
763
|
-
If you still want it to contain anchors at your own risk, set the `check_anchors` option to `false`.
|
764
|
-
|
765
|
-
Using anchors will break [peeking](#-peeking) and [concatenation](#-concatenation).
|
766
|
-
|
767
|
-
<table>
|
768
|
-
<thead>
|
769
|
-
<tr>
|
770
|
-
<th>Syntax Element</th>
|
771
|
-
<th>Description</th>
|
772
|
-
</tr>
|
773
|
-
</thead>
|
774
|
-
<tbody>
|
775
|
-
<tr>
|
776
|
-
<td><i>any string</i></td>
|
777
|
-
<td>Interpreted as regular expression.</td>
|
778
|
-
</tr>
|
779
|
-
</tbody>
|
780
|
-
</table>
|
781
|
-
|
782
|
-
<a name="-sinatra-pattern"></a>
|
783
|
-
### `sinatra`
|
784
|
-
|
785
|
-
**Supported options:**
|
786
|
-
[`capture`](#-available-options--capture),
|
787
|
-
[`except`](#-available-options--except),
|
788
|
-
[`greedy`](#-available-options--greedy),
|
789
|
-
[`space_matches_plus`](#-available-options--space_matches_plus),
|
790
|
-
[`uri_decode`](#-available-options--uri_decode),
|
791
|
-
[`ignore_unknown_options`](#-available-options--ignore_unknown_options).
|
792
|
-
|
793
|
-
<table>
|
794
|
-
<thead>
|
795
|
-
<tr>
|
796
|
-
<th>Syntax Element</th>
|
797
|
-
<th>Description</th>
|
798
|
-
</tr>
|
799
|
-
</thead>
|
800
|
-
<tbody>
|
801
|
-
<tr>
|
802
|
-
<td><b>:</b><i>name</i> <i><b>or</b></i> <b>{</b><i>name</i><b>}</b></td>
|
803
|
-
<td>
|
804
|
-
Captures anything but a forward slash in a semi-greedy fashion. Capture is named <i>name</i>.
|
805
|
-
Capture behavior can be modified with <tt>capture</tt> and <tt>greedy</tt> option.
|
806
|
-
</td>
|
807
|
-
</tr>
|
808
|
-
<tr>
|
809
|
-
<td><b>*</b><i>name</i> <i><b>or</b></i> <b>{+</b><i>name</i><b>}</b></td>
|
810
|
-
<td>
|
811
|
-
Captures anything in a non-greedy fashion. Capture is named <i>name</i>.
|
812
|
-
</td>
|
813
|
-
</tr>
|
814
|
-
<tr>
|
815
|
-
<td><b>*</b> <i><b>or</b></i> <b>{+splat}</b></td>
|
816
|
-
<td>
|
817
|
-
Captures anything in a non-greedy fashion. Capture is named splat.
|
818
|
-
It is always an array of captures, as you can use it more than once in a pattern.
|
819
|
-
</td>
|
820
|
-
</tr>
|
821
|
-
<tr>
|
822
|
-
<td><b>(</b><i>expression</i><b>)</b></td>
|
823
|
-
<td>
|
824
|
-
Enclosed <i>expression</i> is a group. Useful when combined with <tt>?</tt> to make it optional,
|
825
|
-
or to separate two elements that would otherwise be parsed as one.
|
826
|
-
</td>
|
827
|
-
</tr>
|
828
|
-
<tr>
|
829
|
-
<td><i>expression</i><b>|</b><i>expression</i><b>|</b><i>...</i></td>
|
830
|
-
<td>
|
831
|
-
Will match anything matching the nested expressions. May contain any other syntax element, including captures.
|
832
|
-
</td>
|
833
|
-
</tr>
|
834
|
-
<tr>
|
835
|
-
<td><i>x</i><b>?</b></td>
|
836
|
-
<td>Makes <i>x</i> optional. For instance, <tt>(foo)?</tt> matches <tt>foo</tt> or an empty string.</td>
|
837
|
-
</tr>
|
838
|
-
<tr>
|
839
|
-
<td><b>/</b></td>
|
840
|
-
<td>
|
841
|
-
Matches forward slash. Does not match URI encoded version of forward slash.
|
842
|
-
</td>
|
843
|
-
</tr>
|
844
|
-
<tr>
|
845
|
-
<td><b>\</b><i>x</i></td>
|
846
|
-
<td>Matches <i>x</i> or URI encoded version of <i>x</i>. For instance <tt>\*</tt> matches <tt>*</tt>.</td>
|
847
|
-
</tr>
|
848
|
-
<tr>
|
849
|
-
<td><i>any other character</i></td>
|
850
|
-
<td>Matches exactly that character or a URI encoded version of it.</td>
|
851
|
-
</tr>
|
852
|
-
</tbody>
|
853
|
-
</table>
|