grumlin 0.23.0 → 1.0.0.rc2
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/.rubocop.yml +9 -9
- data/Gemfile.lock +1 -1
- data/README.md +100 -142
- data/Rakefile +1 -1
- data/bin/console +18 -3
- data/doc/middlewares.md +49 -10
- data/lib/async/channel.rb +54 -56
- data/lib/grumlin/benchmark/repository.rb +10 -14
- data/lib/grumlin/client.rb +93 -95
- data/lib/grumlin/config.rb +33 -33
- data/lib/grumlin/dummy_transaction.rb +13 -15
- data/lib/grumlin/edge.rb +18 -20
- data/lib/grumlin/expressions/cardinality.rb +5 -9
- data/lib/grumlin/expressions/column.rb +5 -9
- data/lib/grumlin/expressions/expression.rb +7 -11
- data/lib/grumlin/expressions/operator.rb +5 -9
- data/lib/grumlin/expressions/order.rb +5 -9
- data/lib/grumlin/expressions/p.rb +27 -31
- data/lib/grumlin/expressions/pop.rb +5 -9
- data/lib/grumlin/expressions/scope.rb +5 -9
- data/lib/grumlin/expressions/t.rb +5 -9
- data/lib/grumlin/expressions/text_p.rb +5 -9
- data/lib/grumlin/expressions/with_options.rb +17 -21
- data/lib/grumlin/features/feature_list.rb +8 -12
- data/lib/grumlin/features/neptune_features.rb +5 -9
- data/lib/grumlin/features/tinkergraph_features.rb +5 -9
- data/lib/grumlin/features.rb +8 -10
- data/lib/grumlin/middlewares/apply_shortcuts.rb +4 -8
- data/lib/grumlin/middlewares/build_query.rb +16 -20
- data/lib/grumlin/middlewares/builder.rb +15 -0
- data/lib/grumlin/middlewares/cast_results.rb +3 -7
- data/lib/grumlin/middlewares/find_blocklisted_steps.rb +14 -0
- data/lib/grumlin/middlewares/find_mutating_steps.rb +9 -0
- data/lib/grumlin/middlewares/middleware.rb +6 -10
- data/lib/grumlin/middlewares/run_query.rb +3 -7
- data/lib/grumlin/middlewares/serialize_to_bytecode.rb +5 -9
- data/lib/grumlin/middlewares/serialize_to_steps.rb +4 -8
- data/lib/grumlin/path.rb +11 -13
- data/lib/grumlin/property.rb +14 -16
- data/lib/grumlin/query_validators/blocklisted_steps_validator.rb +22 -0
- data/lib/grumlin/query_validators/validator.rb +36 -0
- data/lib/grumlin/repository/error_handling_strategy.rb +36 -40
- data/lib/grumlin/repository/instance_methods.rb +115 -118
- data/lib/grumlin/repository.rb +82 -58
- data/lib/grumlin/request_dispatcher.rb +55 -57
- data/lib/grumlin/request_error_factory.rb +53 -55
- data/lib/grumlin/shortcut.rb +19 -21
- data/lib/grumlin/shortcuts/properties.rb +12 -16
- data/lib/grumlin/shortcuts/storage.rb +67 -74
- data/lib/grumlin/shortcuts/upserts.rb +19 -22
- data/lib/grumlin/shortcuts.rb +23 -25
- data/lib/grumlin/shortcuts_applyer.rb +27 -29
- data/lib/grumlin/step.rb +88 -90
- data/lib/grumlin/step_data.rb +12 -14
- data/lib/grumlin/steppable.rb +23 -25
- data/lib/grumlin/steps.rb +52 -54
- data/lib/grumlin/steps_serializers/bytecode.rb +53 -56
- data/lib/grumlin/steps_serializers/human_readable_bytecode.rb +17 -21
- data/lib/grumlin/steps_serializers/serializer.rb +7 -11
- data/lib/grumlin/steps_serializers/string.rb +26 -30
- data/lib/grumlin/test/rspec/db_cleaner_context.rb +8 -12
- data/lib/grumlin/test/rspec/gremlin_context.rb +18 -16
- data/lib/grumlin/test/rspec.rb +1 -5
- data/lib/grumlin/transaction.rb +34 -36
- data/lib/grumlin/transport.rb +71 -73
- data/lib/grumlin/traversal_start.rb +31 -33
- data/lib/grumlin/traversal_strategies/options_strategy.rb +3 -7
- data/lib/grumlin/traverser.rb +5 -7
- data/lib/grumlin/typed_value.rb +11 -13
- data/lib/grumlin/typing.rb +70 -72
- data/lib/grumlin/version.rb +1 -1
- data/lib/grumlin/vertex.rb +14 -16
- data/lib/grumlin/vertex_property.rb +14 -16
- data/lib/grumlin/with_extension.rb +17 -19
- data/lib/grumlin.rb +13 -0
- metadata +9 -6
- data/lib/grumlin/middlewares/frozen_builder.rb +0 -18
- data/lib/grumlin/sugar.rb +0 -15
@@ -1,73 +1,71 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
598 => ServerTimeoutError,
|
3
|
+
class Grumlin::RequestErrorFactory
|
4
|
+
ERRORS = {
|
5
|
+
499 => Grumlin::InvalidRequestArgumentsError,
|
6
|
+
500 => Grumlin::ServerError,
|
7
|
+
597 => Grumlin::ScriptEvaluationError,
|
8
|
+
599 => Grumlin::ServerSerializationError,
|
9
|
+
598 => Grumlin::ServerTimeoutError,
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
401 => Grumlin::ClientSideError,
|
12
|
+
407 => Grumlin::ClientSideError,
|
13
|
+
498 => Grumlin::ClientSideError
|
14
|
+
}.freeze
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
# Neptune presumably returns message as a JSON string of format
|
17
|
+
# {"detailedMessage":"",
|
18
|
+
# "requestId":"UUID",
|
19
|
+
# "code":"ConcurrentModificationException"}
|
20
|
+
# Currently we simply search for substrings to identify the exact error
|
21
|
+
# TODO: parse json and use `code` instead
|
23
22
|
|
24
|
-
|
25
|
-
|
23
|
+
VERTEX_ALREADY_EXISTS = "Vertex with id already exists:"
|
24
|
+
EDGE_ALREADY_EXISTS = "Edge with id already exists:"
|
26
25
|
|
27
|
-
|
26
|
+
CONCURRENT_VERTEX_INSERT_FAILED = "Failed to complete Insert operation for a Vertex due to conflicting concurrent"
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
CONCURRENT_VERTEX_PROPERTY_INSERT_FAILED =
|
29
|
+
"Failed to complete Insert operation for a VertexProperty due to conflicting concurrent"
|
30
|
+
CONCURRENT_EDGE_PROPERTY_INSERT_FAILED =
|
31
|
+
"Failed to complete Insert operation for a EdgeProperty due to conflicting concurrent"
|
33
32
|
|
34
|
-
|
35
|
-
|
33
|
+
CONCURRENT_EDGE_INSERT_FAILED = "Failed to complete Insert operation for an Edge due to conflicting concurrent"
|
34
|
+
CONCURRENCT_MODIFICATION_FAILED = "Failed to complete operation due to conflicting concurrent"
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
36
|
+
class << self
|
37
|
+
def build(request, response)
|
38
|
+
status = response[:status]
|
39
|
+
query = request[:request]
|
41
40
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
41
|
+
if (error = ERRORS[status[:code]])
|
42
|
+
return (
|
43
|
+
already_exists_error(status) ||
|
44
|
+
concurrent_modification_error(status) ||
|
45
|
+
error
|
46
|
+
).new(status, query)
|
47
|
+
end
|
49
48
|
|
50
|
-
|
49
|
+
return unless Grumlin::RequestDispatcher::SUCCESS[status[:code]].nil?
|
51
50
|
|
52
|
-
|
53
|
-
|
51
|
+
Grumlin::UnknownResponseStatus.new(status)
|
52
|
+
end
|
54
53
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
def already_exists_error(status)
|
55
|
+
return Grumlin::VertexAlreadyExistsError if status[:message]&.include?(VERTEX_ALREADY_EXISTS)
|
56
|
+
return Grumlin::EdgeAlreadyExistsError if status[:message]&.include?(EDGE_ALREADY_EXISTS)
|
57
|
+
end
|
59
58
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
69
|
-
return ConcurrentModificationError if status[:message]&.include?(CONCURRENCT_MODIFICATION_FAILED)
|
59
|
+
def concurrent_modification_error(status) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
60
|
+
return Grumlin::ConcurrentVertexInsertFailedError if status[:message]&.include?(CONCURRENT_VERTEX_INSERT_FAILED)
|
61
|
+
if status[:message]&.include?(CONCURRENT_VERTEX_PROPERTY_INSERT_FAILED)
|
62
|
+
return Grumlin::ConcurrentVertexPropertyInsertFailedError
|
63
|
+
end
|
64
|
+
return Grumlin::ConcurrentEdgeInsertFailedError if status[:message]&.include?(CONCURRENT_EDGE_INSERT_FAILED)
|
65
|
+
if status[:message]&.include?(CONCURRENT_EDGE_PROPERTY_INSERT_FAILED)
|
66
|
+
return Grumlin::ConcurrentEdgePropertyInsertFailedError
|
70
67
|
end
|
68
|
+
return Grumlin::ConcurrentModificationError if status[:message]&.include?(CONCURRENCT_MODIFICATION_FAILED)
|
71
69
|
end
|
72
70
|
end
|
73
71
|
end
|
data/lib/grumlin/shortcut.rb
CHANGED
@@ -1,32 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
extend Forwardable
|
3
|
+
class Grumlin::Shortcut
|
4
|
+
extend Forwardable
|
6
5
|
|
7
|
-
|
6
|
+
attr_reader :name, :block
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
def_delegator :@block, :arity
|
9
|
+
def_delegator :@block, :source_location
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
def initialize(name, lazy: true, &block)
|
12
|
+
@name = name
|
13
|
+
@lazy = lazy
|
14
|
+
@block = block
|
15
|
+
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
def ==(other)
|
18
|
+
@name == other.name && @block == other.block
|
19
|
+
end
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
def lazy?
|
22
|
+
@lazy
|
23
|
+
end
|
25
24
|
|
26
|
-
|
25
|
+
# TODO: to_s, inspect, preview
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
end
|
27
|
+
def apply(object, *args, **params)
|
28
|
+
object.instance_exec(*args, **params, &@block)
|
31
29
|
end
|
32
30
|
end
|
@@ -1,24 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Grumlin
|
4
|
-
|
5
|
-
module Properties
|
6
|
-
extend Grumlin::Shortcuts
|
3
|
+
module Grumlin::Shortcuts::Properties
|
4
|
+
extend Grumlin::Shortcuts
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
shortcut :props do |cardinality = nil, **props|
|
7
|
+
props.reduce(self) do |tt, (prop, value)|
|
8
|
+
next tt if value.nil? # nils are not supported
|
9
|
+
next tt.property(prop, value) if cardinality.nil?
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
tt.property(cardinality, prop, value)
|
12
|
+
end
|
13
|
+
end
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
end
|
15
|
+
shortcut :hasAll do |**props|
|
16
|
+
props.reduce(self) do |tt, (prop, value)|
|
17
|
+
tt.has(prop, value)
|
22
18
|
end
|
23
19
|
end
|
24
20
|
end
|
@@ -1,99 +1,92 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
class Storage
|
6
|
-
extend Forwardable
|
7
|
-
|
8
|
-
class << self
|
9
|
-
def [](other)
|
10
|
-
new(other)
|
11
|
-
end
|
3
|
+
class Grumlin::Shortcuts::Storage
|
4
|
+
extend Forwardable
|
12
5
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
class << self
|
7
|
+
def [](other)
|
8
|
+
new(other)
|
9
|
+
end
|
17
10
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
end
|
11
|
+
def empty
|
12
|
+
@empty ||= new
|
13
|
+
end
|
14
|
+
end
|
24
15
|
|
25
|
-
|
26
|
-
|
27
|
-
|
16
|
+
def initialize(storage = {})
|
17
|
+
@storage = storage
|
18
|
+
storage.each do |n, s|
|
19
|
+
add(n, s)
|
20
|
+
end
|
21
|
+
end
|
28
22
|
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
def_delegator :@storage, :[]
|
24
|
+
def_delegator :@storage, :include?, :known?
|
25
|
+
def_delegator :@storage, :keys, :names
|
26
|
+
def_delegator :self, :__, :g
|
32
27
|
|
33
|
-
|
34
|
-
|
28
|
+
def ==(other)
|
29
|
+
@storage == other.storage
|
30
|
+
end
|
35
31
|
|
36
|
-
|
32
|
+
def add(name, shortcut)
|
33
|
+
@storage[name] = shortcut
|
37
34
|
|
38
|
-
|
39
|
-
next sc.new(name, args: args, params: params, previous_step: self, pool: Grumlin.default_pool)
|
40
|
-
end
|
41
|
-
extend_traversal_classes(shortcut) unless shortcut.lazy?
|
42
|
-
end
|
35
|
+
sc = step_class
|
43
36
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
37
|
+
shortcut_methods_module.define_method(name) do |*args, **params|
|
38
|
+
next sc.new(name, args: args, params: params, previous_step: self, pool: Grumlin.default_pool)
|
39
|
+
end
|
40
|
+
extend_traversal_classes(shortcut) unless shortcut.lazy?
|
41
|
+
end
|
49
42
|
|
50
|
-
|
51
|
-
|
52
|
-
|
43
|
+
def add_from(other)
|
44
|
+
other.storage.each do |name, shortcut|
|
45
|
+
add(name, shortcut)
|
46
|
+
end
|
47
|
+
end
|
53
48
|
|
54
|
-
|
55
|
-
|
56
|
-
|
49
|
+
def __
|
50
|
+
traversal_start_class.new(pool: Grumlin.default_pool)
|
51
|
+
end
|
57
52
|
|
58
|
-
|
59
|
-
|
60
|
-
|
53
|
+
def traversal_start_class
|
54
|
+
@traversal_start_class ||= shortcut_aware_class(Grumlin::TraversalStart)
|
55
|
+
end
|
61
56
|
|
62
|
-
|
63
|
-
|
64
|
-
|
57
|
+
def step_class
|
58
|
+
@step_class ||= shortcut_aware_class(Grumlin::Step)
|
59
|
+
end
|
65
60
|
|
66
|
-
|
61
|
+
protected
|
67
62
|
|
68
|
-
|
63
|
+
attr_reader :storage
|
69
64
|
|
70
|
-
|
65
|
+
private
|
71
66
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
79
|
-
end
|
67
|
+
def shortcut_methods_module
|
68
|
+
@shortcut_methods_module ||= begin
|
69
|
+
shorts = self
|
70
|
+
Module.new do
|
71
|
+
define_method :shortcuts do
|
72
|
+
shorts
|
80
73
|
end
|
81
74
|
end
|
75
|
+
end
|
76
|
+
end
|
82
77
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
78
|
+
def shortcut_aware_class(base)
|
79
|
+
methods = shortcut_methods_module
|
80
|
+
Class.new(base) do
|
81
|
+
include methods
|
82
|
+
end
|
83
|
+
end
|
89
84
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
end
|
94
|
-
step_class.include(m)
|
95
|
-
traversal_start_class.include(m)
|
96
|
-
end
|
85
|
+
def extend_traversal_classes(shortcut)
|
86
|
+
m = Module.new do
|
87
|
+
define_method(shortcut.name, &shortcut.block)
|
97
88
|
end
|
89
|
+
step_class.include(m)
|
90
|
+
traversal_start_class.include(m)
|
98
91
|
end
|
99
92
|
end
|
@@ -1,28 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Grumlin
|
4
|
-
|
5
|
-
module Upserts
|
6
|
-
extend Grumlin::Shortcuts
|
3
|
+
module Grumlin::Shortcuts::Upserts
|
4
|
+
extend Grumlin::Shortcuts
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
6
|
+
shortcut :upsertV do |label, id, create_properties = {}, update_properties = {}|
|
7
|
+
self.V(id)
|
8
|
+
.fold
|
9
|
+
.coalesce(
|
10
|
+
__.unfold,
|
11
|
+
__.addV(label).props(Cardinality.single, **create_properties.merge(T.id => id))
|
12
|
+
).props(Cardinality.single, **update_properties)
|
13
|
+
end
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
15
|
+
shortcut :upsertE do |label, from, to, create_properties = {}, update_properties = {}|
|
16
|
+
id = create_properties[T.id] || Grumlin.fake_uuid(from, label, to)
|
17
|
+
self.V(from)
|
18
|
+
.outE(label).where(__.inV.hasId(to))
|
19
|
+
.fold
|
20
|
+
.coalesce(
|
21
|
+
__.unfold,
|
22
|
+
__.addE(label).from(__.V(from)).to(__.V(to)).props(**create_properties.merge(T.id => id))
|
23
|
+
).props(**update_properties)
|
27
24
|
end
|
28
25
|
end
|
data/lib/grumlin/shortcuts.rb
CHANGED
@@ -1,36 +1,34 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Grumlin
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
3
|
+
module Grumlin::Shortcuts
|
4
|
+
def self.extended(base)
|
5
|
+
base.include(Grumlin::Expressions)
|
6
|
+
end
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
def inherited(subclass)
|
9
|
+
super
|
10
|
+
subclass.shortcuts_from(self)
|
11
|
+
end
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
def shortcut(name, shortcut = nil, override: false, lazy: true, &block)
|
14
|
+
name = name.to_sym
|
15
|
+
lazy = false if override
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
if Grumlin::Step::REGULAR_STEPS.include?(name) && !override
|
18
|
+
raise ArgumentError,
|
19
|
+
"overriding standard gremlin steps is not allowed, if you know what you're doing, pass `override: true`"
|
20
|
+
end
|
22
21
|
|
23
|
-
|
22
|
+
raise ArgumentError, "either shortcut or block must be passed" if [shortcut, block].count(&:nil?) != 1
|
24
23
|
|
25
|
-
|
26
|
-
|
24
|
+
shortcuts.add(name, shortcut || Grumlin::Shortcut.new(name, lazy: lazy, &block))
|
25
|
+
end
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
def shortcuts_from(other_shortcuts)
|
28
|
+
shortcuts.add_from(other_shortcuts.shortcuts)
|
29
|
+
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
end
|
31
|
+
def shortcuts
|
32
|
+
@shortcuts ||= Storage.new
|
35
33
|
end
|
36
34
|
end
|
@@ -1,41 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
class
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
steps
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
processed_steps.add(step.name, args: step.args, params: step.params)
|
19
|
-
end
|
3
|
+
class Grumlin::ShortcutsApplyer
|
4
|
+
class << self
|
5
|
+
def call(steps)
|
6
|
+
return steps if !steps.is_a?(Grumlin::Steps) || !steps.uses_shortcuts?
|
7
|
+
|
8
|
+
shortcuts = steps.shortcuts
|
9
|
+
|
10
|
+
steps = [
|
11
|
+
*process_steps(steps.configuration_steps, shortcuts),
|
12
|
+
*process_steps(steps.steps, shortcuts)
|
13
|
+
]
|
14
|
+
|
15
|
+
Grumlin::Steps.new(shortcuts).tap do |processed_steps|
|
16
|
+
steps.each do |step|
|
17
|
+
processed_steps.add(step.name, args: step.args, params: step.params)
|
20
18
|
end
|
21
19
|
end
|
20
|
+
end
|
22
21
|
|
23
|
-
|
22
|
+
private
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
def process_steps(steps, shortcuts) # rubocop:disable Metrics/AbcSize
|
25
|
+
steps.each_with_object([]) do |step, result|
|
26
|
+
args = step.args.map { |arg| call(arg) }
|
28
27
|
|
29
|
-
|
30
|
-
|
28
|
+
shortcut = shortcuts[step.name]
|
29
|
+
next result << Grumlin::StepData.new(step.name, args: args, params: step.params) unless shortcut&.lazy?
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
t = shortcuts.__
|
32
|
+
step = shortcut.apply(t, *args, **step.params)
|
33
|
+
next if step.nil? || step == t # Shortcut did not add any steps
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
end
|
35
|
+
new_steps = call(Grumlin::Steps.from(step))
|
36
|
+
result.concat(new_steps.configuration_steps, new_steps.steps)
|
39
37
|
end
|
40
38
|
end
|
41
39
|
end
|