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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -1
  3. data/.travis.yml +4 -3
  4. data/.yardopts +1 -0
  5. data/README.md +53 -10
  6. data/Rakefile +4 -1
  7. data/bench/capturing.rb +42 -9
  8. data/bench/template_vs_addressable.rb +3 -0
  9. data/internals.md +64 -0
  10. data/lib/mustermann.rb +14 -5
  11. data/lib/mustermann/ast/compiler.rb +150 -0
  12. data/lib/mustermann/ast/expander.rb +112 -0
  13. data/lib/mustermann/ast/node.rb +155 -0
  14. data/lib/mustermann/ast/parser.rb +136 -0
  15. data/lib/mustermann/ast/pattern.rb +89 -0
  16. data/lib/mustermann/ast/transformer.rb +121 -0
  17. data/lib/mustermann/ast/translator.rb +111 -0
  18. data/lib/mustermann/ast/tree_renderer.rb +29 -0
  19. data/lib/mustermann/ast/validation.rb +40 -0
  20. data/lib/mustermann/error.rb +4 -12
  21. data/lib/mustermann/extension.rb +3 -6
  22. data/lib/mustermann/identity.rb +4 -4
  23. data/lib/mustermann/pattern.rb +34 -5
  24. data/lib/mustermann/rails.rb +7 -16
  25. data/lib/mustermann/regexp_based.rb +4 -4
  26. data/lib/mustermann/shell.rb +4 -4
  27. data/lib/mustermann/simple.rb +1 -1
  28. data/lib/mustermann/simple_match.rb +2 -2
  29. data/lib/mustermann/sinatra.rb +10 -20
  30. data/lib/mustermann/template.rb +11 -104
  31. data/lib/mustermann/version.rb +1 -1
  32. data/mustermann.gemspec +1 -1
  33. data/spec/extension_spec.rb +143 -0
  34. data/spec/mustermann_spec.rb +41 -0
  35. data/spec/pattern_spec.rb +16 -6
  36. data/spec/rails_spec.rb +77 -9
  37. data/spec/sinatra_spec.rb +6 -0
  38. data/spec/support.rb +5 -78
  39. data/spec/support/coverage.rb +18 -0
  40. data/spec/support/env.rb +6 -0
  41. data/spec/support/expand_matcher.rb +27 -0
  42. data/spec/support/match_matcher.rb +39 -0
  43. data/spec/support/pattern.rb +28 -0
  44. metadata +43 -43
  45. data/.test_queue_stats +0 -0
  46. data/lib/mustermann/ast.rb +0 -403
  47. data/spec/ast_spec.rb +0 -8
@@ -0,0 +1,112 @@
1
+ require 'mustermann/ast/translator'
2
+ require 'mustermann/ast/compiler'
3
+
4
+ module Mustermann
5
+ module AST
6
+ # Looks at an AST, remembers the important bits of information to do an
7
+ # ultra fast expansion.
8
+ #
9
+ # @!visibility private
10
+ class Expander < Translator
11
+ raises ExpandError
12
+
13
+ translate Array do
14
+ inject(t.pattern) do |pattern, element|
15
+ t.add_to(pattern, t(element))
16
+ end
17
+ end
18
+
19
+ translate :capture do
20
+ t.for_capture(node)
21
+ end
22
+
23
+ translate :named_splat, :splat do
24
+ t.pattern + t.for_capture(node)
25
+ end
26
+
27
+ translate :root, :group, :expression do
28
+ t(payload)
29
+ end
30
+
31
+ translate :char do
32
+ t.pattern(t.escape(payload, also_escape: /[\/\?#\&\=%]/).gsub(?%, "%%"))
33
+ end
34
+
35
+ translate :separator do
36
+ t.pattern(payload.gsub(?%, "%%"))
37
+ end
38
+
39
+ translate :with_look_ahead do
40
+ t.add_to(t(head), t(payload))
41
+ end
42
+
43
+ translate :optional do
44
+ nested = t(payload)
45
+ nested += t.pattern unless nested.any? { |n| n.first.empty? }
46
+ nested
47
+ end
48
+
49
+ # helper method for captures
50
+ # @!visibility private
51
+ def for_capture(node)
52
+ name = node.name.to_sym
53
+ pattern('%s', name, name => /(?!#{pattern_for(node)})./)
54
+ end
55
+
56
+ # maps sorted key list to sprintf patterns and filters
57
+ # @!visibility private
58
+ def mappings
59
+ @mappings ||= {}
60
+ end
61
+
62
+ # add a tree for expansion
63
+ # @!visibility private
64
+ def add(ast)
65
+ translate(ast).each do |keys, pattern, filter|
66
+ mappings[keys.uniq.sort] ||= [keys, pattern, filter]
67
+ end
68
+ end
69
+
70
+ # helper method for getting a capture's pattern.
71
+ # @!visibility private
72
+ def pattern_for(node, **options)
73
+ Compiler.new.decorator_for(node).pattern(**options)
74
+ end
75
+
76
+ # @see Mustermann::Pattern#expand
77
+ # @!visibility private
78
+ def expand(**values)
79
+ keys, pattern, filters = mappings.fetch(values.keys.sort) { error_for(values) }
80
+ filters.each { |key, filter| values[key] &&= escape(values[key], also_escape: filter) }
81
+ pattern % values.values_at(*keys)
82
+ end
83
+
84
+ # helper method for raising an error for unexpandable values
85
+ # @!visibility private
86
+ def error_for(values)
87
+ expansions = mappings.keys.map(&:inspect).join(" or ")
88
+ raise error_class, "cannot expand with keys %p, possible expansions: %s" % [values.keys.sort, expansions]
89
+ end
90
+
91
+ # @see Mustermann::AST::Translator#expand
92
+ # @!visibility private
93
+ def escape(string, *args)
94
+ # URI::Parser is pretty slow, let's not had every string to it, even if it's uneccessary
95
+ string =~ /\A\w*\Z/ ? string : super
96
+ end
97
+
98
+ # Turns a sprintf pattern into our secret internal data strucutre.
99
+ # @!visibility private
100
+ def pattern(string = "", *keys, **filters)
101
+ [[keys, string, filters]]
102
+ end
103
+
104
+ # Creates the product of two of our secret internal data strucutres.
105
+ # @!visibility private
106
+ def add_to(list, result)
107
+ list << [[], ""] if list.empty?
108
+ list.inject([]) { |l, (k1, p1, f1)| l + result.map { |k2, p2, f2| [k1+k2, p1+p2, **f1, **f2] } }
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,155 @@
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
+ end
16
+
17
+ # @!visibility private
18
+ # @param [Symbol] name of the node
19
+ # @return [String] qualified name of factory for the node
20
+ def self.constant_name(name)
21
+ return self.name if name.to_sym == :node
22
+ name = name.to_s.split(?_).map(&:capitalize).join
23
+ "#{self.name}::#{name}"
24
+ end
25
+
26
+ # Helper for creating a new instance and calling #parse on it.
27
+ # @return [Mustermann::AST::Node]
28
+ # @!visibility private
29
+ def self.parse(*args, &block)
30
+ new(*args).tap { |n| n.parse(&block) }
31
+ end
32
+
33
+ # @!visibility private
34
+ def initialize(payload = nil, **options)
35
+ options.each { |key, value| public_send("#{key}=", value) }
36
+ self.payload = payload
37
+ end
38
+
39
+ # Double dispatch helper for reading from the buffer into the payload.
40
+ # @!visibility private
41
+ def parse
42
+ self.payload ||= []
43
+ while element = yield
44
+ payload << element
45
+ end
46
+ end
47
+
48
+ # Loop through all nodes that don't have child nodes.
49
+ # @!visibility private
50
+ def each_leaf(&block)
51
+ return enum_for(__method__) unless block_given?
52
+ called = false
53
+ Array(payload).each do |entry|
54
+ next unless entry.respond_to? :each_leaf
55
+ entry.each_leaf(&block)
56
+ called = true
57
+ end
58
+ yield(self) unless called
59
+ end
60
+
61
+ # @!visibility private
62
+ class Capture < Node
63
+ # @see Mustermann::AST::Node#parse
64
+ # @!visibility private
65
+ def parse
66
+ self.payload ||= ""
67
+ super
68
+ end
69
+
70
+ # @!visibility private
71
+ alias_method :name, :payload
72
+ end
73
+
74
+ # @!visibility private
75
+ class Char < Node
76
+ end
77
+
78
+ # AST node for template expressions.
79
+ # @!visibility private
80
+ class Expression < Node
81
+ # @!visibility private
82
+ attr_accessor :operator
83
+ end
84
+
85
+ # @!visibility private
86
+ class Group < Node
87
+ # @!visibility private
88
+ def initialize(payload = nil, **options)
89
+ super(Array(payload), **options)
90
+ end
91
+ end
92
+
93
+ # @!visibility private
94
+ class Optional < Node
95
+ end
96
+
97
+ # @!visibility private
98
+ class Root < Node
99
+ # @!visibility private
100
+ attr_accessor :pattern
101
+
102
+ # Will trigger transform.
103
+ #
104
+ # @see Mustermann::AST::Node.parse
105
+ # @!visibility private
106
+ def self.parse(string, &block)
107
+ root = new
108
+ root.pattern = string
109
+ root.parse(&block)
110
+ #root.transform
111
+ root
112
+ end
113
+ end
114
+
115
+ # @!visibility private
116
+ class Separator < Node
117
+ end
118
+
119
+ # @!visibility private
120
+ class Splat < Capture
121
+ # @see Mustermann::AST::Node::Capture#name
122
+ # @!visibility private
123
+ def name
124
+ "splat"
125
+ end
126
+ end
127
+
128
+ # @!visibility private
129
+ class NamedSplat < Splat
130
+ # @see Mustermann::AST::Node::Capture#name
131
+ # @!visibility private
132
+ alias_method :name, :payload
133
+ end
134
+
135
+ # AST node for template variables.
136
+ # @!visibility private
137
+ class Variable < Capture
138
+ # @!visibility private
139
+ attr_accessor :prefix, :explode
140
+ end
141
+
142
+ # @!visibility private
143
+ class WithLookAhead < Node
144
+ # @!visibility private
145
+ attr_accessor :head, :at_end
146
+
147
+ # @!visibility private
148
+ def initialize(payload, at_end)
149
+ self.head, *self.payload = Array(payload)
150
+ self.at_end = at_end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,136 @@
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
+ # @param [Hash] **options parse options
13
+ # @return [Mustermann::AST::Node] parse tree for string
14
+ # @!visibility private
15
+ def self.parse(string)
16
+ new.parse(string)
17
+ end
18
+
19
+ # Defines another grammar rule for first character.
20
+ #
21
+ # @see Mustermann::Rails
22
+ # @see Mustermann::Sinatra
23
+ # @see Mustermann::Template
24
+ # @!visibility private
25
+ def self.on(*chars, &block)
26
+ chars.each do |char|
27
+ define_method("read %p" % char, &block)
28
+ end
29
+ end
30
+
31
+ # Defines another grammar rule for a suffix.
32
+ #
33
+ # @see Mustermann::Sinatra
34
+ # @!visibility private
35
+ def self.suffix(pattern = /./, &block)
36
+ @suffix ||= []
37
+ @suffix << [pattern, block] if block
38
+ @suffix
39
+ end
40
+
41
+ # @!visibility private
42
+ attr_reader :buffer, :string
43
+
44
+ extend Forwardable
45
+ def_delegators :buffer, :eos?, :getch
46
+
47
+ # @param [String] string to be parsed
48
+ # @return [Mustermann::AST::Node] parse tree for string
49
+ # @!visibility private
50
+ def parse(string)
51
+ @string = string
52
+ @buffer = StringScanner.new(string)
53
+ node(:root, string) { read unless eos? }
54
+ end
55
+
56
+ # @example
57
+ # node(:char, 'x').compile =~ 'x' # => true
58
+ #
59
+ # @param [Symbol] type node type
60
+ # @return [Mustermann::AST::Node]
61
+ # @!visibility private
62
+ def node(type, *args, &block)
63
+ type = Node[type] unless type.respond_to? :new
64
+ block ? type.parse(*args, &block) : type.new(*args)
65
+ end
66
+
67
+ # Create a node for a character we don't have an explicite rule for.
68
+ #
69
+ # @param [String] char the character
70
+ # @return [Mustermann::AST::Node] the node
71
+ # @!visibility private
72
+ def default_node(char)
73
+ char == ?/ ? node(:separator, char) : node(:char, char)
74
+ end
75
+
76
+ # Reads the next element from the buffer.
77
+ # @return [Mustermann::AST::Node] next element
78
+ # @!visibility private
79
+ def read
80
+ char = getch
81
+ method = "read %p" % char
82
+ element = respond_to?(method) ? send(method, char) : default_node(char)
83
+ read_suffix(element)
84
+ end
85
+
86
+ # Checks for a potential suffix on the buffer.
87
+ # @param [Mustermann::AST::Node] element node without suffix
88
+ # @return [Mustermann::AST::Node] node with suffix
89
+ # @!visibility private
90
+ def read_suffix(element)
91
+ self.class.suffix.inject(element) do |element, (regexp, callback)|
92
+ next element unless payload = scan(regexp)
93
+ instance_exec(payload, element, &callback)
94
+ end
95
+ end
96
+
97
+ # Wrapper around {StringScanner#scan} that turns strings into escaped
98
+ # regular expressions and returns a MatchData if the regexp has any
99
+ # named captures.
100
+ #
101
+ # @param [Regexp, String] regexp
102
+ # @see StringScanner#scan
103
+ # @return [String, MatchData, nil]
104
+ # @!visibility private
105
+ def scan(regexp)
106
+ regexp = Regexp.new(Regexp.escape(regexp)) unless regexp.is_a? Regexp
107
+ string = buffer.scan(regexp)
108
+ regexp.names.any? ? regexp.match(string) : string
109
+ end
110
+
111
+ # Asserts a regular expression matches what's next on the buffer.
112
+ # Will return corresponding MatchData if regexp includes named captures.
113
+ #
114
+ # @param [Regexp] regexp expected to match
115
+ # @return [String, MatchData] the match
116
+ # @raise [Mustermann::ParseError] if expectation wasn't met
117
+ # @!visibility private
118
+ def expect(regexp, **options)
119
+ scan(regexp)|| unexpected(**options)
120
+ end
121
+
122
+ # Helper for raising an exception for an unexpected character.
123
+ # Will read character from buffer if buffer is passed in.
124
+ #
125
+ # @param [String, nil] char the unexcpected character
126
+ # @raise [Mustermann::ParseError, Exception]
127
+ # @!visibility private
128
+ def unexpected(char = getch, exception: ParseError)
129
+ char = "space" if char == " "
130
+ raise exception, "unexpected #{char || "end of string"} while parsing #{string.inspect}"
131
+ end
132
+ end
133
+
134
+ private_constant :Parser
135
+ end
136
+ end
@@ -0,0 +1,89 @@
1
+ require 'mustermann/ast/parser'
2
+ require 'mustermann/ast/compiler'
3
+ require 'mustermann/ast/transformer'
4
+ require 'mustermann/ast/validation'
5
+ require 'mustermann/ast/expander'
6
+ require 'mustermann/regexp_based'
7
+
8
+ module Mustermann
9
+ # @see Mustermann::AST::Pattern
10
+ module AST
11
+ # Superclass for pattern styles that parse an AST from the string pattern.
12
+ # @abstract
13
+ class Pattern < Mustermann::RegexpBased
14
+ supported_options :capture, :except, :greedy, :space_matches_plus
15
+
16
+ extend Forwardable, SingleForwardable
17
+ single_delegate on: :parser, suffix: :parser
18
+ instance_delegate %i[parser compiler transformer validation expander_class] => 'self.class'
19
+ instance_delegate parse: :parser, transform: :transformer, validate: :validation
20
+
21
+ # @api private
22
+ # @return [#expand] expander object for pattern
23
+ # @!visibility private
24
+ attr_accessor :expander
25
+
26
+ # @api private
27
+ # @return [#parse] parser object for pattern
28
+ # @!visibility private
29
+ def self.parser
30
+ return Parser if self == AST::Pattern
31
+ const_set :Parser, Class.new(superclass.parser) unless const_defined? :Parser, false
32
+ const_get :Parser
33
+ end
34
+
35
+ # @api private
36
+ # @return [#compile] compiler object for pattern
37
+ # @!visibility private
38
+ def self.compiler
39
+ Compiler
40
+ end
41
+
42
+ # @api private
43
+ # @return [#transform] compiler object for pattern
44
+ # @!visibility private
45
+ def self.transformer
46
+ Transformer
47
+ end
48
+
49
+ # @api private
50
+ # @return [#validate] validation object for pattern
51
+ # @!visibility private
52
+ def self.validation
53
+ Validation
54
+ end
55
+
56
+ # @api private
57
+ # @return [#new] expander factory for pattern
58
+ # @!visibility private
59
+ def self.expander_class
60
+ Expander
61
+ end
62
+
63
+ # @!visibility private
64
+ def compile(string, **options)
65
+ self.expander = expander_class.new
66
+ options[:except] &&= parse options[:except]
67
+ ast = validate(transform(parse(string)))
68
+ expander.add(ast)
69
+ compiler.compile(ast, **options)
70
+ rescue CompileError => error
71
+ error.message << ": %p" % string
72
+ raise error
73
+ end
74
+
75
+ # All AST-based pattern implementations support expanding.
76
+ #
77
+ # @example (see Mustermann::Pattern#expand)
78
+ # @param (see Mustermann::Pattern#expand)
79
+ # @return (see Mustermann::Pattern#expand)
80
+ # @raise (see Mustermann::Pattern#expand)
81
+ # @see Mustermann::Pattern#expand
82
+ def expand(**values)
83
+ expander.expand(**values)
84
+ end
85
+
86
+ private :compile
87
+ end
88
+ end
89
+ end