mustermann19 0.3.1

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.
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