mustermann 0.3.1 → 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 +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
|