trailblazer-activity-dsl-linear 1.0.0 → 1.2.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/.github/workflows/ci.yml +2 -3
- data/CHANGES.md +100 -0
- data/Gemfile +7 -4
- data/Rakefile +1 -1
- data/lib/trailblazer/activity/dsl/linear/feature/merge.rb +2 -2
- data/lib/trailblazer/activity/dsl/linear/feature/patch.rb +9 -5
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/dsl.rb +241 -156
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/runtime.rb +276 -0
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping.rb +70 -226
- data/lib/trailblazer/activity/dsl/linear/helper/path.rb +37 -18
- data/lib/trailblazer/activity/dsl/linear/helper.rb +38 -17
- data/lib/trailblazer/activity/dsl/linear/normalizer/extensions.rb +63 -0
- data/lib/trailblazer/activity/dsl/linear/normalizer/inherit.rb +90 -0
- data/lib/trailblazer/activity/dsl/linear/normalizer/output_tuples.rb +160 -0
- data/lib/trailblazer/activity/dsl/linear/normalizer/terminus.rb +26 -29
- data/lib/trailblazer/activity/dsl/linear/normalizer.rb +99 -160
- data/lib/trailblazer/activity/dsl/linear/sequence/builder.rb +3 -2
- data/lib/trailblazer/activity/dsl/linear/sequence/compiler.rb +21 -17
- data/lib/trailblazer/activity/dsl/linear/sequence/search.rb +2 -8
- data/lib/trailblazer/activity/dsl/linear/strategy.rb +56 -17
- data/lib/trailblazer/activity/dsl/linear/version.rb +1 -1
- data/lib/trailblazer/activity/dsl/linear.rb +13 -1
- data/lib/trailblazer/activity/fast_track.rb +96 -67
- data/lib/trailblazer/activity/path.rb +35 -53
- data/lib/trailblazer/activity/railway.rb +63 -65
- data/trailblazer-activity-dsl-linear.gemspec +8 -8
- metadata +27 -18
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/inherit.rb +0 -38
@@ -3,7 +3,7 @@ module Trailblazer
|
|
3
3
|
module DSL
|
4
4
|
module Linear
|
5
5
|
module Helper
|
6
|
-
# Normalizer logic for {Path() do end}.
|
6
|
+
# Normalizer logic for {Path() do ... end}.
|
7
7
|
#
|
8
8
|
# TODO: it would be cool to be able to connect an (empty) path to specific termini,
|
9
9
|
# this would work if we could add multiple magnetic_to.
|
@@ -11,6 +11,7 @@ module Trailblazer
|
|
11
11
|
# Normalizer steps to handle Path() macro.
|
12
12
|
module Normalizer
|
13
13
|
module_function
|
14
|
+
|
14
15
|
# Replace a block-expecting {PathBranch} instance with another one that's holding
|
15
16
|
# the global {:block} from {#step ... do end}.
|
16
17
|
def forward_block_for_path_branch(ctx, options:, normalizer_options:, library_options:, **)
|
@@ -20,12 +21,12 @@ module Trailblazer
|
|
20
21
|
return unless block
|
21
22
|
|
22
23
|
output, path_branch =
|
23
|
-
non_symbol_options.find { |output, cfg| cfg.
|
24
|
+
non_symbol_options.find { |output, cfg| cfg.is_a?(Linear::PathBranch) }
|
24
25
|
|
25
26
|
path_branch_with_block = Linear::PathBranch.new(
|
26
|
-
normalizer_options
|
27
|
-
merge(path_branch.options)
|
28
|
-
merge(block: block)
|
27
|
+
normalizer_options
|
28
|
+
.merge(path_branch.options)
|
29
|
+
.merge(block: block)
|
29
30
|
)
|
30
31
|
|
31
32
|
ctx[:options] = ctx[:options].merge(non_symbol_options: non_symbol_options.merge(output => path_branch_with_block))
|
@@ -35,10 +36,10 @@ module Trailblazer
|
|
35
36
|
# The {Track} instance contains all additional {adds} steps and
|
36
37
|
# is picked up in {Normalizer.normalize_connections_from_dsl}.
|
37
38
|
def convert_paths_to_tracks(ctx, non_symbol_options:, block: false, **)
|
38
|
-
new_tracks = non_symbol_options
|
39
|
-
find_all { |output, cfg| cfg.
|
40
|
-
collect { |output, cfg| [output, Path.convert_path_to_track(block:
|
41
|
-
to_h
|
39
|
+
new_tracks = non_symbol_options
|
40
|
+
.find_all { |output, cfg| cfg.is_a?(Linear::PathBranch) }
|
41
|
+
.collect { |output, cfg| [output, Path.convert_path_to_track(block: block, **cfg.options)] }
|
42
|
+
.to_h
|
42
43
|
|
43
44
|
ctx[:non_symbol_options] = non_symbol_options.merge(new_tracks)
|
44
45
|
end
|
@@ -46,7 +47,27 @@ module Trailblazer
|
|
46
47
|
|
47
48
|
module_function
|
48
49
|
|
49
|
-
def convert_path_to_track(track_color: "track_#{rand}", connect_to: nil, before: false, block: nil, **options)
|
50
|
+
def convert_path_to_track(track_color: "track_#{rand}", connect_to: nil, before: false, block: nil, terminus: nil, **options)
|
51
|
+
options =
|
52
|
+
if end_task = options[:end_task] # TODO: remove in 2.0.
|
53
|
+
Activity::Deprecate.warn Linear::Deprecate.dsl_caller_location,
|
54
|
+
%(Using `:end_task` and `:end_id` in Path() is deprecated, use `:terminus` instead. Please refer to https://trailblazer.to/2.1/docs/activity.html#activity-wiring-api-path-end_task-end_id-deprecation)
|
55
|
+
|
56
|
+
options.merge(
|
57
|
+
end_task: Activity.End(end_task.to_h[:semantic]),
|
58
|
+
end_id: options[:end_id]
|
59
|
+
)
|
60
|
+
elsif connect_to
|
61
|
+
{}
|
62
|
+
elsif terminus
|
63
|
+
options.merge(
|
64
|
+
end_task: Activity.End(terminus),
|
65
|
+
end_id: "End.#{terminus}"
|
66
|
+
)
|
67
|
+
else # Path() with End() inside block.
|
68
|
+
{}
|
69
|
+
end
|
70
|
+
|
50
71
|
# DISCUSS: if anyone overrides `#step` in the "outer" activity, this won't be applied inside the branch.
|
51
72
|
|
52
73
|
# DISCUSS: use Path::Sequencer::Builder here instead?
|
@@ -54,7 +75,7 @@ module Trailblazer
|
|
54
75
|
|
55
76
|
seq = path.to_h[:sequence]
|
56
77
|
# Strip default ends `Start.default` and `End.success` (if present).
|
57
|
-
seq = seq[1..-1].reject{ |row| row[3][:stop_event] && row.id ==
|
78
|
+
seq = seq[1..-1].reject { |row| row[3][:stop_event] && row.id == "End.success" }
|
58
79
|
|
59
80
|
if connect_to
|
60
81
|
seq = connect_for_sequence(seq, connect_to: connect_to)
|
@@ -69,7 +90,7 @@ module Trailblazer
|
|
69
90
|
insert_method = options[:stop_event] ? Activity::Adds::Insert.method(:Append) : Activity::Adds::Insert.method(:Prepend)
|
70
91
|
|
71
92
|
insert_target = "End.success" # insert before/after
|
72
|
-
insert_target = before if before && connect_to.instance_of?(
|
93
|
+
insert_target = before if before && connect_to.instance_of?(Linear::Normalizer::OutputTuples::Track) # FIXME: this is a bit hacky, of course!
|
73
94
|
|
74
95
|
{
|
75
96
|
row: row,
|
@@ -78,7 +99,7 @@ module Trailblazer
|
|
78
99
|
end
|
79
100
|
|
80
101
|
# Connect the Output() => Track(path_track)
|
81
|
-
|
102
|
+
Linear::Normalizer::OutputTuples::Track.new(track_color, adds, {})
|
82
103
|
end
|
83
104
|
|
84
105
|
# Connect last row of the {sequence} to the given step via its {Id}
|
@@ -87,16 +108,14 @@ module Trailblazer
|
|
87
108
|
output, _ = sequence[-1][2][0].(sequence, sequence[-1]) # FIXME: the Forward() proc contains the row's Output, and the only current way to retrieve it is calling the search strategy. It should be Forward#to_h
|
88
109
|
|
89
110
|
# searches = [Search.ById(output, connect_to.value)]
|
90
|
-
searches = [Sequence::Search.ById(output, connect_to.value)] if connect_to.instance_of?(
|
91
|
-
searches = [Sequence::Search.Forward(output, connect_to.color)] if connect_to.instance_of?(
|
111
|
+
searches = [Sequence::Search.ById(output, connect_to.value)] if connect_to.instance_of?(Linear::Normalizer::OutputTuples::Id)
|
112
|
+
searches = [Sequence::Search.Forward(output, connect_to.color)] if connect_to.instance_of?(Linear::Normalizer::OutputTuples::Track) # FIXME: use existing mapping logic!
|
92
113
|
|
93
114
|
row = sequence[-1]
|
94
115
|
row = row[0..1] + [searches] + [row[3]] # FIXME: not mutating an array is so hard: we only want to replace the "searches" element, index 2
|
95
116
|
row = Sequence::Row[*row]
|
96
117
|
|
97
|
-
sequence
|
98
|
-
|
99
|
-
sequence
|
118
|
+
sequence[0..-2] + [row]
|
100
119
|
end
|
101
120
|
end # Path
|
102
121
|
end
|
@@ -6,10 +6,7 @@ module Trailblazer
|
|
6
6
|
# and then get processed in the normalizer.
|
7
7
|
#
|
8
8
|
# @private
|
9
|
-
|
10
|
-
Id = Struct.new(:value)
|
11
|
-
Track = Struct.new(:color, :adds, :options)
|
12
|
-
Extension = Struct.new(:callable) do
|
9
|
+
Extension = Struct.new(:callable) do
|
13
10
|
def call(*args, &block)
|
14
11
|
callable.(*args, &block)
|
15
12
|
end
|
@@ -27,14 +24,14 @@ module Trailblazer
|
|
27
24
|
|
28
25
|
# Output( Left, :failure )
|
29
26
|
# Output( :failure ) #=> Output::Semantic
|
30
|
-
def Output(signal, semantic=nil)
|
31
|
-
return
|
27
|
+
def Output(signal, semantic = nil)
|
28
|
+
return Normalizer::OutputTuples::Output::Semantic.new(signal) if semantic.nil?
|
32
29
|
|
33
|
-
|
30
|
+
Normalizer::OutputTuples::Output::CustomOutput.new(signal, semantic)
|
34
31
|
end
|
35
32
|
|
36
33
|
def End(semantic)
|
37
|
-
|
34
|
+
Normalizer::OutputTuples::End.new(semantic).freeze
|
38
35
|
end
|
39
36
|
|
40
37
|
def end_id(semantic:, **)
|
@@ -42,32 +39,56 @@ module Trailblazer
|
|
42
39
|
end
|
43
40
|
|
44
41
|
def Track(color, wrap_around: false)
|
45
|
-
Track.new(color, [], wrap_around: wrap_around).freeze
|
42
|
+
Normalizer::OutputTuples::Track.new(color, [], wrap_around: wrap_around).freeze
|
46
43
|
end
|
47
44
|
|
48
45
|
def Id(id)
|
49
|
-
Id.new(id).freeze
|
46
|
+
Normalizer::OutputTuples::Id.new(id).freeze
|
50
47
|
end
|
51
48
|
|
52
49
|
def Path(**options, &block)
|
53
|
-
options = options.merge(block: block) if
|
50
|
+
options = options.merge(block: block) if block
|
54
51
|
|
55
52
|
Linear::PathBranch.new(options) # picked up by normalizer.
|
56
53
|
end
|
57
54
|
|
58
55
|
# Computes the {:outputs} options for {activity}.
|
59
|
-
|
60
|
-
|
56
|
+
# @param :strict If true, all outputs of {activity} will be wired to the track named after the
|
57
|
+
# output's semantic.
|
58
|
+
def Subprocess(activity, patch: {}, strict: false)
|
59
|
+
activity = Linear::Patch.customize(activity, options: patch)
|
60
|
+
|
61
|
+
outputs = activity.to_h[:outputs]
|
62
|
+
options = {}
|
63
|
+
|
64
|
+
if strict
|
65
|
+
options.merge!(
|
66
|
+
outputs.collect { |output| [Normalizer::OutputTuples::Output::Semantic.new(output.semantic, true), Track(output.semantic)] }.to_h
|
67
|
+
)
|
68
|
+
end
|
61
69
|
|
62
70
|
{
|
63
71
|
task: activity,
|
64
|
-
outputs:
|
72
|
+
outputs: outputs.collect { |output| [output.semantic, output] }.to_h,
|
65
73
|
}
|
74
|
+
.merge(options)
|
66
75
|
end
|
67
76
|
|
68
|
-
def In(**kws)
|
69
|
-
|
70
|
-
|
77
|
+
def In(**kws)
|
78
|
+
VariableMapping::DSL::In(**kws)
|
79
|
+
end
|
80
|
+
|
81
|
+
def Out(**kws)
|
82
|
+
VariableMapping::DSL::Out(**kws)
|
83
|
+
end
|
84
|
+
|
85
|
+
def Inject(*args, **kws)
|
86
|
+
VariableMapping::DSL::Inject(*args, **kws)
|
87
|
+
end
|
88
|
+
|
89
|
+
def Extension(*args, **kws)
|
90
|
+
Normalizer::Extensions.Extension(*args, **kws)
|
91
|
+
end
|
71
92
|
|
72
93
|
def DataVariable
|
73
94
|
DataVariableName.new
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
module DSL
|
4
|
+
module Linear
|
5
|
+
module Normalizer
|
6
|
+
# Implements {:extensions} option and allows adding taskWrap extensions using
|
7
|
+
# Linear::Normalizer::Extensions.Extension().
|
8
|
+
module Extensions
|
9
|
+
module_function
|
10
|
+
|
11
|
+
Extension = Struct.new(:generic?, :id)
|
12
|
+
|
13
|
+
def Extension(is_generic: false)
|
14
|
+
Extension.new(is_generic, rand) # {id} has to be unique for every Extension instance (for Hash identity).
|
15
|
+
end
|
16
|
+
|
17
|
+
# Convert {:extensions} option to {Extension} tuples. The new way of adding extensions is
|
18
|
+
# step ..., Extension() => my_extension
|
19
|
+
def convert_extensions_option_to_tuples(ctx, non_symbol_options:, extensions: nil, **)
|
20
|
+
return unless extensions
|
21
|
+
# TODO: deprecate {:extensions} in the DSL?
|
22
|
+
|
23
|
+
extensions_tuples = extensions.collect { |ext| [Extension(), ext] }.to_h
|
24
|
+
|
25
|
+
ctx.merge!(
|
26
|
+
non_symbol_options: non_symbol_options.merge(extensions_tuples)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def compile_extensions(ctx, non_symbol_options:, **)
|
31
|
+
extensions_ary =
|
32
|
+
non_symbol_options
|
33
|
+
.find_all { |k, v| k.instance_of?(Extension) }
|
34
|
+
.to_h
|
35
|
+
.values
|
36
|
+
|
37
|
+
ctx.merge!(
|
38
|
+
extensions: extensions_ary
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Don't record Extension()s created by the DSL! This happens in VariableMapping, for instance.
|
43
|
+
# Either the user also inherits I/O tuples and the extension will be recreated,
|
44
|
+
# or they really don't want this particular extension to be inherited.
|
45
|
+
def compile_recorded_extensions(ctx, non_symbol_options:, **)
|
46
|
+
recorded_extension_tuples =
|
47
|
+
non_symbol_options
|
48
|
+
.find_all { |k, v| k.instance_of?(Extension) }
|
49
|
+
.reject { |k, v| k.generic? }
|
50
|
+
.to_h
|
51
|
+
|
52
|
+
ctx.merge!(
|
53
|
+
non_symbol_options: non_symbol_options.merge(
|
54
|
+
Normalizer::Inherit.Record(recorded_extension_tuples, type: :extensions)
|
55
|
+
)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end # Extensions
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
module DSL
|
4
|
+
module Linear
|
5
|
+
module Normalizer
|
6
|
+
# Implements the generic {:inherit} option.
|
7
|
+
# Features such as variable mapping or the Wiring API can
|
8
|
+
# use the generic behavior for their inheritance.
|
9
|
+
|
10
|
+
# "generic": built by the DSL from options, options that are inherited, so you might not want to record or inherit generic options
|
11
|
+
module Inherit
|
12
|
+
module_function
|
13
|
+
|
14
|
+
# Options you want to have stored and inherited can be
|
15
|
+
# declared using Record.
|
16
|
+
Record = Struct.new(:options, :type, :non_symbol_options?) # FIXME: i hate symbol vs. non-symbol.
|
17
|
+
|
18
|
+
def Record(options, type:, non_symbol_options: true)
|
19
|
+
{Record.new(options, type, non_symbol_options) => nil}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Currently, the {:inherit} option copies over {:extensions} from the original step and merges them with new :extensions.
|
23
|
+
#
|
24
|
+
### Recall
|
25
|
+
# Fetch remembered options and add them to the processed options.
|
26
|
+
def recall_recorded_options(ctx, non_symbol_options:, sequence:, id:, inherit: nil, extensions: [], **)
|
27
|
+
return unless inherit === true || inherit.is_a?(Array)
|
28
|
+
|
29
|
+
# E.g. {variable_mapping: true, wiring_api: true}
|
30
|
+
types_to_recall =
|
31
|
+
if inherit === true
|
32
|
+
# we want to inherit "everything": extensions, output_tuples, variable_mapping
|
33
|
+
Hash.new { true }
|
34
|
+
else
|
35
|
+
inherit.collect { |type| [type, true] }.to_h
|
36
|
+
end
|
37
|
+
|
38
|
+
row = find_row(sequence, id) # from this row we're inheriting options.
|
39
|
+
|
40
|
+
# DISCUSS: should we maybe revert the idea of separating options by type?
|
41
|
+
# Anyway, key idea here is that Record() users don't have to know these details
|
42
|
+
# about symbol vs. non-symbol.
|
43
|
+
symbol_options_to_merge = {}
|
44
|
+
non_symbol_options_to_merge = {}
|
45
|
+
|
46
|
+
row.data[:recorded_options].each do |type, record|
|
47
|
+
next unless types_to_recall[type]
|
48
|
+
|
49
|
+
target = record.non_symbol_options? ? non_symbol_options_to_merge : symbol_options_to_merge
|
50
|
+
target.merge!(record.options)
|
51
|
+
end
|
52
|
+
|
53
|
+
ctx[:non_symbol_options] = non_symbol_options_to_merge.merge(non_symbol_options)
|
54
|
+
|
55
|
+
ctx.merge!(symbol_options_to_merge)
|
56
|
+
|
57
|
+
ctx.merge!(
|
58
|
+
inherited_recorded_options: row.data[:recorded_options]
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_row(sequence, id)
|
63
|
+
index = Activity::Adds::Insert.find_index(sequence, id)
|
64
|
+
sequence[index]
|
65
|
+
end
|
66
|
+
|
67
|
+
### Record
|
68
|
+
# Figure out what to remember from the options and store it in {row.data[:recorded_options]}.
|
69
|
+
# Note that this is generic logic not tied to variable_mapping, OutputTuples or anything.
|
70
|
+
def compile_recorded_options(ctx, non_symbol_options:, **)
|
71
|
+
recorded_options = {}
|
72
|
+
|
73
|
+
non_symbol_options
|
74
|
+
.find_all { |k, v| k.instance_of?(Record) }
|
75
|
+
.collect do |k, v|
|
76
|
+
recorded_options[k.type] = k # DISCUSS: we overwrite potential data with same type.
|
77
|
+
end
|
78
|
+
|
79
|
+
ctx.merge!(
|
80
|
+
recorded_options: recorded_options,
|
81
|
+
# add {row.data[:recorded_options]} in Sequence:
|
82
|
+
non_symbol_options: non_symbol_options.merge(Strategy.DataVariable() => :recorded_options)
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end # Inherit
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
module DSL
|
4
|
+
module Linear
|
5
|
+
module Normalizer
|
6
|
+
# Implements Output(:success) => Track(:success)
|
7
|
+
# Internals are documented: https://trailblazer.to/2.1/docs/internals.html#internals-wiring-api-output-tuples
|
8
|
+
module OutputTuples
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# Connector when using Id(:validate).
|
12
|
+
class Id < Struct.new(:value)
|
13
|
+
def to_a(*)
|
14
|
+
return [Linear::Sequence::Search.method(:ById), value], [] # {value} is the "target".
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Connector when using Track(:success).
|
19
|
+
class Track < Struct.new(:color, :adds, :options)
|
20
|
+
def to_a(*)
|
21
|
+
search_strategy = options[:wrap_around] ? :WrapAround : :Forward
|
22
|
+
|
23
|
+
return [Linear::Sequence::Search.method(search_strategy), color], adds
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Connector representing a (to-be-created?) terminus when using End(:semantic).
|
28
|
+
class End < Struct.new(:semantic)
|
29
|
+
def to_a(ctx)
|
30
|
+
end_id = Linear::Strategy.end_id(semantic: semantic)
|
31
|
+
end_exists = Activity::Adds::Insert.find_index(ctx[:sequence], end_id)
|
32
|
+
|
33
|
+
terminus = Activity.End(semantic)
|
34
|
+
|
35
|
+
adds = end_exists ? [] : OutputTuples::Connections.add_terminus(terminus, id: end_id, sequence: ctx[:sequence], normalizers: ctx[:normalizers])
|
36
|
+
|
37
|
+
return [Linear::Sequence::Search.method(:ById), end_id], adds
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Logic related to {Output() => ...}, called "Wiring API".
|
42
|
+
# TODO: move to different namespace (feature/dsl)
|
43
|
+
def Output(semantic, is_generic: true)
|
44
|
+
Normalizer::OutputTuples::Output::Semantic.new(semantic, is_generic)
|
45
|
+
end
|
46
|
+
|
47
|
+
module Output
|
48
|
+
# Note that both {Semantic} and {CustomOutput} are {is_a?(Output)}
|
49
|
+
Semantic = Struct.new(:semantic, :generic?).include(Output)
|
50
|
+
CustomOutput = Struct.new(:signal, :semantic, :generic?).include(Output) # generic? is always false
|
51
|
+
end
|
52
|
+
|
53
|
+
def normalize_output_tuples(ctx, non_symbol_options:, **)
|
54
|
+
output_tuples = non_symbol_options.find_all { |k, v| k.is_a?(OutputTuples::Output) }
|
55
|
+
|
56
|
+
ctx.merge!(output_tuples: output_tuples)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Remember all custom (non-generic) {:output_tuples}.
|
60
|
+
def remember_custom_output_tuples(ctx, output_tuples:, non_symbol_options:, **)
|
61
|
+
# We don't include generic OutputSemantic (from Subprocess(strict: true)) for inheritance, as this is not a user customization.
|
62
|
+
custom_output_tuples = output_tuples.reject { |k, v| k.generic? }
|
63
|
+
|
64
|
+
# save Output() tuples under {:custom_output_tuples} for inheritance.
|
65
|
+
ctx.merge!(
|
66
|
+
non_symbol_options: non_symbol_options.merge(
|
67
|
+
Normalizer::Inherit.Record(custom_output_tuples.to_h, type: :custom_output_tuples)
|
68
|
+
)
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Take all Output(signal, semantic), convert to OutputSemantic and extend {:outputs}.
|
73
|
+
# Since only users use this style, we don't have to filter.
|
74
|
+
def register_additional_outputs(ctx, output_tuples:, outputs:, **)
|
75
|
+
# We need to preserve the order when replacing Output with OutputSemantic,
|
76
|
+
# that's why we recreate {output_tuples} here.
|
77
|
+
output_tuples =
|
78
|
+
output_tuples.collect do |(output, connector)|
|
79
|
+
if output.is_a?(Output::CustomOutput)
|
80
|
+
# add custom output to :outputs.
|
81
|
+
outputs = outputs.merge(output.semantic => Activity.Output(output.signal, output.semantic))
|
82
|
+
|
83
|
+
# Convert Output to OutputSemantic.
|
84
|
+
[Strategy.Output(output.semantic), connector]
|
85
|
+
else
|
86
|
+
[output, connector]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
ctx.merge!(
|
91
|
+
output_tuples: output_tuples,
|
92
|
+
outputs: outputs
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Implements {inherit: :outputs, strict: false}
|
97
|
+
# return connections from {parent} step which are supported by current step
|
98
|
+
def filter_inherited_output_tuples(ctx, outputs:, output_tuples:, inherit: false, inherited_recorded_options: {}, **)
|
99
|
+
return unless inherit === true
|
100
|
+
strict_outputs = false # TODO: implement "strict outputs" for inherit! meaning we connect all inherited Output regardless of the new activity's interface
|
101
|
+
return if strict_outputs === true
|
102
|
+
|
103
|
+
# Grab the inherited {:custom_output_tuples} so we can throw those out if the new activity doesn't support
|
104
|
+
# the respective outputs.
|
105
|
+
inherited_output_tuples_record = inherited_recorded_options[:custom_output_tuples]
|
106
|
+
inherited_output_tuples = inherited_output_tuples_record ? inherited_output_tuples_record.options : {}
|
107
|
+
|
108
|
+
allowed_semantics = outputs.keys # these outputs are exposed by the inheriting step.
|
109
|
+
inherited_semantics = inherited_output_tuples.collect { |output, _| output.semantic }
|
110
|
+
unsupported_semantics = inherited_semantics - allowed_semantics
|
111
|
+
|
112
|
+
filtered_output_tuples = output_tuples.reject { |output, _| unsupported_semantics.include?(output.semantic) }
|
113
|
+
|
114
|
+
ctx.merge!(
|
115
|
+
output_tuples: filtered_output_tuples.to_h
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Compile connections from tuples.
|
120
|
+
module Connections
|
121
|
+
module_function
|
122
|
+
|
123
|
+
# we want this in the end:
|
124
|
+
# {output.semantic => search strategy}
|
125
|
+
# Process {Output(:semantic) => target} and make them {:connections}.
|
126
|
+
# This combines {:connections} and {:outputs}
|
127
|
+
def compile_wirings(ctx, adds:, output_tuples:, outputs:, id:, **)
|
128
|
+
# DISCUSS: how could we add another magnetic_to to an end?
|
129
|
+
# Go through all {Output() => Track()/Id()/End()} tuples.
|
130
|
+
wirings =
|
131
|
+
output_tuples.collect do |output, connector|
|
132
|
+
(search_builder, search_args), connector_adds = connector.to_a(ctx) # Call {#to_a} on Track/Id/End/...
|
133
|
+
|
134
|
+
adds += connector_adds
|
135
|
+
|
136
|
+
semantic = output.semantic
|
137
|
+
output = outputs[semantic] || raise("No `#{semantic}` output found for #{id.inspect} and outputs #{outputs.inspect}")
|
138
|
+
|
139
|
+
# return proc to be called when compiling Seq, e.g. {ById(output, :id)}
|
140
|
+
search_builder.(output, *search_args)
|
141
|
+
end
|
142
|
+
|
143
|
+
ctx[:wirings] = wirings
|
144
|
+
ctx[:adds] = adds
|
145
|
+
end
|
146
|
+
|
147
|
+
# Returns ADDS for the new terminus.
|
148
|
+
# @private
|
149
|
+
def add_terminus(end_event, id:, sequence:, normalizers:)
|
150
|
+
step_options = Linear::Sequence::Builder.invoke_normalizer_for(:terminus, end_event, {id: id}, sequence: sequence, normalizer_options: {}, normalizers: normalizers)
|
151
|
+
|
152
|
+
step_options[:adds]
|
153
|
+
end
|
154
|
+
end # Connections
|
155
|
+
end # OutputTuples
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -10,28 +10,28 @@ module Trailblazer
|
|
10
10
|
def Normalizer
|
11
11
|
normalizer_steps =
|
12
12
|
{
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
13
|
+
"activity.normalize_step_interface" => Normalizer.Task(Normalizer.method(:normalize_step_interface)), # first
|
14
|
+
"activity.merge_library_options" => Normalizer.Task(Normalizer.method(:merge_library_options)), # Merge "macro"/user options over library options.
|
15
|
+
"activity.normalize_for_macro" => Normalizer.Task(Normalizer.method(:merge_user_options)),
|
16
|
+
"activity.normalize_normalizer_options" => Normalizer.Task(Normalizer.method(:merge_normalizer_options)),
|
17
|
+
"activity.normalize_non_symbol_options" => Normalizer.Task(Normalizer.method(:normalize_non_symbol_options)),
|
18
|
+
"activity.normalize_context" => Normalizer.method(:normalize_context),
|
19
|
+
"terminus.normalize_task" => Normalizer.Task(Terminus.method(:normalize_task)),
|
20
|
+
"terminus.normalize_id" => Normalizer.Task(method(:normalize_id)),
|
21
|
+
"terminus.normalize_magnetic_to" => Normalizer.Task(Terminus.method(:normalize_magnetic_to)),
|
22
|
+
"terminus.append_end" => Normalizer.Task(Terminus.method(:append_end)),
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
"activity.compile_data" => Normalizer.Task(Normalizer.method(:compile_data)), # FIXME: redundant with {Linear::Normalizer}.
|
25
|
+
"activity.create_row" => Normalizer.Task(Normalizer.method(:create_row)),
|
26
|
+
"activity.create_add" => Normalizer.Task(Normalizer.method(:create_add)),
|
27
|
+
"activity.create_adds" => Normalizer.Task(Normalizer.method(:create_adds)),
|
28
28
|
}
|
29
29
|
|
30
30
|
TaskWrap::Pipeline.new(normalizer_steps.to_a)
|
31
31
|
end
|
32
32
|
|
33
33
|
# @private
|
34
|
-
def normalize_id(ctx, id: nil,
|
34
|
+
def normalize_id(ctx, semantic:, id: nil, **)
|
35
35
|
ctx.merge!(
|
36
36
|
id: id || Strategy.end_id(semantic: semantic)
|
37
37
|
)
|
@@ -40,11 +40,11 @@ module Trailblazer
|
|
40
40
|
# @private
|
41
41
|
# Set {:task} and {:semantic}.
|
42
42
|
def normalize_task(ctx, task:, **)
|
43
|
-
if task.
|
44
|
-
|
43
|
+
if task.is_a?(Activity::End) # DISCUSS: do we want this check?
|
44
|
+
_ctx = _normalize_task_for_end_event(ctx, **ctx)
|
45
45
|
else
|
46
46
|
# When used such as {terminus :found}, create the end event automatically.
|
47
|
-
|
47
|
+
_ctx = _normalize_task_for_symbol(ctx, **ctx)
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
@@ -56,8 +56,8 @@ module Trailblazer
|
|
56
56
|
|
57
57
|
def _normalize_task_for_symbol(ctx, task:, semantic: task, **)
|
58
58
|
ctx.merge!(
|
59
|
-
task:
|
60
|
-
semantic: semantic
|
59
|
+
task: Activity.End(semantic),
|
60
|
+
semantic: semantic
|
61
61
|
)
|
62
62
|
end
|
63
63
|
|
@@ -68,19 +68,16 @@ module Trailblazer
|
|
68
68
|
end
|
69
69
|
|
70
70
|
# @private
|
71
|
-
def append_end(ctx, task:,
|
71
|
+
def append_end(ctx, task:, append_to: "End.success", non_symbol_options:, **)
|
72
72
|
terminus_args = {
|
73
|
-
sequence_insert:
|
74
|
-
stop_event:
|
73
|
+
sequence_insert: [Activity::Adds::Insert.method(:Append), append_to],
|
74
|
+
stop_event: true,
|
75
|
+
non_symbol_options: non_symbol_options.merge(Strategy.DataVariable() => [:stop_event, :semantic])
|
75
76
|
}
|
76
77
|
|
77
78
|
ctx.merge!(
|
78
|
-
wirings: [
|
79
|
-
|
80
|
-
Activity::Output.new(task, semantic), # DISCUSS: do we really want to transport the semantic "in" the object?
|
81
|
-
)
|
82
|
-
],
|
83
|
-
adds: [],
|
79
|
+
wirings: [],
|
80
|
+
adds: [],
|
84
81
|
**terminus_args
|
85
82
|
)
|
86
83
|
end
|