mustermann 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +2 -1
- data/.travis.yml +4 -3
- data/.yardopts +1 -0
- data/README.md +53 -10
- data/Rakefile +4 -1
- data/bench/capturing.rb +42 -9
- data/bench/template_vs_addressable.rb +3 -0
- data/internals.md +64 -0
- data/lib/mustermann.rb +14 -5
- data/lib/mustermann/ast/compiler.rb +150 -0
- data/lib/mustermann/ast/expander.rb +112 -0
- data/lib/mustermann/ast/node.rb +155 -0
- data/lib/mustermann/ast/parser.rb +136 -0
- data/lib/mustermann/ast/pattern.rb +89 -0
- data/lib/mustermann/ast/transformer.rb +121 -0
- data/lib/mustermann/ast/translator.rb +111 -0
- data/lib/mustermann/ast/tree_renderer.rb +29 -0
- data/lib/mustermann/ast/validation.rb +40 -0
- data/lib/mustermann/error.rb +4 -12
- data/lib/mustermann/extension.rb +3 -6
- data/lib/mustermann/identity.rb +4 -4
- data/lib/mustermann/pattern.rb +34 -5
- data/lib/mustermann/rails.rb +7 -16
- data/lib/mustermann/regexp_based.rb +4 -4
- data/lib/mustermann/shell.rb +4 -4
- data/lib/mustermann/simple.rb +1 -1
- data/lib/mustermann/simple_match.rb +2 -2
- data/lib/mustermann/sinatra.rb +10 -20
- data/lib/mustermann/template.rb +11 -104
- data/lib/mustermann/version.rb +1 -1
- data/mustermann.gemspec +1 -1
- data/spec/extension_spec.rb +143 -0
- data/spec/mustermann_spec.rb +41 -0
- data/spec/pattern_spec.rb +16 -6
- data/spec/rails_spec.rb +77 -9
- data/spec/sinatra_spec.rb +6 -0
- data/spec/support.rb +5 -78
- data/spec/support/coverage.rb +18 -0
- data/spec/support/env.rb +6 -0
- data/spec/support/expand_matcher.rb +27 -0
- data/spec/support/match_matcher.rb +39 -0
- data/spec/support/pattern.rb +28 -0
- metadata +43 -43
- data/.test_queue_stats +0 -0
- data/lib/mustermann/ast.rb +0 -403
- data/spec/ast_spec.rb +0 -8
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'mustermann/ast/translator'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
module AST
|
5
|
+
# Takes a tree, turns it into an even better tree.
|
6
|
+
# @!visibility private
|
7
|
+
class Transformer < Translator
|
8
|
+
# @!visibility private
|
9
|
+
Operator ||= Struct.new(:separator, :allow_reserved, :prefix, :parametric)
|
10
|
+
|
11
|
+
# Operators available for expressions.
|
12
|
+
# @!visibility private
|
13
|
+
OPERATORS ||= {
|
14
|
+
nil => Operator.new(?,, false, false, false), ?+ => Operator.new(?,, true, false, false),
|
15
|
+
?# => Operator.new(?,, true, ?#, false), ?. => Operator.new(?., false, ?., false),
|
16
|
+
?/ => Operator.new(?/, false, ?/, false), ?; => Operator.new(?;, false, ?;, true),
|
17
|
+
?? => Operator.new(?&, false, ??, true), ?& => Operator.new(?&, false, ?&, true)
|
18
|
+
}
|
19
|
+
|
20
|
+
# Transforms a tree.
|
21
|
+
# @note might mutate handed in tree instead of creating a new one
|
22
|
+
# @param [Mustermann::AST::Node] tree to be transformed
|
23
|
+
# @return [Mustermann::AST::Node] transformed tree
|
24
|
+
# @!visibility private
|
25
|
+
def self.transform(ast)
|
26
|
+
new.translate(ast)
|
27
|
+
end
|
28
|
+
|
29
|
+
translate(:node) { self }
|
30
|
+
|
31
|
+
translate(:expression) do
|
32
|
+
self.operator = OPERATORS.fetch(operator) { raise CompileError, "#{operator} operator not supported" }
|
33
|
+
separator = Node[:separator].new(operator.separator)
|
34
|
+
prefix = Node[:separator].new(operator.prefix)
|
35
|
+
self.payload = Array(payload.inject { |list, element| Array(list) << t(separator) << t(element) })
|
36
|
+
payload.unshift(prefix) if operator.prefix
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
translate(:group, :root) do
|
41
|
+
self.payload = t(payload)
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# Inserts with_look_ahead nodes wherever appropriate
|
46
|
+
# @!visibility private
|
47
|
+
class ArrayTransform < NodeTranslator
|
48
|
+
register Array
|
49
|
+
|
50
|
+
# the new array
|
51
|
+
# @!visibility private
|
52
|
+
def payload
|
53
|
+
@payload ||= []
|
54
|
+
end
|
55
|
+
|
56
|
+
# buffer for potential look ahead
|
57
|
+
# @!visibility private
|
58
|
+
def lookahead_buffer
|
59
|
+
@lookahead_buffer ||= []
|
60
|
+
end
|
61
|
+
|
62
|
+
# transform the array
|
63
|
+
# @!visibility private
|
64
|
+
def translate
|
65
|
+
each { |e| track t(e) }
|
66
|
+
payload.concat create_lookahead(lookahead_buffer, true)
|
67
|
+
end
|
68
|
+
|
69
|
+
# handle a single element from the array
|
70
|
+
# @!visibility private
|
71
|
+
def track(element)
|
72
|
+
return list_for(element) << element if lookahead_buffer.empty?
|
73
|
+
return lookahead_buffer << element if lookahead? element
|
74
|
+
|
75
|
+
lookahead = lookahead_buffer.dup
|
76
|
+
lookahead = create_lookahead(lookahead, false) if element.is_a? Node[:separator]
|
77
|
+
lookahead_buffer.clear
|
78
|
+
|
79
|
+
payload.concat(lookahead) << element
|
80
|
+
end
|
81
|
+
|
82
|
+
# turn look ahead buffer into look ahead node
|
83
|
+
# @!visibility private
|
84
|
+
def create_lookahead(elements, *args)
|
85
|
+
return elements unless elements.size > 1
|
86
|
+
[Node[:with_look_ahead].new(elements, *args)]
|
87
|
+
end
|
88
|
+
|
89
|
+
# can the given element be used in a look-ahead?
|
90
|
+
# @!visibility private
|
91
|
+
def lookahead?(element, in_lookahead = false)
|
92
|
+
case element
|
93
|
+
when Node[:char] then in_lookahead
|
94
|
+
when Node[:group] then lookahead_payload?(element.payload, in_lookahead)
|
95
|
+
when Node[:optional] then lookahead?(element.payload, true) or expect_lookahead?(element.payload)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# does the list of elements look look-ahead-ish to you?
|
100
|
+
# @!visibility private
|
101
|
+
def lookahead_payload?(payload, in_lookahead)
|
102
|
+
return unless payload[0..-2].all? { |e| lookahead?(e, in_lookahead) }
|
103
|
+
expect_lookahead?(payload.last) or lookahead?(payload.last, in_lookahead)
|
104
|
+
end
|
105
|
+
|
106
|
+
# can the current element deal with a look-ahead?
|
107
|
+
# @!visibility private
|
108
|
+
def expect_lookahead?(element)
|
109
|
+
return element.class == Node[:capture] unless element.is_a? Node[:group]
|
110
|
+
element.payload.all? { |e| expect_lookahead?(e) }
|
111
|
+
end
|
112
|
+
|
113
|
+
# helper method for deciding where to put an element for now
|
114
|
+
# @!visibility private
|
115
|
+
def list_for(element)
|
116
|
+
expect_lookahead?(element) ? lookahead_buffer : payload
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'mustermann/ast/node'
|
2
|
+
require 'mustermann/error'
|
3
|
+
require 'delegate'
|
4
|
+
|
5
|
+
module Mustermann
|
6
|
+
module AST
|
7
|
+
# Implements translator pattern
|
8
|
+
#
|
9
|
+
# @abstract
|
10
|
+
# @!visibility private
|
11
|
+
class Translator
|
12
|
+
# Encapsulates a single node translation
|
13
|
+
# @!visibility private
|
14
|
+
class NodeTranslator < DelegateClass(Node)
|
15
|
+
# @param [Array<Symbol, Class>] list of types to register for.
|
16
|
+
# @!visibility private
|
17
|
+
def self.register(*types)
|
18
|
+
types.each do |type|
|
19
|
+
type = Node.constant_name(type) if type.is_a? Symbol
|
20
|
+
translator.dispatch_table[type.to_s] = self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param node [Mustermann::AST::Node, Object]
|
25
|
+
# @param translator [Mustermann::AST::Translator]
|
26
|
+
#
|
27
|
+
# @!visibility private
|
28
|
+
def initialize(node, translator)
|
29
|
+
@translator = translator
|
30
|
+
super(node)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @!visibility private
|
34
|
+
attr_reader :translator
|
35
|
+
|
36
|
+
# shorthand for translating a nested object
|
37
|
+
# @!visibility private
|
38
|
+
def t(*args, &block)
|
39
|
+
return translator unless args.any?
|
40
|
+
translator.translate(*args, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
# @!visibility private
|
44
|
+
alias_method :node, :__getobj__
|
45
|
+
end
|
46
|
+
|
47
|
+
# maps types to translations
|
48
|
+
# @!visibility private
|
49
|
+
def self.dispatch_table
|
50
|
+
@dispatch_table ||= {}
|
51
|
+
end
|
52
|
+
|
53
|
+
# some magic sauce so {NodeTranslator}s know whom to talk to for {#register}
|
54
|
+
# @!visibility private
|
55
|
+
def self.inherited(subclass)
|
56
|
+
node_translator = Class.new(NodeTranslator)
|
57
|
+
node_translator.define_singleton_method(:translator) { subclass }
|
58
|
+
subclass.const_set(:NodeTranslator, node_translator)
|
59
|
+
super
|
60
|
+
end
|
61
|
+
|
62
|
+
# DSL-ish method for specifying the exception class to use.
|
63
|
+
# @!visibility private
|
64
|
+
def self.raises(error)
|
65
|
+
define_method(:error_class) { error }
|
66
|
+
end
|
67
|
+
|
68
|
+
# DSL method for defining single method translations.
|
69
|
+
# @!visibility private
|
70
|
+
def self.translate(*types, &block)
|
71
|
+
Class.new(const_get(:NodeTranslator)) do
|
72
|
+
register(*types)
|
73
|
+
define_method(:translate, &block)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
raises Mustermann::Error
|
78
|
+
|
79
|
+
# @param [Mustermann::AST::Node, Object] object to translate
|
80
|
+
# @return decorator encapsulating translation
|
81
|
+
#
|
82
|
+
# @!visibility private
|
83
|
+
def decorator_for(node)
|
84
|
+
factory = node.class.ancestors.inject(nil) { |d,a| d || self.class.dispatch_table[a.name] }
|
85
|
+
raise error_class, "#{self.class}: Cannot translate #{node.class}" unless factory
|
86
|
+
factory.new(node, self)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Start the translation dance for a (sub)tree.
|
90
|
+
# @!visibility private
|
91
|
+
def translate(node, *args, &block)
|
92
|
+
result = decorator_for(node).translate(*args, &block)
|
93
|
+
result = result.node while result.is_a? NodeTranslator
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
97
|
+
# Reusing URI::Parser instance gives > 50% performance boost for some operations, like expanding.
|
98
|
+
# @!visibility private
|
99
|
+
def uri_parser
|
100
|
+
@uri_parser ||= URI::Parser.new
|
101
|
+
end
|
102
|
+
|
103
|
+
# @return [String] escaped character
|
104
|
+
# @!visibility private
|
105
|
+
def escape(char, parser: uri_parser, escape: parser.regexp[:UNSAFE], also_escape: nil)
|
106
|
+
escape = Regexp.union(also_escape, escape) if also_escape
|
107
|
+
char =~ escape ? parser.escape(char, Regexp.union(*escape)) : char
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'mustermann/ast/translator'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
module AST
|
5
|
+
# Turns an AST into a human readable string.
|
6
|
+
# @!visibility private
|
7
|
+
class TreeRenderer < Translator
|
8
|
+
# @example
|
9
|
+
# Mustermann::AST::TreeRenderer.render Mustermann::Sinatra::Parser.parse('/foo')
|
10
|
+
#
|
11
|
+
# @!visibility private
|
12
|
+
def self.render(ast)
|
13
|
+
new.translate(ast)
|
14
|
+
end
|
15
|
+
|
16
|
+
translate(Object) { inspect }
|
17
|
+
translate(Array) { map { |e| "\n" << t(e) }.join.gsub("\n", "\n ") }
|
18
|
+
translate(:node) { "#{t.type(node)} #{t(payload)}" }
|
19
|
+
translate(:with_look_ahead) { "#{t.type(node)} #{t(head)} #{t(payload)}" }
|
20
|
+
|
21
|
+
# Turns a class name into a node identifier.
|
22
|
+
#
|
23
|
+
# @!visibility private
|
24
|
+
def type(node)
|
25
|
+
node.class.name[/[^:]+$/].split(/(?<=.)(?=[A-Z])/).map(&:downcase).join(?_)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'mustermann/ast/translator'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
module AST
|
5
|
+
# Checks the AST for certain validations, like correct capture names.
|
6
|
+
#
|
7
|
+
# Internally a poor man's visitor (abusing translator to not have to impelment a visitor).
|
8
|
+
# @!visibility private
|
9
|
+
class Validation < Translator
|
10
|
+
# Runs validations.
|
11
|
+
#
|
12
|
+
# @param [Mustermann::AST::Node] ast to be validated
|
13
|
+
# @return [Mustermann::AST::Node] the validated ast
|
14
|
+
# @raises [Mustermann::AST::CompileError] if validation fails
|
15
|
+
# @!visibility private
|
16
|
+
def self.validate(ast)
|
17
|
+
new.translate(ast)
|
18
|
+
ast
|
19
|
+
end
|
20
|
+
|
21
|
+
translate(Object, :splat) {}
|
22
|
+
translate(:node) { t(payload) }
|
23
|
+
translate(Array) { each { |p| t(p)} }
|
24
|
+
|
25
|
+
translate(:capture, :variable, :named_splat) do
|
26
|
+
raise CompileError, "capture name can't be empty" if name.nil? or name.empty?
|
27
|
+
raise CompileError, "capture name must start with underscore or lower case letter" unless name =~ /^[a-z_]/
|
28
|
+
raise CompileError, "capture name can't be #{name}" if name == "splat" or name == "captures"
|
29
|
+
raise CompileError, "can't use the same capture name twice" if t.names.include? name
|
30
|
+
t.names << name
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Array<String>] list of capture names in tree
|
34
|
+
# @!visibility private
|
35
|
+
def names
|
36
|
+
@names ||= []
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/mustermann/error.rb
CHANGED
@@ -1,14 +1,6 @@
|
|
1
1
|
module Mustermann
|
2
|
-
# Raised if anything goes wrong while generating a {Pattern}.
|
3
|
-
|
4
|
-
|
5
|
-
# Raised if anything goes wrong while
|
6
|
-
class CompileError < Error; end
|
7
|
-
|
8
|
-
# Raised if anything goes wrong while parsing a {Pattern}.
|
9
|
-
class ParseError < Error; end
|
10
|
-
|
11
|
-
#@!visibility private
|
12
|
-
class UnexpectedClosingGroup < ParseError; end
|
13
|
-
private_constant :UnexpectedClosingGroup
|
2
|
+
Error ||= Class.new(StandardError) # Raised if anything goes wrong while generating a {Pattern}.
|
3
|
+
CompileError ||= Class.new(Error) # Raised if anything goes wrong while compiling a {Pattern}.
|
4
|
+
ParseError ||= Class.new(Error) # Raised if anything goes wrong while parsing a {Pattern}.
|
5
|
+
ExpandError ||= Class.new(Error) # Raised if anything goes wrong while expanding a {Pattern}.
|
14
6
|
end
|
data/lib/mustermann/extension.rb
CHANGED
@@ -29,8 +29,8 @@ module Mustermann
|
|
29
29
|
pattern[:except] = except if except
|
30
30
|
pattern[:capture] = capture if capture
|
31
31
|
|
32
|
-
if settings.respond_to? :pattern
|
33
|
-
pattern.merge!
|
32
|
+
if settings.respond_to? :pattern and settings.pattern?
|
33
|
+
pattern.merge! settings.pattern do |key, local, global|
|
34
34
|
next local unless local.is_a? Hash
|
35
35
|
next global.merge(local) if global.is_a? Hash
|
36
36
|
Hash.new(global).merge! local
|
@@ -38,10 +38,7 @@ module Mustermann
|
|
38
38
|
end
|
39
39
|
|
40
40
|
path = Mustermann.new(path, **pattern)
|
41
|
-
|
42
|
-
if defined? Template and path.is_a? Template
|
43
|
-
condition { params.merge! path.params(request.path_info) }
|
44
|
-
end
|
41
|
+
condition { params.merge! path.params(captures: Array(params[:captures]), offset: -1) }
|
45
42
|
end
|
46
43
|
|
47
44
|
super(verb, path, block, options)
|
data/lib/mustermann/identity.rb
CHANGED
@@ -6,12 +6,12 @@ module Mustermann
|
|
6
6
|
# @example
|
7
7
|
# Mustermann.new('/:foo', type: :identity) === '/bar' # => false
|
8
8
|
#
|
9
|
-
# @see Pattern
|
9
|
+
# @see Mustermann::Pattern
|
10
10
|
# @see file:README.md#identity Syntax description in the README
|
11
11
|
class Identity < Pattern
|
12
|
-
# @param (see Pattern#===)
|
13
|
-
# @return (see Pattern#===)
|
14
|
-
# @see (see Pattern#===)
|
12
|
+
# @param (see Mustermann::Pattern#===)
|
13
|
+
# @return (see Mustermann::Pattern#===)
|
14
|
+
# @see (see Mustermann::Pattern#===)
|
15
15
|
def ===(string)
|
16
16
|
unescape(string) == @string
|
17
17
|
end
|
data/lib/mustermann/pattern.rb
CHANGED
@@ -33,7 +33,7 @@ module Mustermann
|
|
33
33
|
# @param (see #initialize)
|
34
34
|
# @raise (see #initialize)
|
35
35
|
# @raise [ArgumentError] if some option is not supported
|
36
|
-
# @return [Pattern] a new instance of Pattern
|
36
|
+
# @return [Mustermann::Pattern] a new instance of Mustermann::Pattern
|
37
37
|
# @see #initialize
|
38
38
|
def self.new(string, ignore_unknown_options: false, **options)
|
39
39
|
unless ignore_unknown_options
|
@@ -47,7 +47,7 @@ module Mustermann
|
|
47
47
|
supported_options :uri_decode, :ignore_unknown_options
|
48
48
|
|
49
49
|
# @overload initialize(string, **options)
|
50
|
-
# @param [String] string the string
|
50
|
+
# @param [String] string the string representation of the pattern
|
51
51
|
# @param [Hash] options options for fine-tuning the pattern behavior
|
52
52
|
# @raise [Mustermann::Error] if the pattern can't be generated from the string
|
53
53
|
# @see file:README.md#Types_and_Options "Types and Options" in the README
|
@@ -66,7 +66,7 @@ module Mustermann
|
|
66
66
|
# @return [MatchData, Mustermann::SimpleMatch, nil] MatchData or similar object if the pattern matches.
|
67
67
|
# @see http://ruby-doc.org/core-2.0/Regexp.html#method-i-match Regexp#match
|
68
68
|
# @see http://ruby-doc.org/core-2.0/MatchData.html MatchData
|
69
|
-
# @see SimpleMatch
|
69
|
+
# @see Mustermann::SimpleMatch
|
70
70
|
def match(string)
|
71
71
|
SimpleMatch.new(string) if self === string
|
72
72
|
end
|
@@ -100,10 +100,10 @@ module Mustermann
|
|
100
100
|
|
101
101
|
# @param [String] string the string to match against
|
102
102
|
# @return [Hash{String: String, Array<String>}, nil] Sinatra style params if pattern matches.
|
103
|
-
def params(string = nil, captures: nil)
|
103
|
+
def params(string = nil, captures: nil, offset: 0)
|
104
104
|
return unless captures ||= match(string)
|
105
105
|
params = named_captures.map do |name, positions|
|
106
|
-
values = positions.map { |pos| map_param(name, captures[pos]) }.flatten
|
106
|
+
values = positions.map { |pos| map_param(name, captures[pos + offset]) }.flatten
|
107
107
|
values = values.first if values.size < 2 and not always_array? name
|
108
108
|
[name, values]
|
109
109
|
end
|
@@ -111,6 +111,35 @@ module Mustermann
|
|
111
111
|
Hash[params]
|
112
112
|
end
|
113
113
|
|
114
|
+
# @note This method is only implemented by certain subclasses.
|
115
|
+
#
|
116
|
+
# @example Expanding a pattern
|
117
|
+
# pattern = Mustermann.new('/:name(.:ext)?')
|
118
|
+
# pattern.expand(name: 'hello') # => "/hello"
|
119
|
+
# pattern.expand(name: 'hello', ext: 'png') # => "/hello.png"
|
120
|
+
#
|
121
|
+
# @example Checking if a pattern supports expanding
|
122
|
+
# if pattern.respond_to? :expand
|
123
|
+
# pattern.expand(name: "foo")
|
124
|
+
# else
|
125
|
+
# warn "does not support expanding"
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# @param [Hash{Symbol: #to_s, Array<#to_s>}] **values values to use for expansion
|
129
|
+
# @return [String] expanded string
|
130
|
+
# @raise [NotImplementedError] raised if expand is not supported.
|
131
|
+
# @raise [ArgumentError] raised if a value is missing or unknown
|
132
|
+
def expand(**values)
|
133
|
+
raise NotImplementedError, "expanding not supported by #{self.class}"
|
134
|
+
end
|
135
|
+
|
136
|
+
# @!visibility private
|
137
|
+
# @return [Boolean]
|
138
|
+
# @see Object#respond_to?
|
139
|
+
def respond_to?(method, *args)
|
140
|
+
method.to_s == 'expand' ? false : super
|
141
|
+
end
|
142
|
+
|
114
143
|
# @!visibility private
|
115
144
|
def inspect
|
116
145
|
"#<%p:%p>" % [self.class, @string]
|
data/lib/mustermann/rails.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'mustermann/ast'
|
1
|
+
require 'mustermann/ast/pattern'
|
2
2
|
|
3
3
|
module Mustermann
|
4
4
|
# Rails style pattern implementation.
|
@@ -6,21 +6,12 @@ module Mustermann
|
|
6
6
|
# @example
|
7
7
|
# Mustermann.new('/:foo', type: :rails) === '/bar' # => true
|
8
8
|
#
|
9
|
-
# @see Pattern
|
9
|
+
# @see Mustermann::Pattern
|
10
10
|
# @see file:README.md#rails Syntax description in the README
|
11
|
-
class Rails < AST
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
when ?* then NamedSplat.parse { buffer.scan(/\w+/) }
|
17
|
-
when ?/ then Separator.new(char)
|
18
|
-
when ?( then Optional.new(Group.parse { parse_buffer(buffer) })
|
19
|
-
when ?: then Capture.parse { buffer.scan(/\w+/) }
|
20
|
-
else Char.new(char)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
private :parse_element
|
11
|
+
class Rails < AST::Pattern
|
12
|
+
on(nil, ?)) { |c| unexpected(c) }
|
13
|
+
on(?*) { |c| node(:named_splat) { scan(/\w+/) } }
|
14
|
+
on(?() { |c| node(:optional, node(:group) { read unless scan(?)) }) }
|
15
|
+
on(?:) { |c| node(:capture) { scan(/\w+/) } }
|
25
16
|
end
|
26
17
|
end
|