mustermann19 0.3.1.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/README.md +6 -0
- data/Rakefile +2 -2
- data/lib/mustermann.rb +36 -13
- data/lib/mustermann/ast/boundaries.rb +48 -0
- data/lib/mustermann/ast/expander.rb +11 -7
- data/lib/mustermann/ast/node.rb +47 -9
- data/lib/mustermann/ast/parser.rb +25 -27
- data/lib/mustermann/ast/pattern.rb +18 -5
- data/lib/mustermann/ast/transformer.rb +54 -6
- data/lib/mustermann/ast/translator.rb +20 -0
- data/lib/mustermann/ast/tree_renderer.rb +2 -9
- data/lib/mustermann/express.rb +2 -0
- data/lib/mustermann/flask.rb +2 -0
- data/lib/mustermann/identity.rb +5 -3
- data/lib/mustermann/pattern.rb +7 -0
- data/lib/mustermann/pyramid.rb +2 -0
- data/lib/mustermann/rails.rb +32 -4
- data/lib/mustermann/rails/versions.rb +51 -0
- data/lib/mustermann/regexp.rb +1 -0
- data/lib/mustermann/regular.rb +2 -0
- data/lib/mustermann/shell.rb +2 -0
- data/lib/mustermann/simple.rb +1 -0
- data/lib/mustermann/sinatra.rb +5 -7
- data/lib/mustermann/string_scanner.rb +1 -1
- data/lib/mustermann/template.rb +4 -1
- data/lib/mustermann/uri_template.rb +1 -0
- data/lib/mustermann/version.rb +1 -1
- data/mustermann/README.md +838 -0
- data/mustermann/lib/mustermann/ast/compiler.rb +156 -0
- data/spec/ast_spec.rb +14 -0
- data/spec/mustermann_spec.rb +3 -2
- data/spec/rails_spec.rb +45 -0
- data/spec/shell_spec.rb +4 -2
- data/spec/sinatra_spec.rb +12 -5
- data/spec/support/match_matcher.rb +1 -1
- data/spec/support/pattern.rb +2 -2
- data/spec/template_spec.rb +5 -0
- metadata +37 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c18eae7550233eced7217aec1439b9a51737fc1
|
4
|
+
data.tar.gz: e242e2fc80b76b0fd6842c6e801b69aa79958354
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a201dc17c06299c6b7f49749e7ec6add20bea0db6efe5c438044e72209ad0be2f394ac5b59d1eca9b6c9706b73b9dd7dde61c951c37fba8396b5ed30940fc435
|
7
|
+
data.tar.gz: e60038818b219dcf23c58334df578d0d101ce49f609c0cc93e950f0eb804e38a8cccc9835014cee05c1d91b49bb522d35549923aef32238bff620015b7d70aae
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -8,6 +8,12 @@
|
|
8
8
|
|
9
9
|
|
10
10
|
*Make sure you view the correct docs: [latest release](http://rubydoc.info/gems/mustermann/frames), [master](http://rubydoc.info/github/rkh/mustermann/master/frames).*
|
11
|
+
* **[mustermann](mustermann/README.md): Your personal string matching expert. This is probably what you're looking for.**
|
12
|
+
* [mustermann-everything](mustermann-everything/README.md): A meta gem depending on all other official mustermann gems.
|
13
|
+
* [mustermann-fileutils](mustermann-fileutils/README.md): Efficient file system operations using Mustermann patterns.
|
14
|
+
* [mustermann-strscan](mustermann-strscan/README.md): A version of Ruby's [StringScanner](http://ruby-doc.org/stdlib-2.0/libdoc/strscan/rdoc/StringScanner.html) made for pattern objects.
|
15
|
+
* [mustermann-visualizer](mustermann-visualizer/README.md): Syntax highlighting and tree visualization for patterns.
|
16
|
+
* A selection of pattern types for mustermann, each as their own little library, see [below](#-pattern-types).
|
11
17
|
|
12
18
|
Welcome to [Mustermann](http://en.wikipedia.org/wiki/List_of_placeholder_names_by_language#German). Mustermann is your personal string matching expert. As an expert in the field of strings and patterns, Mustermann keeps its runtime dependencies to a minimum and is fully covered with specs and documentation.
|
13
19
|
|
data/Rakefile
CHANGED
data/lib/mustermann.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
require 'mustermann/pattern'
|
2
2
|
require 'mustermann/composite'
|
3
|
+
require 'thread'
|
3
4
|
|
4
5
|
# Namespace and main entry point for the Mustermann library.
|
5
6
|
#
|
6
7
|
# Under normal circumstances the only external API entry point you should be using is {Mustermann.new}.
|
7
8
|
module Mustermann
|
9
|
+
# Type to use if no type is given.
|
10
|
+
# @api private
|
11
|
+
DEFAULT_TYPE = :sinatra
|
12
|
+
|
8
13
|
# Creates a new pattern based on input.
|
9
14
|
#
|
10
15
|
# * From {Mustermann::Pattern}: returns given pattern.
|
@@ -54,7 +59,7 @@ module Mustermann
|
|
54
59
|
# @see file:README.md#Types_and_Options "Types and Options" in the README
|
55
60
|
def self.new(*input)
|
56
61
|
options = input.last.kind_of?(Hash) ? input.pop : {}
|
57
|
-
type = options.delete(:type) ||
|
62
|
+
type = options.delete(:type) || DEFAULT_TYPE
|
58
63
|
input = input.first if input.size < 2
|
59
64
|
case input
|
60
65
|
when Pattern then input
|
@@ -69,6 +74,9 @@ module Mustermann
|
|
69
74
|
end
|
70
75
|
end
|
71
76
|
|
77
|
+
@mutex ||= Mutex.new
|
78
|
+
@types ||= {}
|
79
|
+
|
72
80
|
# Maps a type to its factory.
|
73
81
|
#
|
74
82
|
# @example
|
@@ -77,10 +85,24 @@ module Mustermann
|
|
77
85
|
# @param [Symbol] key a pattern type identifier
|
78
86
|
# @raise [ArgumentError] if the type is not supported
|
79
87
|
# @return [Class, #new] pattern factory
|
80
|
-
def self.[](
|
81
|
-
|
82
|
-
|
83
|
-
|
88
|
+
def self.[](name)
|
89
|
+
return name if name.respond_to? :new
|
90
|
+
@types.fetch(normalized = normalized_type(name)) do
|
91
|
+
@mutex.synchronize do
|
92
|
+
error = try_require "mustermann/#{normalized}"
|
93
|
+
@types.fetch(normalized) { raise ArgumentError, "unsupported type %p#{" (#{error.message})" if error}" % name }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [LoadError, nil]
|
99
|
+
# @!visibility private
|
100
|
+
def self.try_require(path)
|
101
|
+
require(path)
|
102
|
+
nil
|
103
|
+
rescue LoadError => error
|
104
|
+
raise(error) if error.respond_to?(:path) && error.path != path
|
105
|
+
error
|
84
106
|
end
|
85
107
|
|
86
108
|
# @!visibility private
|
@@ -93,18 +115,19 @@ module Mustermann
|
|
93
115
|
@register
|
94
116
|
end
|
95
117
|
|
118
|
+
def self.register(name, type)
|
119
|
+
@types[normalized_type(name)] = type
|
120
|
+
end
|
121
|
+
|
122
|
+
# @!visibility private
|
123
|
+
def self.normalized_type(type)
|
124
|
+
type.to_s.gsub('-', '_').downcase
|
125
|
+
end
|
126
|
+
|
96
127
|
# @!visibility private
|
97
128
|
def self.extend_object(object)
|
98
129
|
return super unless defined? ::Sinatra::Base and object.is_a? Class and object < ::Sinatra::Base
|
99
130
|
require 'mustermann/extension'
|
100
131
|
object.register Extension
|
101
132
|
end
|
102
|
-
|
103
|
-
register :identity
|
104
|
-
register :rails
|
105
|
-
register :regular, :regexp
|
106
|
-
register :shell
|
107
|
-
register :simple
|
108
|
-
register :sinatra
|
109
|
-
register :template
|
110
133
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'mustermann/ast/translator'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
module AST
|
5
|
+
# Make sure #start and #stop is set on every node and within its parents #start and #stop.
|
6
|
+
# @!visibility private
|
7
|
+
class Boundaries < Translator
|
8
|
+
# @return [Mustermann::AST::Node] the ast passed as first argument
|
9
|
+
# @!visibility private
|
10
|
+
#def self.set_boundaries(ast, string: nil, start: 0, stop: string.length)
|
11
|
+
def self.set_boundaries(ast, options = {})
|
12
|
+
string = options.fetch(:string, nil)
|
13
|
+
start = options.fetch(:start, 0)
|
14
|
+
stop = options.fetch(:stop, string.length)
|
15
|
+
new.translate(ast, start, stop)
|
16
|
+
ast
|
17
|
+
end
|
18
|
+
|
19
|
+
translate(:node) do |start, stop|
|
20
|
+
t.set_boundaries(node, start, stop)
|
21
|
+
t(payload, node.start, node.stop)
|
22
|
+
end
|
23
|
+
|
24
|
+
translate(:with_look_ahead) do |start, stop|
|
25
|
+
t.set_boundaries(node, start, stop)
|
26
|
+
t(head, node.start, node.stop)
|
27
|
+
t(payload, node.start, node.stop)
|
28
|
+
end
|
29
|
+
|
30
|
+
translate(Array) do |start, stop|
|
31
|
+
each do |subnode|
|
32
|
+
t(subnode, start, stop)
|
33
|
+
start = subnode.stop
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
translate(Object) { |*| node }
|
38
|
+
|
39
|
+
# Checks that a node is within the given boundaries.
|
40
|
+
# @!visibility private
|
41
|
+
def set_boundaries(node, start, stop)
|
42
|
+
node.start = start if node.start.nil? or node.start < start
|
43
|
+
node.stop = node.start + node.min_size if node.stop.nil? or node.stop < node.start
|
44
|
+
node.stop = stop if node.stop > stop
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -10,21 +10,25 @@ module Mustermann
|
|
10
10
|
class Expander < Translator
|
11
11
|
raises ExpandError
|
12
12
|
|
13
|
-
translate Array do
|
13
|
+
translate Array do |*args|
|
14
14
|
inject(t.pattern) do |pattern, element|
|
15
|
-
t.add_to(pattern, t(element))
|
15
|
+
t.add_to(pattern, t(element, *args))
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
translate :capture do
|
20
|
-
t.for_capture(node)
|
19
|
+
translate :capture do |options = {}|
|
20
|
+
t.for_capture(node, options)
|
21
21
|
end
|
22
22
|
|
23
23
|
translate :named_splat, :splat do
|
24
24
|
t.pattern + t.for_capture(node)
|
25
25
|
end
|
26
26
|
|
27
|
-
translate :
|
27
|
+
translate :expression do
|
28
|
+
t(payload, allow_reserved: operator.allow_reserved)
|
29
|
+
end
|
30
|
+
|
31
|
+
translate :root, :group do
|
28
32
|
t(payload)
|
29
33
|
end
|
30
34
|
|
@@ -52,9 +56,9 @@ module Mustermann
|
|
52
56
|
|
53
57
|
# helper method for captures
|
54
58
|
# @!visibility private
|
55
|
-
def for_capture(node)
|
59
|
+
def for_capture(node, options = {})
|
56
60
|
name = node.name.to_sym
|
57
|
-
pattern('%s', name, name => /(?!#{pattern_for(node)})./)
|
61
|
+
pattern('%s', name, name => /(?!#{pattern_for(node, options)})./)
|
58
62
|
end
|
59
63
|
|
60
64
|
# maps sorted key list to sprintf patterns and filters
|
data/lib/mustermann/ast/node.rb
CHANGED
@@ -4,24 +4,23 @@ module Mustermann
|
|
4
4
|
# @!visibility private
|
5
5
|
class Node
|
6
6
|
# @!visibility private
|
7
|
-
attr_accessor :payload
|
7
|
+
attr_accessor :payload, :start, :stop
|
8
8
|
|
9
9
|
# @!visibility private
|
10
10
|
# @param [Symbol] name of the node
|
11
11
|
# @return [Class] factory for the node
|
12
12
|
def self.[](name)
|
13
|
-
@names
|
14
|
-
|
15
|
-
@names.fetch(name) do
|
13
|
+
@names ||= {}
|
14
|
+
@names[name] ||= begin
|
16
15
|
const_name = constant_name(name)
|
17
|
-
const_name.split("::").inject(Object){|current, const| current.const_get(const) }
|
16
|
+
const_name.split("::").inject(Object){|current, const| current.const_get(const) } rescue nil
|
18
17
|
end
|
19
18
|
end
|
20
19
|
|
20
|
+
# Turns a class name into a node identifier.
|
21
21
|
# @!visibility private
|
22
|
-
def
|
23
|
-
|
24
|
-
super(type)
|
22
|
+
def self.type
|
23
|
+
name[/[^:]+$/].split(/(?<=.)(?=[A-Z])/).map(&:downcase).join(?_).to_sym
|
25
24
|
end
|
26
25
|
|
27
26
|
# @!visibility private
|
@@ -47,6 +46,12 @@ module Mustermann
|
|
47
46
|
self.payload = payload
|
48
47
|
end
|
49
48
|
|
49
|
+
# @!visibility private
|
50
|
+
def is_a?(type)
|
51
|
+
type = Node[type] if type.is_a? Symbol
|
52
|
+
super(type)
|
53
|
+
end
|
54
|
+
|
50
55
|
# Double dispatch helper for reading from the buffer into the payload.
|
51
56
|
# @!visibility private
|
52
57
|
def parse
|
@@ -69,6 +74,24 @@ module Mustermann
|
|
69
74
|
yield(self) unless called
|
70
75
|
end
|
71
76
|
|
77
|
+
# @return [Integer] length of the substring
|
78
|
+
# @!visibility private
|
79
|
+
def length
|
80
|
+
stop - start if start and stop
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [Integer] minimum size for a node
|
84
|
+
# @!visibility private
|
85
|
+
def min_size
|
86
|
+
0
|
87
|
+
end
|
88
|
+
|
89
|
+
# Turns a class name into a node identifier.
|
90
|
+
# @!visibility private
|
91
|
+
def type
|
92
|
+
self.class.type
|
93
|
+
end
|
94
|
+
|
72
95
|
# @!visibility private
|
73
96
|
class Capture < Node
|
74
97
|
# @see Mustermann::AST::Compiler::Capture#default
|
@@ -96,6 +119,11 @@ module Mustermann
|
|
96
119
|
|
97
120
|
# @!visibility private
|
98
121
|
class Char < Node
|
122
|
+
# @return [Integer] minimum size for a node
|
123
|
+
# @!visibility private
|
124
|
+
def min_size
|
125
|
+
1
|
126
|
+
end
|
99
127
|
end
|
100
128
|
|
101
129
|
# AST node for template expressions.
|
@@ -126,6 +154,10 @@ module Mustermann
|
|
126
154
|
class Optional < Node
|
127
155
|
end
|
128
156
|
|
157
|
+
# @!visibility private
|
158
|
+
class Or < Node
|
159
|
+
end
|
160
|
+
|
129
161
|
# @!visibility private
|
130
162
|
class Root < Node
|
131
163
|
# @!visibility private
|
@@ -145,6 +177,11 @@ module Mustermann
|
|
145
177
|
|
146
178
|
# @!visibility private
|
147
179
|
class Separator < Node
|
180
|
+
# @return [Integer] minimum size for a node
|
181
|
+
# @!visibility private
|
182
|
+
def min_size
|
183
|
+
1
|
184
|
+
end
|
148
185
|
end
|
149
186
|
|
150
187
|
# @!visibility private
|
@@ -176,7 +213,8 @@ module Mustermann
|
|
176
213
|
attr_accessor :head, :at_end
|
177
214
|
|
178
215
|
# @!visibility private
|
179
|
-
def initialize(payload, at_end)
|
216
|
+
def initialize(payload, at_end, options = {})
|
217
|
+
super(options)
|
180
218
|
self.head, *self.payload = Array(payload)
|
181
219
|
self.at_end = at_end
|
182
220
|
end
|
@@ -42,7 +42,7 @@ module Mustermann
|
|
42
42
|
attr_reader :buffer, :string, :pattern
|
43
43
|
|
44
44
|
extend Forwardable
|
45
|
-
def_delegators :buffer, :eos?, :getch
|
45
|
+
def_delegators :buffer, :eos?, :getch, :pos
|
46
46
|
|
47
47
|
# @!visibility private
|
48
48
|
def initialize(options = {})
|
@@ -66,8 +66,10 @@ module Mustermann
|
|
66
66
|
# @return [Mustermann::AST::Node]
|
67
67
|
# @!visibility private
|
68
68
|
def node(type, *args, &block)
|
69
|
-
type
|
70
|
-
|
69
|
+
type = Node[type] unless type.respond_to? :new
|
70
|
+
start = pos
|
71
|
+
node = block ? type.parse(*args, &block) : type.new(*args)
|
72
|
+
min_size(start, pos, node)
|
71
73
|
end
|
72
74
|
|
73
75
|
# Create a node for a character we don't have an explicit rule for.
|
@@ -83,12 +85,26 @@ module Mustermann
|
|
83
85
|
# @return [Mustermann::AST::Node] next element
|
84
86
|
# @!visibility private
|
85
87
|
def read
|
86
|
-
|
87
|
-
|
88
|
-
|
88
|
+
start = pos
|
89
|
+
char = getch
|
90
|
+
method = "read %p" % char
|
91
|
+
element= respond_to?(method) ? send(method, char) : default_node(char)
|
92
|
+
min_size(start, pos, element)
|
89
93
|
read_suffix(element)
|
90
94
|
end
|
91
95
|
|
96
|
+
# sets start on node to start if it's not set to a lower value.
|
97
|
+
# sets stop on node to stop if it's not set to a higher value.
|
98
|
+
# @return [Mustermann::AST::Node] the node passed as third argument
|
99
|
+
# @!visibility private
|
100
|
+
def min_size(start, stop, node)
|
101
|
+
stop ||= start
|
102
|
+
start ||= stop
|
103
|
+
node.start = start unless node.start and node.start < start
|
104
|
+
node.stop = stop unless node.stop and node.stop > stop
|
105
|
+
node
|
106
|
+
end
|
107
|
+
|
92
108
|
# Checks for a potential suffix on the buffer.
|
93
109
|
# @param [Mustermann::AST::Node] element node without suffix
|
94
110
|
# @return [Mustermann::AST::Node] node with suffix
|
@@ -96,7 +112,8 @@ module Mustermann
|
|
96
112
|
def read_suffix(element)
|
97
113
|
self.class.suffix.inject(element) do |ele, (regexp, after, callback)|
|
98
114
|
next ele unless ele.is_a?(after) and payload = scan(regexp)
|
99
|
-
instance_exec(payload, ele, &callback)
|
115
|
+
content = instance_exec(payload, ele, &callback)
|
116
|
+
min_size(element.start, pos, content)
|
100
117
|
end
|
101
118
|
end
|
102
119
|
|
@@ -109,25 +126,8 @@ module Mustermann
|
|
109
126
|
# @return [String, MatchData, nil]
|
110
127
|
# @!visibility private
|
111
128
|
def scan(regexp)
|
112
|
-
match_buffer(:scan, regexp)
|
113
|
-
end
|
114
|
-
|
115
|
-
# Wrapper around {StringScanner#check} that turns strings into escaped
|
116
|
-
# regular expressions and returns a MatchData if the regexp has any
|
117
|
-
# named captures.
|
118
|
-
#
|
119
|
-
# @param [Regexp, String] regexp
|
120
|
-
# @see StringScanner#check
|
121
|
-
# @return [String, MatchData, nil]
|
122
|
-
# @!visibility private
|
123
|
-
def check(regexp)
|
124
|
-
match_buffer(:check, regexp)
|
125
|
-
end
|
126
|
-
|
127
|
-
# @!visibility private
|
128
|
-
def match_buffer(method, regexp)
|
129
129
|
regexp = Regexp.new(Regexp.escape(regexp)) unless regexp.is_a? Regexp
|
130
|
-
string = buffer.
|
130
|
+
string = buffer.scan(regexp)
|
131
131
|
regexp.names.any? ? regexp.match(string) : string
|
132
132
|
end
|
133
133
|
|
@@ -247,8 +247,6 @@ module Mustermann
|
|
247
247
|
char = "space" if char == " "
|
248
248
|
raise exception, "unexpected #{char || "end of string"} while parsing #{string.inspect}"
|
249
249
|
end
|
250
|
-
|
251
|
-
private :match_buffer
|
252
250
|
end
|
253
251
|
|
254
252
|
#private_constant :Parser
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'mustermann/ast/parser'
|
2
|
+
require 'mustermann/ast/boundaries'
|
2
3
|
require 'mustermann/ast/compiler'
|
3
4
|
require 'mustermann/ast/transformer'
|
4
5
|
require 'mustermann/ast/validation'
|
@@ -18,9 +19,9 @@ module Mustermann
|
|
18
19
|
|
19
20
|
extend Forwardable, SingleForwardable
|
20
21
|
single_delegate on: :parser, suffix: :parser
|
21
|
-
instance_delegate %w[parser compiler transformer validation template_generator param_scanner].map(&:to_sym) => 'self.class'
|
22
|
+
instance_delegate %w[parser compiler transformer validation template_generator param_scanner boundaries].map(&:to_sym) => 'self.class'
|
22
23
|
instance_delegate parse: :parser, transform: :transformer, validate: :validation,
|
23
|
-
generate_templates: :template_generator, scan_params: :param_scanner
|
24
|
+
generate_templates: :template_generator, scan_params: :param_scanner, set_boundaries: :boundaries
|
24
25
|
|
25
26
|
# @api private
|
26
27
|
# @return [#parse] parser object for pattern
|
@@ -39,7 +40,14 @@ module Mustermann
|
|
39
40
|
end
|
40
41
|
|
41
42
|
# @api private
|
42
|
-
# @return [#
|
43
|
+
# @return [#set_boundaries] translator making sure start and stop is set on all nodes
|
44
|
+
# @!visibility private
|
45
|
+
def self.boundaries
|
46
|
+
Boundaries
|
47
|
+
end
|
48
|
+
|
49
|
+
# @api private
|
50
|
+
# @return [#transform] transformer object for pattern
|
43
51
|
# @!visibility private
|
44
52
|
def self.transformer
|
45
53
|
Transformer
|
@@ -79,7 +87,12 @@ module Mustermann
|
|
79
87
|
# @!visibility private
|
80
88
|
def to_ast
|
81
89
|
@ast_cache ||= EqualityMap.new
|
82
|
-
@ast_cache.fetch(@string)
|
90
|
+
@ast_cache.fetch(@string) do
|
91
|
+
ast = parse(@string, pattern: self)
|
92
|
+
ast &&= transform(ast)
|
93
|
+
ast &&= set_boundaries(ast, string: @string)
|
94
|
+
validate(ast)
|
95
|
+
end
|
83
96
|
end
|
84
97
|
|
85
98
|
# All AST-based pattern implementations support expanding.
|
@@ -117,7 +130,7 @@ module Mustermann
|
|
117
130
|
@param_converters ||= scan_params(to_ast)
|
118
131
|
end
|
119
132
|
|
120
|
-
private :compile, :parse, :transform, :validate, :generate_templates, :param_converters, :scan_params
|
133
|
+
private :compile, :parse, :transform, :validate, :generate_templates, :param_converters, :scan_params, :set_boundaries
|
121
134
|
end
|
122
135
|
end
|
123
136
|
end
|