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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -3
  3. data/CHANGES.md +100 -0
  4. data/Gemfile +7 -4
  5. data/Rakefile +1 -1
  6. data/lib/trailblazer/activity/dsl/linear/feature/merge.rb +2 -2
  7. data/lib/trailblazer/activity/dsl/linear/feature/patch.rb +9 -5
  8. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/dsl.rb +241 -156
  9. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/runtime.rb +276 -0
  10. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping.rb +70 -226
  11. data/lib/trailblazer/activity/dsl/linear/helper/path.rb +37 -18
  12. data/lib/trailblazer/activity/dsl/linear/helper.rb +38 -17
  13. data/lib/trailblazer/activity/dsl/linear/normalizer/extensions.rb +63 -0
  14. data/lib/trailblazer/activity/dsl/linear/normalizer/inherit.rb +90 -0
  15. data/lib/trailblazer/activity/dsl/linear/normalizer/output_tuples.rb +160 -0
  16. data/lib/trailblazer/activity/dsl/linear/normalizer/terminus.rb +26 -29
  17. data/lib/trailblazer/activity/dsl/linear/normalizer.rb +99 -160
  18. data/lib/trailblazer/activity/dsl/linear/sequence/builder.rb +3 -2
  19. data/lib/trailblazer/activity/dsl/linear/sequence/compiler.rb +21 -17
  20. data/lib/trailblazer/activity/dsl/linear/sequence/search.rb +2 -8
  21. data/lib/trailblazer/activity/dsl/linear/strategy.rb +56 -17
  22. data/lib/trailblazer/activity/dsl/linear/version.rb +1 -1
  23. data/lib/trailblazer/activity/dsl/linear.rb +13 -1
  24. data/lib/trailblazer/activity/fast_track.rb +96 -67
  25. data/lib/trailblazer/activity/path.rb +35 -53
  26. data/lib/trailblazer/activity/railway.rb +63 -65
  27. data/trailblazer-activity-dsl-linear.gemspec +8 -8
  28. metadata +27 -18
  29. 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.kind_of?(Linear::PathBranch) }
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.kind_of?(Linear::PathBranch) }.
40
- collect { |output, cfg| [output, Path.convert_path_to_track(block: ctx[:block], **cfg.options)] }.
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 == 'End.success' }
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?(Trailblazer::Activity::DSL::Linear::Track) # FIXME: this is a bit hacky, of course!
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
- return Linear::Track.new(track_color, adds, {})
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?(Trailblazer::Activity::DSL::Linear::Id)
91
- searches = [Sequence::Search.Forward(output, connect_to.color)] if connect_to.instance_of?(Trailblazer::Activity::DSL::Linear::Track) # FIXME: use existing mapping logic!
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 = sequence[0..-2] + [row]
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
- OutputSemantic = Struct.new(:value)
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 OutputSemantic.new(signal) if semantic.nil?
27
+ def Output(signal, semantic = nil)
28
+ return Normalizer::OutputTuples::Output::Semantic.new(signal) if semantic.nil?
32
29
 
33
- Activity.Output(signal, semantic)
30
+ Normalizer::OutputTuples::Output::CustomOutput.new(signal, semantic)
34
31
  end
35
32
 
36
33
  def End(semantic)
37
- Activity.End(semantic)
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 block_given?
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
- def Subprocess(activity, patch: {})
60
- activity = Patch.customize(activity, options: patch)
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: Hash[activity.to_h[:outputs].collect { |output| [output.semantic, output] }]
72
+ outputs: outputs.collect { |output| [output.semantic, output] }.to_h,
65
73
  }
74
+ .merge(options)
66
75
  end
67
76
 
68
- def In(**kws); VariableMapping::DSL::In(**kws); end
69
- def Out(**kws); VariableMapping::DSL::Out(**kws); end
70
- def Inject(**kws); VariableMapping::DSL::Inject(**kws); end
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
- "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)),
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
- "activity.compile_data" => Normalizer.Task(Normalizer.method(:compile_data)), # FIXME
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)),
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, semantic:, **)
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.kind_of?(Activity::End) # DISCUSS: do we want this check?
44
- ctx = _normalize_task_for_end_event(ctx, **ctx)
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
- ctx = _normalize_task_for_symbol(ctx, **ctx)
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: Strategy.End(semantic),
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:, semantic:, append_to: "End.success", **)
71
+ def append_end(ctx, task:, append_to: "End.success", non_symbol_options:, **)
72
72
  terminus_args = {
73
- sequence_insert: [Activity::Adds::Insert.method(:Append), append_to],
74
- stop_event: true
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
- Linear::Sequence::Search::Noop(
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