mustermann19 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +10 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +22 -0
  7. data/README.md +1081 -0
  8. data/Rakefile +6 -0
  9. data/bench/capturing.rb +57 -0
  10. data/bench/regexp.rb +21 -0
  11. data/bench/simple_vs_sinatra.rb +23 -0
  12. data/bench/template_vs_addressable.rb +26 -0
  13. data/internals.md +64 -0
  14. data/lib/mustermann.rb +61 -0
  15. data/lib/mustermann/ast/compiler.rb +168 -0
  16. data/lib/mustermann/ast/expander.rb +134 -0
  17. data/lib/mustermann/ast/node.rb +160 -0
  18. data/lib/mustermann/ast/parser.rb +137 -0
  19. data/lib/mustermann/ast/pattern.rb +84 -0
  20. data/lib/mustermann/ast/transformer.rb +129 -0
  21. data/lib/mustermann/ast/translator.rb +108 -0
  22. data/lib/mustermann/ast/tree_renderer.rb +29 -0
  23. data/lib/mustermann/ast/validation.rb +43 -0
  24. data/lib/mustermann/caster.rb +117 -0
  25. data/lib/mustermann/equality_map.rb +48 -0
  26. data/lib/mustermann/error.rb +6 -0
  27. data/lib/mustermann/expander.rb +206 -0
  28. data/lib/mustermann/extension.rb +52 -0
  29. data/lib/mustermann/identity.rb +19 -0
  30. data/lib/mustermann/mapper.rb +98 -0
  31. data/lib/mustermann/pattern.rb +182 -0
  32. data/lib/mustermann/rails.rb +17 -0
  33. data/lib/mustermann/regexp_based.rb +30 -0
  34. data/lib/mustermann/regular.rb +26 -0
  35. data/lib/mustermann/router.rb +9 -0
  36. data/lib/mustermann/router/rack.rb +50 -0
  37. data/lib/mustermann/router/simple.rb +144 -0
  38. data/lib/mustermann/shell.rb +29 -0
  39. data/lib/mustermann/simple.rb +38 -0
  40. data/lib/mustermann/simple_match.rb +30 -0
  41. data/lib/mustermann/sinatra.rb +22 -0
  42. data/lib/mustermann/template.rb +48 -0
  43. data/lib/mustermann/to_pattern.rb +45 -0
  44. data/lib/mustermann/version.rb +3 -0
  45. data/mustermann.gemspec +31 -0
  46. data/spec/expander_spec.rb +105 -0
  47. data/spec/extension_spec.rb +296 -0
  48. data/spec/identity_spec.rb +83 -0
  49. data/spec/mapper_spec.rb +83 -0
  50. data/spec/mustermann_spec.rb +65 -0
  51. data/spec/pattern_spec.rb +49 -0
  52. data/spec/rails_spec.rb +522 -0
  53. data/spec/regexp_based_spec.rb +8 -0
  54. data/spec/regular_spec.rb +36 -0
  55. data/spec/router/rack_spec.rb +39 -0
  56. data/spec/router/simple_spec.rb +32 -0
  57. data/spec/shell_spec.rb +109 -0
  58. data/spec/simple_match_spec.rb +10 -0
  59. data/spec/simple_spec.rb +237 -0
  60. data/spec/sinatra_spec.rb +574 -0
  61. data/spec/support.rb +5 -0
  62. data/spec/support/coverage.rb +16 -0
  63. data/spec/support/env.rb +15 -0
  64. data/spec/support/expand_matcher.rb +27 -0
  65. data/spec/support/match_matcher.rb +39 -0
  66. data/spec/support/pattern.rb +39 -0
  67. data/spec/template_spec.rb +815 -0
  68. data/spec/to_pattern_spec.rb +20 -0
  69. metadata +301 -0
@@ -0,0 +1,160 @@
1
+ module Mustermann
2
+ # @see Mustermann::AST::Pattern
3
+ module AST
4
+ # @!visibility private
5
+ class Node
6
+ # @!visibility private
7
+ attr_accessor :payload
8
+
9
+ # @!visibility private
10
+ # @param [Symbol] name of the node
11
+ # @return [Class] factory for the node
12
+ def self.[](name)
13
+ @names ||= {}
14
+ #@names.fetch(name) { Object.const_get(constant_name(name)) }
15
+ @names.fetch(name) do
16
+ const_name = constant_name(name)
17
+ const_name.split("::").inject(Object){|current, const| current.const_get(const) }
18
+ end
19
+ end
20
+
21
+ # @!visibility private
22
+ # @param [Symbol] name of the node
23
+ # @return [String] qualified name of factory for the node
24
+ def self.constant_name(name)
25
+ return self.name if name.to_sym == :node
26
+ name = name.to_s.split(?_).map(&:capitalize).join
27
+ "#{self.name}::#{name}"
28
+ end
29
+
30
+ # Helper for creating a new instance and calling #parse on it.
31
+ # @return [Mustermann::AST::Node]
32
+ # @!visibility private
33
+ def self.parse(*args, &block)
34
+ new(*args).tap { |n| n.parse(&block) }
35
+ end
36
+
37
+ # @!visibility private
38
+ def initialize(payload = nil, options = {})
39
+ options, payload = payload, nil if payload.is_a?(Hash)
40
+ options.each { |key, value| public_send("#{key}=", value) }
41
+ self.payload = payload
42
+ end
43
+
44
+ # Double dispatch helper for reading from the buffer into the payload.
45
+ # @!visibility private
46
+ def parse
47
+ self.payload ||= []
48
+ while element = yield
49
+ payload << element
50
+ end
51
+ end
52
+
53
+ # Loop through all nodes that don't have child nodes.
54
+ # @!visibility private
55
+ def each_leaf(&block)
56
+ return enum_for(__method__) unless block_given?
57
+ called = false
58
+ Array(payload).each do |entry|
59
+ next unless entry.respond_to? :each_leaf
60
+ entry.each_leaf(&block)
61
+ called = true
62
+ end
63
+ yield(self) unless called
64
+ end
65
+
66
+ # @!visibility private
67
+ class Capture < Node
68
+ # @see Mustermann::AST::Node#parse
69
+ # @!visibility private
70
+ def parse
71
+ self.payload ||= ""
72
+ super
73
+ end
74
+
75
+ # @!visibility private
76
+ alias_method :name, :payload
77
+ end
78
+
79
+ # @!visibility private
80
+ class Char < Node
81
+ end
82
+
83
+ # AST node for template expressions.
84
+ # @!visibility private
85
+ class Expression < Node
86
+ # @!visibility private
87
+ attr_accessor :operator
88
+ end
89
+
90
+ # @!visibility private
91
+ class Group < Node
92
+ # @!visibility private
93
+ def initialize(payload = nil, options = {})
94
+ options, payload = payload, nil if payload.is_a?(Hash)
95
+ super(Array(payload), options)
96
+ end
97
+ end
98
+
99
+ # @!visibility private
100
+ class Optional < Node
101
+ end
102
+
103
+ # @!visibility private
104
+ class Root < Node
105
+ # @!visibility private
106
+ attr_accessor :pattern
107
+
108
+ # Will trigger transform.
109
+ #
110
+ # @see Mustermann::AST::Node.parse
111
+ # @!visibility private
112
+ def self.parse(string, &block)
113
+ root = new
114
+ root.pattern = string
115
+ root.parse(&block)
116
+ root
117
+ end
118
+ end
119
+
120
+ # @!visibility private
121
+ class Separator < Node
122
+ end
123
+
124
+ # @!visibility private
125
+ class Splat < Capture
126
+ # @see Mustermann::AST::Node::Capture#name
127
+ # @!visibility private
128
+ def name
129
+ "splat"
130
+ end
131
+ end
132
+
133
+ # @!visibility private
134
+ class NamedSplat < Splat
135
+ # @see Mustermann::AST::Node::Capture#name
136
+ # @!visibility private
137
+ alias_method :name, :payload
138
+ end
139
+
140
+ # AST node for template variables.
141
+ # @!visibility private
142
+ class Variable < Capture
143
+ # @!visibility private
144
+ attr_accessor :prefix, :explode
145
+ end
146
+
147
+ # @!visibility private
148
+ class WithLookAhead < Node
149
+ # @!visibility private
150
+ attr_accessor :head, :at_end
151
+
152
+ # @!visibility private
153
+ def initialize(payload, at_end)
154
+ self.head, *self.payload = Array(payload)
155
+ self.at_end = at_end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,137 @@
1
+ require 'mustermann/ast/node'
2
+ require 'forwardable'
3
+ require 'strscan'
4
+
5
+ module Mustermann
6
+ # @see Mustermann::AST::Pattern
7
+ module AST
8
+ # Simple, StringScanner based parser.
9
+ # @!visibility private
10
+ class Parser
11
+ # @param [String] string to be parsed
12
+ # @return [Mustermann::AST::Node] parse tree for string
13
+ # @!visibility private
14
+ def self.parse(string)
15
+ new.parse(string)
16
+ end
17
+
18
+ # Defines another grammar rule for first character.
19
+ #
20
+ # @see Mustermann::Rails
21
+ # @see Mustermann::Sinatra
22
+ # @see Mustermann::Template
23
+ # @!visibility private
24
+ def self.on(*chars, &block)
25
+ chars.each do |char|
26
+ define_method("read %p" % char, &block)
27
+ end
28
+ end
29
+
30
+ # Defines another grammar rule for a suffix.
31
+ #
32
+ # @see Mustermann::Sinatra
33
+ # @!visibility private
34
+ def self.suffix(pattern = /./, &block)
35
+ @suffix ||= []
36
+ @suffix << [pattern, block] if block
37
+ @suffix
38
+ end
39
+
40
+ # @!visibility private
41
+ attr_reader :buffer, :string
42
+
43
+ extend Forwardable
44
+ def_delegators :buffer, :eos?, :getch
45
+
46
+ # @param [String] string to be parsed
47
+ # @return [Mustermann::AST::Node] parse tree for string
48
+ # @!visibility private
49
+ def parse(string)
50
+ @string = string
51
+ @buffer = StringScanner.new(string)
52
+ node(:root, string) { read unless eos? }
53
+ end
54
+
55
+ # @example
56
+ # node(:char, 'x').compile =~ 'x' # => true
57
+ #
58
+ # @param [Symbol] type node type
59
+ # @return [Mustermann::AST::Node]
60
+ # @!visibility private
61
+ def node(type, *args, &block)
62
+ type = Node[type] unless type.respond_to? :new
63
+ block ? type.parse(*args, &block) : type.new(*args)
64
+ end
65
+
66
+ # Create a node for a character we don't have an explicit rule for.
67
+ #
68
+ # @param [String] char the character
69
+ # @return [Mustermann::AST::Node] the node
70
+ # @!visibility private
71
+ def default_node(char)
72
+ char == ?/ ? node(:separator, char) : node(:char, char)
73
+ end
74
+
75
+ # Reads the next element from the buffer.
76
+ # @return [Mustermann::AST::Node] next element
77
+ # @!visibility private
78
+ def read
79
+ char = getch
80
+ method = "read %p" % char
81
+ element = respond_to?(method) ? send(method, char) : default_node(char)
82
+ read_suffix(element)
83
+ end
84
+
85
+ # Checks for a potential suffix on the buffer.
86
+ # @param [Mustermann::AST::Node] element node without suffix
87
+ # @return [Mustermann::AST::Node] node with suffix
88
+ # @!visibility private
89
+ def read_suffix(element)
90
+ self.class.suffix.inject(element) do |ele, (regexp, callback)|
91
+ next ele unless payload = scan(regexp)
92
+ instance_exec(payload, ele, &callback)
93
+ end
94
+ end
95
+
96
+ # Wrapper around {StringScanner#scan} that turns strings into escaped
97
+ # regular expressions and returns a MatchData if the regexp has any
98
+ # named captures.
99
+ #
100
+ # @param [Regexp, String] regexp
101
+ # @see StringScanner#scan
102
+ # @return [String, MatchData, nil]
103
+ # @!visibility private
104
+ def scan(regexp)
105
+ regexp = Regexp.new(Regexp.escape(regexp)) unless regexp.is_a? Regexp
106
+ string = buffer.scan(regexp)
107
+ regexp.names.any? ? regexp.match(string) : string
108
+ end
109
+
110
+ # Asserts a regular expression matches what's next on the buffer.
111
+ # Will return corresponding MatchData if regexp includes named captures.
112
+ #
113
+ # @param [Regexp] regexp expected to match
114
+ # @return [String, MatchData] the match
115
+ # @raise [Mustermann::ParseError] if expectation wasn't met
116
+ # @!visibility private
117
+ def expect(regexp, options = {})
118
+ scan(regexp)|| unexpected(options)
119
+ end
120
+
121
+ # Helper for raising an exception for an unexpected character.
122
+ # Will read character from buffer if buffer is passed in.
123
+ #
124
+ # @param [String, nil] char the unexpected character
125
+ # @raise [Mustermann::ParseError, Exception]
126
+ # @!visibility private
127
+ def unexpected(char = getch, options = {})
128
+ options, char = char, getch if char.is_a?(Hash)
129
+ exception = options.fetch(:exception, ParseError)
130
+ char = "space" if char == " "
131
+ raise exception, "unexpected #{char || "end of string"} while parsing #{string.inspect}"
132
+ end
133
+ end
134
+
135
+ #private_constant :Parser
136
+ end
137
+ end
@@ -0,0 +1,84 @@
1
+ require 'mustermann/ast/parser'
2
+ require 'mustermann/ast/compiler'
3
+ require 'mustermann/ast/transformer'
4
+ require 'mustermann/ast/validation'
5
+ require 'mustermann/regexp_based'
6
+ require 'mustermann/expander'
7
+ require 'mustermann/equality_map'
8
+
9
+ module Mustermann
10
+ # @see Mustermann::AST::Pattern
11
+ module AST
12
+ # Superclass for pattern styles that parse an AST from the string pattern.
13
+ # @abstract
14
+ class Pattern < Mustermann::RegexpBased
15
+ supported_options :capture, :except, :greedy, :space_matches_plus
16
+
17
+ extend Forwardable, SingleForwardable
18
+ single_delegate on: :parser, suffix: :parser
19
+ instance_delegate %w[parser compiler transformer validation].map(&:to_sym) => 'self.class'
20
+ instance_delegate parse: :parser, transform: :transformer, validate: :validation
21
+
22
+ # @api private
23
+ # @return [#parse] parser object for pattern
24
+ # @!visibility private
25
+ def self.parser
26
+ return Parser if self == AST::Pattern
27
+ const_set :Parser, Class.new(superclass.parser) unless const_defined? :Parser, false
28
+ const_get :Parser
29
+ end
30
+
31
+ # @api private
32
+ # @return [#compile] compiler object for pattern
33
+ # @!visibility private
34
+ def self.compiler
35
+ Compiler
36
+ end
37
+
38
+ # @api private
39
+ # @return [#transform] compiler object for pattern
40
+ # @!visibility private
41
+ def self.transformer
42
+ Transformer
43
+ end
44
+
45
+ # @api private
46
+ # @return [#validate] validation object for pattern
47
+ # @!visibility private
48
+ def self.validation
49
+ Validation
50
+ end
51
+
52
+ # @!visibility private
53
+ def compile(options = {})
54
+ options[:except] &&= parse options[:except]
55
+ compiler.compile(to_ast, options)
56
+ rescue CompileError => error
57
+ error.message << ": %p" % @string
58
+ raise error
59
+ end
60
+
61
+ # Internal AST representation of pattern.
62
+ # @!visibility private
63
+ def to_ast
64
+ @ast_cache ||= EqualityMap.new
65
+ @ast_cache.fetch(@string) { validate(transform(parse(@string))) }
66
+ end
67
+
68
+ # All AST-based pattern implementations support expanding.
69
+ #
70
+ # @example (see Mustermann::Pattern#expand)
71
+ # @param (see Mustermann::Pattern#expand)
72
+ # @return (see Mustermann::Pattern#expand)
73
+ # @raise (see Mustermann::Pattern#expand)
74
+ # @see Mustermann::Pattern#expand
75
+ # @see Mustermann::Expander
76
+ def expand(values = {})
77
+ @expander ||= Mustermann::Expander.new(self)
78
+ @expander.expand(values)
79
+ end
80
+
81
+ private :compile
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,129 @@
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
+
9
+ # Transforms a tree.
10
+ # @note might mutate handed in tree instead of creating a new one
11
+ # @param [Mustermann::AST::Node] tree to be transformed
12
+ # @return [Mustermann::AST::Node] transformed tree
13
+ # @!visibility private
14
+ def self.transform(tree)
15
+ new.translate(tree)
16
+ end
17
+
18
+ translate(:node) { self }
19
+ translate(:group, :root) do
20
+ self.payload = t(payload)
21
+ self
22
+ end
23
+
24
+ # URI expression transformations depending on operator
25
+ # @!visibility private
26
+ class ExpressionTransform < NodeTranslator
27
+ register :expression
28
+
29
+ # @!visibility private
30
+ Operator ||= Struct.new(:separator, :allow_reserved, :prefix, :parametric)
31
+
32
+ # Operators available for expressions.
33
+ # @!visibility private
34
+ OPERATORS ||= {
35
+ nil => Operator.new(?,, false, false, false), ?+ => Operator.new(?,, true, false, false),
36
+ ?# => Operator.new(?,, true, ?#, false), ?. => Operator.new(?., false, ?., false),
37
+ ?/ => Operator.new(?/, false, ?/, false), ?; => Operator.new(?;, false, ?;, true),
38
+ ?? => Operator.new(?&, false, ??, true), ?& => Operator.new(?&, false, ?&, true)
39
+ }
40
+
41
+ # Sets operator and inserts separators in between variables.
42
+ # @!visibility private
43
+ def translate
44
+ self.operator = OPERATORS.fetch(operator) { raise CompileError, "#{operator} operator not supported" }
45
+ separator = Node[:separator].new(operator.separator)
46
+ prefix = Node[:separator].new(operator.prefix)
47
+ self.payload = Array(payload.inject { |list, element| Array(list) << t(separator) << t(element) })
48
+ payload.unshift(prefix) if operator.prefix
49
+ self
50
+ end
51
+ end
52
+
53
+ # Inserts with_look_ahead nodes wherever appropriate
54
+ # @!visibility private
55
+ class ArrayTransform < NodeTranslator
56
+ register Array
57
+
58
+ # the new array
59
+ # @!visibility private
60
+ def payload
61
+ @payload ||= []
62
+ end
63
+
64
+ # buffer for potential look ahead
65
+ # @!visibility private
66
+ def lookahead_buffer
67
+ @lookahead_buffer ||= []
68
+ end
69
+
70
+ # transform the array
71
+ # @!visibility private
72
+ def translate
73
+ each { |e| track t(e) }
74
+ payload.concat create_lookahead(lookahead_buffer, true)
75
+ end
76
+
77
+ # handle a single element from the array
78
+ # @!visibility private
79
+ def track(element)
80
+ return list_for(element) << element if lookahead_buffer.empty?
81
+ return lookahead_buffer << element if lookahead? element
82
+
83
+ lookahead = lookahead_buffer.dup
84
+ lookahead = create_lookahead(lookahead, false) if element.is_a? Node[:separator]
85
+ lookahead_buffer.clear
86
+
87
+ payload.concat(lookahead) << element
88
+ end
89
+
90
+ # turn look ahead buffer into look ahead node
91
+ # @!visibility private
92
+ def create_lookahead(elements, *args)
93
+ return elements unless elements.size > 1
94
+ [Node[:with_look_ahead].new(elements, *args)]
95
+ end
96
+
97
+ # can the given element be used in a look-ahead?
98
+ # @!visibility private
99
+ def lookahead?(element, in_lookahead = false)
100
+ case element
101
+ when Node[:char] then in_lookahead
102
+ when Node[:group] then lookahead_payload?(element.payload, in_lookahead)
103
+ when Node[:optional] then lookahead?(element.payload, true) or expect_lookahead?(element.payload)
104
+ end
105
+ end
106
+
107
+ # does the list of elements look look-ahead-ish to you?
108
+ # @!visibility private
109
+ def lookahead_payload?(payload, in_lookahead)
110
+ return unless payload[0..-2].all? { |e| lookahead?(e, in_lookahead) }
111
+ expect_lookahead?(payload.last) or lookahead?(payload.last, in_lookahead)
112
+ end
113
+
114
+ # can the current element deal with a look-ahead?
115
+ # @!visibility private
116
+ def expect_lookahead?(element)
117
+ return element.class == Node[:capture] unless element.is_a? Node[:group]
118
+ element.payload.all? { |e| expect_lookahead?(e) }
119
+ end
120
+
121
+ # helper method for deciding where to put an element for now
122
+ # @!visibility private
123
+ def list_for(element)
124
+ expect_lookahead?(element) ? lookahead_buffer : payload
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end