mustermann 0.3.1 → 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/README.md +429 -672
- data/lib/mustermann.rb +95 -20
- data/lib/mustermann/ast/boundaries.rb +44 -0
- data/lib/mustermann/ast/compiler.rb +13 -7
- data/lib/mustermann/ast/expander.rb +22 -12
- data/lib/mustermann/ast/node.rb +69 -5
- data/lib/mustermann/ast/param_scanner.rb +20 -0
- data/lib/mustermann/ast/parser.rb +138 -19
- data/lib/mustermann/ast/pattern.rb +59 -7
- data/lib/mustermann/ast/template_generator.rb +28 -0
- data/lib/mustermann/ast/transformer.rb +2 -2
- data/lib/mustermann/ast/translator.rb +20 -0
- data/lib/mustermann/ast/validation.rb +4 -3
- data/lib/mustermann/composite.rb +101 -0
- data/lib/mustermann/expander.rb +2 -2
- data/lib/mustermann/identity.rb +56 -0
- data/lib/mustermann/pattern.rb +185 -10
- data/lib/mustermann/pattern_cache.rb +49 -0
- data/lib/mustermann/regexp.rb +1 -0
- data/lib/mustermann/regexp_based.rb +18 -1
- data/lib/mustermann/regular.rb +4 -1
- data/lib/mustermann/simple_match.rb +5 -0
- data/lib/mustermann/sinatra.rb +22 -5
- data/lib/mustermann/to_pattern.rb +11 -6
- data/lib/mustermann/version.rb +1 -1
- data/mustermann.gemspec +1 -14
- data/spec/ast_spec.rb +14 -0
- data/spec/composite_spec.rb +147 -0
- data/spec/expander_spec.rb +15 -0
- data/spec/identity_spec.rb +44 -0
- data/spec/mustermann_spec.rb +17 -2
- data/spec/pattern_spec.rb +7 -3
- data/spec/regular_spec.rb +25 -0
- data/spec/sinatra_spec.rb +184 -9
- data/spec/to_pattern_spec.rb +49 -0
- metadata +15 -180
- data/.gitignore +0 -18
- data/.rspec +0 -2
- data/.travis.yml +0 -4
- data/.yardopts +0 -1
- data/Gemfile +0 -2
- data/LICENSE +0 -22
- data/Rakefile +0 -6
- data/internals.md +0 -64
- data/lib/mustermann/ast/tree_renderer.rb +0 -29
- data/lib/mustermann/rails.rb +0 -17
- data/lib/mustermann/shell.rb +0 -29
- data/lib/mustermann/simple.rb +0 -35
- data/lib/mustermann/template.rb +0 -47
- data/spec/rails_spec.rb +0 -521
- data/spec/shell_spec.rb +0 -108
- data/spec/simple_spec.rb +0 -236
- data/spec/support.rb +0 -5
- data/spec/support/coverage.rb +0 -16
- data/spec/support/env.rb +0 -16
- data/spec/support/expand_matcher.rb +0 -27
- data/spec/support/match_matcher.rb +0 -39
- data/spec/support/pattern.rb +0 -39
- data/spec/template_spec.rb +0 -814
data/lib/mustermann.rb
CHANGED
@@ -1,43 +1,117 @@
|
|
1
1
|
require 'mustermann/pattern'
|
2
|
+
require 'mustermann/composite'
|
3
|
+
require 'thread'
|
2
4
|
|
3
5
|
# Namespace and main entry point for the Mustermann library.
|
4
6
|
#
|
5
7
|
# Under normal circumstances the only external API entry point you should be using is {Mustermann.new}.
|
6
8
|
module Mustermann
|
7
|
-
#
|
9
|
+
# Type to use if no type is given.
|
10
|
+
# @api private
|
11
|
+
DEFAULT_TYPE = :sinatra
|
12
|
+
|
13
|
+
# Creates a new pattern based on input.
|
14
|
+
#
|
15
|
+
# * From {Mustermann::Pattern}: returns given pattern.
|
16
|
+
# * From String: creates a pattern from the string, depending on type option (defaults to {Mustermann::Sinatra})
|
17
|
+
# * From Regexp: creates a {Mustermann::Regular} pattern.
|
18
|
+
# * From Symbol: creates a {Mustermann::Sinatra} pattern with a single named capture named after the input.
|
19
|
+
# * From an Array or multiple inputs: creates a new pattern from each element, combines them to a {Mustermann::Composite}.
|
20
|
+
# * From anything else: Will try to call to_pattern on it or raise a TypeError.
|
21
|
+
#
|
22
|
+
# Note that if the input is a {Mustermann::Pattern}, Regexp or Symbol, the type option is ignored and if to_pattern is
|
23
|
+
# called on the object, the type will be handed on but might be ignored by the input object.
|
24
|
+
#
|
25
|
+
# If you want to enforce the pattern type, you should create them via their expected class.
|
26
|
+
#
|
27
|
+
# @example creating patterns
|
28
|
+
# require 'mustermann'
|
29
|
+
#
|
30
|
+
# Mustermann.new("/:name") # => #<Mustermann::Sinatra:"/example">
|
31
|
+
# Mustermann.new("/{name}", type: :template) # => #<Mustermann::Template:"/{name}">
|
32
|
+
# Mustermann.new(/.*/) # => #<Mustermann::Regular:".*">
|
33
|
+
# Mustermann.new(:name, capture: :word) # => #<Mustermann::Sinatra:":name">
|
34
|
+
# Mustermann.new("/", "/*.jpg", type: :shell) # => #<Mustermann::Composite:(shell:"/" | shell:"/*.jpg")>
|
35
|
+
#
|
36
|
+
# @example using custom #to_pattern
|
37
|
+
# require 'mustermann'
|
38
|
+
#
|
39
|
+
# class MyObject
|
40
|
+
# def to_pattern(**options)
|
41
|
+
# Mustermann.new("/:name", **options)
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# Mustermann.new(MyObject.new, type: :rails) # => #<Mustermann::Rails:"/:name">
|
46
|
+
#
|
47
|
+
# @example enforcing type
|
48
|
+
# require 'mustermann/sinatra'
|
49
|
+
#
|
50
|
+
# Mustermann::Sinatra.new("/:name")
|
51
|
+
#
|
52
|
+
# @param [String, Pattern, Regexp, Symbol, #to_pattern, Array<String, Pattern, Regexp, Symbol, #to_pattern>]
|
53
|
+
# input The representation of the pattern
|
8
54
|
# @param [Hash] options The options hash
|
9
55
|
# @return [Mustermann::Pattern] pattern corresponding to string.
|
10
56
|
# @raise (see [])
|
11
57
|
# @raise (see Mustermann::Pattern.new)
|
58
|
+
# @raise [TypeError] if the passed object cannot be converted to a pattern
|
12
59
|
# @see file:README.md#Types_and_Options "Types and Options" in the README
|
13
|
-
def self.new(input, type:
|
60
|
+
def self.new(*input, type: DEFAULT_TYPE, **options)
|
61
|
+
type ||= DEFAULT_TYPE
|
62
|
+
input = input.first if input.size < 2
|
14
63
|
case input
|
15
64
|
when Pattern then input
|
16
65
|
when Regexp then self[:regexp].new(input, **options)
|
17
66
|
when String then self[type].new(input, **options)
|
18
|
-
|
67
|
+
when Symbol then self[:sinatra].new(input.inspect, **options)
|
68
|
+
when Array then Composite.new(input, type: type, **options)
|
69
|
+
else
|
70
|
+
pattern = input.to_pattern(type: type, **options) if input.respond_to? :to_pattern
|
71
|
+
raise TypeError, "#{input.class} can't be coerced into Mustermann::Pattern" if pattern.nil?
|
72
|
+
pattern
|
19
73
|
end
|
20
74
|
end
|
21
75
|
|
76
|
+
@mutex ||= Mutex.new
|
77
|
+
@types ||= {}
|
78
|
+
|
22
79
|
# Maps a type to its factory.
|
23
80
|
#
|
24
81
|
# @example
|
25
82
|
# Mustermann[:sinatra] # => Mustermann::Sinatra
|
26
83
|
#
|
27
|
-
# @param [Symbol]
|
84
|
+
# @param [Symbol] name a pattern type identifier
|
28
85
|
# @raise [ArgumentError] if the type is not supported
|
29
86
|
# @return [Class, #new] pattern factory
|
30
|
-
def self.[](
|
31
|
-
|
32
|
-
|
33
|
-
|
87
|
+
def self.[](name)
|
88
|
+
return name if name.respond_to? :new
|
89
|
+
@types.fetch(normalized = normalized_type(name)) do
|
90
|
+
@mutex.synchronize do
|
91
|
+
error = try_require "mustermann/#{normalized}"
|
92
|
+
@types.fetch(normalized) { raise ArgumentError, "unsupported type %p#{" (#{error.message})" if error}" % name }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [LoadError, nil]
|
98
|
+
# @!visibility private
|
99
|
+
def self.try_require(path)
|
100
|
+
require(path)
|
101
|
+
nil
|
102
|
+
rescue LoadError => error
|
103
|
+
raise(error) unless error.path == path
|
104
|
+
error
|
105
|
+
end
|
106
|
+
|
107
|
+
# @!visibility private
|
108
|
+
def self.register(name, type)
|
109
|
+
@types[normalized_type(name)] = type
|
34
110
|
end
|
35
111
|
|
36
112
|
# @!visibility private
|
37
|
-
def self.
|
38
|
-
|
39
|
-
identifiers.each { |i| @register[i] = [constant, load] }
|
40
|
-
@register
|
113
|
+
def self.normalized_type(type)
|
114
|
+
type.to_s.gsub('-', '_').downcase
|
41
115
|
end
|
42
116
|
|
43
117
|
# @!visibility private
|
@@ -46,12 +120,13 @@ module Mustermann
|
|
46
120
|
require 'mustermann/extension'
|
47
121
|
object.register Extension
|
48
122
|
end
|
123
|
+
end
|
49
124
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
125
|
+
# :nocov:
|
126
|
+
begin
|
127
|
+
require 'mustermann/visualizer' if defined?(Pry) or defined?(IRB)
|
128
|
+
rescue LoadError => error
|
129
|
+
raise error unless error.path == 'mustermann/visualizer'
|
130
|
+
$stderr.puts(error.message) if caller_locations[1].absolute_path =~ %r{/lib/pry/|/irb/|^\((?:irb|pry)\)$}
|
131
|
+
end
|
132
|
+
# :nocov:
|
@@ -0,0 +1,44 @@
|
|
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
|
+
new.translate(ast, start, stop)
|
12
|
+
ast
|
13
|
+
end
|
14
|
+
|
15
|
+
translate(:node) do |start, stop|
|
16
|
+
t.set_boundaries(node, start, stop)
|
17
|
+
t(payload, node.start, node.stop)
|
18
|
+
end
|
19
|
+
|
20
|
+
translate(:with_look_ahead) do |start, stop|
|
21
|
+
t.set_boundaries(node, start, stop)
|
22
|
+
t(head, node.start, node.stop)
|
23
|
+
t(payload, node.start, node.stop)
|
24
|
+
end
|
25
|
+
|
26
|
+
translate(Array) do |start, stop|
|
27
|
+
each do |subnode|
|
28
|
+
t(subnode, start, stop)
|
29
|
+
start = subnode.stop
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
translate(Object) { |*| node }
|
34
|
+
|
35
|
+
# Checks that a node is within the given boundaries.
|
36
|
+
# @!visibility private
|
37
|
+
def set_boundaries(node, start, stop)
|
38
|
+
node.start = start if node.start.nil? or node.start < start
|
39
|
+
node.stop = node.start + node.min_size if node.stop.nil? or node.stop < node.start
|
40
|
+
node.stop = stop if node.stop > stop
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -15,6 +15,10 @@ module Mustermann
|
|
15
15
|
translate(:optional) { |**o| "(?:%s)?" % t(payload, **o) }
|
16
16
|
translate(:char) { |**o| t.encoded(payload, **o) }
|
17
17
|
|
18
|
+
translate :union do |**options|
|
19
|
+
"(?:%s)" % payload.map { |e| "(?:%s)" % t(e, **options) }.join(?|)
|
20
|
+
end
|
21
|
+
|
18
22
|
translate :expression do |greedy: true, **options|
|
19
23
|
t(payload, allow_reserved: operator.allow_reserved, greedy: greedy && !operator.allow_reserved,
|
20
24
|
parametric: operator.parametric, separator: operator.separator, **options)
|
@@ -53,14 +57,14 @@ module Mustermann
|
|
53
57
|
end
|
54
58
|
|
55
59
|
private
|
56
|
-
def qualified(string, greedy: true, **options)
|
60
|
+
def qualified(string, greedy: true, **options) "#{string}#{qualifier || "+#{?? unless greedy}"}" end
|
57
61
|
def with_lookahead(string, lookahead: nil, **options) lookahead ? "(?:(?!#{lookahead})#{string})" : string end
|
58
62
|
def from_hash(hash, **options) pattern(capture: hash[name.to_sym], **options) end
|
59
63
|
def from_array(array, **options) Regexp.union(*array.map { |e| pattern(capture: e, **options) }) end
|
60
64
|
def from_symbol(symbol, **options) qualified(with_lookahead("[[:#{symbol}:]]", **options), **options) end
|
61
65
|
def from_string(string, **options) Regexp.new(string.chars.map { |c| t.encoded(c, **options) }.join) end
|
62
66
|
def from_nil(**options) qualified(with_lookahead(default(**options), **options), **options) end
|
63
|
-
def default(**options) "[^/\\?#]"
|
67
|
+
def default(**options) constraint || "[^/\\?#]" end
|
64
68
|
end
|
65
69
|
|
66
70
|
# @!visibility private
|
@@ -69,7 +73,7 @@ module Mustermann
|
|
69
73
|
# splats are always non-greedy
|
70
74
|
# @!visibility private
|
71
75
|
def pattern(**options)
|
72
|
-
".*?"
|
76
|
+
constraint || ".*?"
|
73
77
|
end
|
74
78
|
end
|
75
79
|
|
@@ -120,7 +124,10 @@ module Mustermann
|
|
120
124
|
return Regexp.escape(char) unless uri_decode
|
121
125
|
encoded = escape(char, escape: /./)
|
122
126
|
list = [escape(char), encoded.downcase, encoded.upcase].uniq.map { |c| Regexp.escape(c) }
|
123
|
-
|
127
|
+
if char == " "
|
128
|
+
list << encoded('+') if space_matches_plus
|
129
|
+
list << " "
|
130
|
+
end
|
124
131
|
"(?:%s)" % list.join("|")
|
125
132
|
end
|
126
133
|
|
@@ -139,9 +146,8 @@ module Mustermann
|
|
139
146
|
#
|
140
147
|
# @!visibility private
|
141
148
|
def compile(ast, except: nil, **options)
|
142
|
-
except
|
143
|
-
|
144
|
-
Regexp.new(expression)
|
149
|
+
except &&= "(?!#{translate(except, no_captures: true, **options)}\\Z)"
|
150
|
+
Regexp.new("#{except}#{translate(ast, **options)}")
|
145
151
|
end
|
146
152
|
end
|
147
153
|
|
@@ -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
|
|
@@ -46,11 +50,15 @@ module Mustermann
|
|
46
50
|
nested
|
47
51
|
end
|
48
52
|
|
53
|
+
translate :union do
|
54
|
+
payload.map { |e| t(e) }.inject(:+)
|
55
|
+
end
|
56
|
+
|
49
57
|
# helper method for captures
|
50
58
|
# @!visibility private
|
51
|
-
def for_capture(node)
|
59
|
+
def for_capture(node, **options)
|
52
60
|
name = node.name.to_sym
|
53
|
-
pattern('%s', name, name => /(?!#{pattern_for(node)})./)
|
61
|
+
pattern('%s', name, name => /(?!#{pattern_for(node, **options)})./)
|
54
62
|
end
|
55
63
|
|
56
64
|
# maps sorted key list to sprintf patterns and filters
|
@@ -70,7 +78,7 @@ module Mustermann
|
|
70
78
|
def add(ast)
|
71
79
|
translate(ast).each do |keys, pattern, filter|
|
72
80
|
self.keys.concat(keys).uniq!
|
73
|
-
mappings[keys.
|
81
|
+
mappings[keys.sort] ||= [keys, pattern, filter]
|
74
82
|
end
|
75
83
|
end
|
76
84
|
|
@@ -82,10 +90,12 @@ module Mustermann
|
|
82
90
|
|
83
91
|
# @see Mustermann::Pattern#expand
|
84
92
|
# @!visibility private
|
85
|
-
def expand(
|
86
|
-
|
93
|
+
def expand(values)
|
94
|
+
values = values.each_with_object({}){ |(key, value), new_hash|
|
95
|
+
new_hash[value.instance_of?(Array) ? [key] * value.length : key] = value }
|
96
|
+
keys, pattern, filters = mappings.fetch(values.keys.flatten.sort) { error_for(values) }
|
87
97
|
filters.each { |key, filter| values[key] &&= escape(values[key], also_escape: filter) }
|
88
|
-
pattern % values.values_at(*keys)
|
98
|
+
pattern % (values[keys] || values.values_at(*keys))
|
89
99
|
end
|
90
100
|
|
91
101
|
# @see Mustermann::Pattern#expandable?
|
@@ -112,7 +122,7 @@ module Mustermann
|
|
112
122
|
# @see Mustermann::AST::Translator#expand
|
113
123
|
# @!visibility private
|
114
124
|
def escape(string, *args)
|
115
|
-
# URI::Parser is pretty slow, let's not
|
125
|
+
# URI::Parser is pretty slow, let's not send every string to it, even if it's unnecessary
|
116
126
|
string =~ /\A\w*\Z/ ? string : super
|
117
127
|
end
|
118
128
|
|
data/lib/mustermann/ast/node.rb
CHANGED
@@ -4,14 +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
|
13
|
+
@names ||= {}
|
14
|
+
@names[name] ||= begin
|
15
|
+
const_name = constant_name(name)
|
16
|
+
Object.const_get(const_name) if Object.const_defined?(const_name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Turns a class name into a node identifier.
|
21
|
+
# @!visibility private
|
22
|
+
def self.type
|
23
|
+
name[/[^:]+$/].split(/(?<=.)(?=[A-Z])/).map(&:downcase).join(?_).to_sym
|
15
24
|
end
|
16
25
|
|
17
26
|
# @!visibility private
|
@@ -36,6 +45,12 @@ module Mustermann
|
|
36
45
|
self.payload = payload
|
37
46
|
end
|
38
47
|
|
48
|
+
# @!visibility private
|
49
|
+
def is_a?(type)
|
50
|
+
type = Node[type] if type.is_a? Symbol
|
51
|
+
super(type)
|
52
|
+
end
|
53
|
+
|
39
54
|
# Double dispatch helper for reading from the buffer into the payload.
|
40
55
|
# @!visibility private
|
41
56
|
def parse
|
@@ -58,8 +73,38 @@ module Mustermann
|
|
58
73
|
yield(self) unless called
|
59
74
|
end
|
60
75
|
|
76
|
+
# @return [Integer] length of the substring
|
77
|
+
# @!visibility private
|
78
|
+
def length
|
79
|
+
stop - start if start and stop
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Integer] minimum size for a node
|
83
|
+
# @!visibility private
|
84
|
+
def min_size
|
85
|
+
0
|
86
|
+
end
|
87
|
+
|
88
|
+
# Turns a class name into a node identifier.
|
89
|
+
# @!visibility private
|
90
|
+
def type
|
91
|
+
self.class.type
|
92
|
+
end
|
93
|
+
|
61
94
|
# @!visibility private
|
62
95
|
class Capture < Node
|
96
|
+
# @see Mustermann::AST::Compiler::Capture#default
|
97
|
+
# @!visibility private
|
98
|
+
attr_accessor :constraint
|
99
|
+
|
100
|
+
# @see Mustermann::AST::Compiler::Capture#qualified
|
101
|
+
# @!visibility private
|
102
|
+
attr_accessor :qualifier
|
103
|
+
|
104
|
+
# @see Mustermann::AST::Pattern#map_param
|
105
|
+
# @!visibility private
|
106
|
+
attr_accessor :convert
|
107
|
+
|
63
108
|
# @see Mustermann::AST::Node#parse
|
64
109
|
# @!visibility private
|
65
110
|
def parse
|
@@ -73,6 +118,11 @@ module Mustermann
|
|
73
118
|
|
74
119
|
# @!visibility private
|
75
120
|
class Char < Node
|
121
|
+
# @return [Integer] minimum size for a node
|
122
|
+
# @!visibility private
|
123
|
+
def min_size
|
124
|
+
1
|
125
|
+
end
|
76
126
|
end
|
77
127
|
|
78
128
|
# AST node for template expressions.
|
@@ -83,13 +133,21 @@ module Mustermann
|
|
83
133
|
end
|
84
134
|
|
85
135
|
# @!visibility private
|
86
|
-
class
|
136
|
+
class Composition < Node
|
87
137
|
# @!visibility private
|
88
138
|
def initialize(payload = nil, **options)
|
89
139
|
super(Array(payload), **options)
|
90
140
|
end
|
91
141
|
end
|
92
142
|
|
143
|
+
# @!visibility private
|
144
|
+
class Group < Composition
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!visibility private
|
148
|
+
class Union < Composition
|
149
|
+
end
|
150
|
+
|
93
151
|
# @!visibility private
|
94
152
|
class Optional < Node
|
95
153
|
end
|
@@ -113,6 +171,11 @@ module Mustermann
|
|
113
171
|
|
114
172
|
# @!visibility private
|
115
173
|
class Separator < Node
|
174
|
+
# @return [Integer] minimum size for a node
|
175
|
+
# @!visibility private
|
176
|
+
def min_size
|
177
|
+
1
|
178
|
+
end
|
116
179
|
end
|
117
180
|
|
118
181
|
# @!visibility private
|
@@ -144,7 +207,8 @@ module Mustermann
|
|
144
207
|
attr_accessor :head, :at_end
|
145
208
|
|
146
209
|
# @!visibility private
|
147
|
-
def initialize(payload, at_end)
|
210
|
+
def initialize(payload, at_end, **options)
|
211
|
+
super(**options)
|
148
212
|
self.head, *self.payload = Array(payload)
|
149
213
|
self.at_end = at_end
|
150
214
|
end
|