mustermann19 0.3.1.2 → 0.4.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.
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