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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 13070ee02993a1b1a6f7e9cf97709b3401185b54
4
- data.tar.gz: 7cc22ed29750abd8899d649ca6f9b9849701f099
3
+ metadata.gz: 7c18eae7550233eced7217aec1439b9a51737fc1
4
+ data.tar.gz: e242e2fc80b76b0fd6842c6e801b69aa79958354
5
5
  SHA512:
6
- metadata.gz: fb819ee53bef2d16e6985b72e5dcb529465f8d9c555b5881e8c5b0adf862aed90d26167ca9e4b94843bb666af9038a530bf0ffb9530a856e5605c70db47051b2
7
- data.tar.gz: 1ac73cb9e5b86f4d0f51e1269f3d3116e1e5ac124d1b3483494769443e5cea2b43648e972a1fa0e2a27c41d8e5d3aa858d132818dcbe9cfed6e847ad77b4f142
6
+ metadata.gz: a201dc17c06299c6b7f49749e7ec6add20bea0db6efe5c438044e72209ad0be2f394ac5b59d1eca9b6c9706b73b9dd7dde61c951c37fba8396b5ed30940fc435
7
+ data.tar.gz: e60038818b219dcf23c58334df578d0d101ce49f609c0cc93e950f0eb804e38a8cccc9835014cee05c1d91b49bb522d35549923aef32238bff620015b7d70aae
data/.travis.yml CHANGED
@@ -3,6 +3,7 @@ rvm:
3
3
  - 1.9.2
4
4
  - 2.1.2
5
5
  - 2.1.4
6
+ - jruby
6
7
  - jruby-head
7
8
  - rbx-2
8
9
  matrix:
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
@@ -1,6 +1,6 @@
1
1
  ENV['JRUBY_OPTS'] = '--2.0 -X-C'
2
2
  ENV['RBXOPT'] = '-X20'
3
3
 
4
- task(:spec) { ruby '-w -S rspec' }
4
+ task(:rspec) { ruby '-S rspec' }
5
5
  task(:doc_stats) { ruby '-S yard stats' }
6
- task(default: [:spec, :doc_stats])
6
+ task default: [:rspec, :doc_stats]
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) || :sinatra
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.[](key)
81
- constant, library = register.fetch(key) { raise ArgumentError, "unsupported type %p" % key }
82
- require library if library
83
- constant.respond_to?(:new) ? constant : register[key] = const_get(constant)
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 :root, :group, :expression do
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
@@ -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
- #@names.fetch(name) { Object.const_get(constant_name(name)) }
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 is_a?(type)
23
- type = Node[type] if type.is_a? Symbol
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 = Node[type] unless type.respond_to? :new
70
- block ? type.parse(*args, &block) : type.new(*args)
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
- char = getch
87
- method = "read %p" % char
88
- element = respond_to?(method) ? send(method, char) : default_node(char)
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.public_send(method, regexp)
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 [#transform] compiler object for pattern
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) { validate(transform(parse(@string, pattern: self))) }
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