mustermann 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|