trailblazer-activity 0.13.0 → 0.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36888239a75b308074977ecb9637e421940f63a53fdafbadb5a65106152fc550
4
- data.tar.gz: 3aff2a8fb3da591b98f72f387555791c8273dc02968b68a5f4eb8d59a69f7e45
3
+ metadata.gz: 2c710a0a76320ef3d041b5abeff53c24b34b1923caff08e3c92656919af62431
4
+ data.tar.gz: b235c975eff4e9174110270e73c75e56ec3d10fd3038616039a5de27322043d3
5
5
  SHA512:
6
- metadata.gz: bffee75027f1af8e1dd87487f07505bc5e8238e62eb67ffc1a128f90fbaeb5d4feadcbac2294991ef69febd71245292dd39128fd52e78bba11a9be8afce1cdf9
7
- data.tar.gz: 8ac7bc2b36e8db2349134ce935d1da0881a749c881dca6703c1ed192862d526a734de5da8c956f59b60293300f3dd3fabb05c0e21fbd5187cf2a53b09992c368
6
+ metadata.gz: 38bff15d697d6362b36df62be35a05248510ac6c4326962a1e12c0c3fb041da30aa39c1c6ab8c591d77cbcb186534a4284bfeba33b6cc79e4ef684ef7837e9b9
7
+ data.tar.gz: 586078a93ac567f69c079b017f054513e4069b8613e63badb19a0b61a8f72fbdfb22e3fb98fa8d97c233a4ee1b2818537d9e4df72d2fe0c7a2b7dcdb0b4a5bf4
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
@@ -6,10 +6,10 @@ jobs:
6
6
  fail-fast: false
7
7
  matrix:
8
8
  # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
9
- ruby: [2.5, 2.6, 2.7, '3.0', head, jruby, jruby-head]
9
+ ruby: [2.5, 2.6, 2.7, '3.0', 3.1, head, jruby, jruby-head]
10
10
  runs-on: ubuntu-latest
11
11
  steps:
12
- - uses: actions/checkout@v2
12
+ - uses: actions/checkout@v3
13
13
  - uses: ruby/setup-ruby@v1
14
14
  with:
15
15
  ruby-version: ${{ matrix.ruby }}
data/CHANGES.md CHANGED
@@ -1,3 +1,16 @@
1
+ # 0.14.0
2
+
3
+ * Remove `Pipeline.insert_before` and friends. Pipeline is now altered using ADDS mechanics, just
4
+ as we do it with the `Sequence` in the `trailblazer-activity-dsl-linear` gem.
5
+ * `Pipeline::Merge` is now `TaskWrap::Extension`. The "pre-friendly interface" you used to leverage for creating
6
+ taskWrap (tw) extensions is now deprecated and you will see warnings. See https://trailblazer.to/2.1/docs/activity.html#activity-taskwrap-extension
7
+ * Replace `TaskWrap::Extension()` with `TaskWrap::Extension.WrapStatic()` as a consistent interface for creating tW extensions at compile-time.
8
+ * Remove `Insert.find`.
9
+ * Rename `Activity::State::Config` to `Activity::Config`.
10
+ * Move `VariableMapping` to the `trailblazer-activity-dsl-linear` gem.
11
+ * Move `Pipeline.prepend` to the `trailblazer-activity-linear-dsl` gem.
12
+ * Add `Testing#assert_call` as a consistent test implementation. (0.14.0.beta2)
13
+
1
14
  # 0.13.0
2
15
 
3
16
  * Removed `TaskWrap::Inject::Defaults`. This is now implemented through `dsl`'s `:inject` option.
@@ -33,7 +46,7 @@
33
46
 
34
47
  # 0.11.2
35
48
 
36
- * Updrading `trailblazer-context` version :drum:
49
+ * Upgrading `trailblazer-context` version :drum:
37
50
 
38
51
  # 0.11.1
39
52
 
@@ -0,0 +1,153 @@
1
+ module Trailblazer
2
+ class Activity
3
+ # The Adds interface are mechanics to alter sequences/pipelines.
4
+ # "one" ADDS structure: {row: ..., insert: [Insert, "id"]}
5
+ #
6
+ # To work with the instructions provided here, the pipeline structure
7
+ # needs to expose {#to_a}.
8
+ module Adds
9
+ module_function
10
+ # @returns Sequence/Pipeline New sequence instance
11
+ # @private
12
+ def insert_row(pipeline, row:, insert:)
13
+ insert_function, *args = insert
14
+
15
+ insert_function.(pipeline, row, *args)
16
+ end
17
+
18
+ # Inserts one or more {Adds} into {pipeline}.
19
+ def apply_adds(pipeline, adds)
20
+ adds.each do |add|
21
+ pipeline = insert_row(pipeline, **add)
22
+ end
23
+
24
+ pipeline
25
+ end
26
+
27
+ # @param inserts Array of friendly interface insertions
28
+ # def call(pipeline, *inserts)
29
+ # adds = build_adds_for_friendly_interface(inserts)
30
+
31
+ # Adds.apply_adds(pipeline, adds)
32
+ # end
33
+
34
+ module FriendlyInterface
35
+ # @public
36
+ # @return Array of ADDS
37
+ #
38
+ # Translate a collection of friendly interface to ADDS.
39
+ # This is a mini-DSL, if you want.
40
+ def self.adds_for(inserts)
41
+ inserts.collect do |task, options|
42
+ build_adds(task, **options)
43
+ end
44
+ end
45
+
46
+ # @private
47
+ def self.build_adds(task, id:, prepend: "task_wrap.call_task", append: nil)
48
+ insert, insert_id = append ? [:Append, append] : [:Prepend, prepend]
49
+
50
+ {
51
+ insert: [Activity::Adds::Insert.method(insert), insert_id],
52
+ row: TaskWrap::Pipeline::Row(id, task)
53
+ }
54
+ end
55
+ end
56
+
57
+ # Functions to alter the Sequence/Pipeline by inserting, replacing, or deleting a row.
58
+ #
59
+ # they don't mutate the data structure but rebuild it, has to respond to {to_a}
60
+ #
61
+ # These methods are invoked via {Adds.apply_adds} and should never be called directly.
62
+ module Insert
63
+ module_function
64
+
65
+ # Append {new_row} after {insert_id}.
66
+ def Append(pipeline, new_row, insert_id=nil)
67
+ build_from_ary(pipeline, insert_id) do |ary, index|
68
+ index = ary.size if index.nil? # append to end of pipeline.
69
+
70
+ range_before_index(ary, index+1) + [new_row] + Array(ary[index+1..-1])
71
+ end
72
+ end
73
+
74
+ # Insert {new_row} before {insert_id}.
75
+ def Prepend(pipeline, new_row, insert_id=nil)
76
+ build_from_ary(pipeline, insert_id) do |ary, index|
77
+ index = 0 if index.nil? # Prepend to beginning of pipeline.
78
+
79
+ range_before_index(ary, index) + [new_row] + ary[index..-1]
80
+ end
81
+ end
82
+
83
+ def Replace(pipeline, new_row, insert_id)
84
+ build_from_ary(pipeline, insert_id) do |ary, index|
85
+ range_before_index(ary, index) + [new_row] + ary[index+1..-1]
86
+ end
87
+ end
88
+
89
+ def Delete(pipeline, _, insert_id)
90
+ build_from_ary(pipeline, insert_id) do |ary, index|
91
+ range_before_index(ary, index) + ary[index+1..-1]
92
+ end
93
+ end
94
+
95
+ # @private
96
+ def build(sequence, rows)
97
+ sequence.class.new(rows)
98
+ end
99
+
100
+ # @private
101
+ def find_index(ary, insert_id)
102
+ ary.find_index { |row| row.id == insert_id }
103
+ end
104
+
105
+ # Converts the pipeline structure to an array,
106
+ # automatically finds the index for {insert_id},
107
+ # and calls the user block with the computed values.
108
+ #
109
+ # Single-entry point, could be named {#call}.
110
+ # @private
111
+ def apply_on_ary(pipeline, insert_id, raise_index_error: true, &block)
112
+ ary = pipeline.to_a
113
+
114
+ if insert_id.nil?
115
+ index = nil
116
+ else
117
+ index = find_index(ary, insert_id) # DISCUSS: this only makes sense if there are more than {Append} using this.
118
+ raise IndexError.new(pipeline, insert_id) if index.nil? && raise_index_error
119
+ end
120
+
121
+ _new_ary = yield(ary, index) # call the block.
122
+ end
123
+
124
+ def build_from_ary(pipeline, insert_id, &block)
125
+ new_ary = apply_on_ary(pipeline, insert_id, &block)
126
+
127
+ # Wrap the sequence/pipeline array into a concrete Sequence/Pipeline.
128
+ build(pipeline, new_ary)
129
+ end
130
+
131
+ # Always returns a valid, concat-able array for all indices
132
+ # before the {index}.
133
+ # @private
134
+ def range_before_index(ary, index)
135
+ return [] if index == 0
136
+ ary[0..index-1]
137
+ end
138
+ end # Insert
139
+
140
+ class IndexError < ::IndexError
141
+ def initialize(sequence, step_id)
142
+ valid_ids = sequence.to_a.collect{ |row| row.id.inspect }
143
+
144
+ message = "\n" \
145
+ "\e[31m#{step_id.inspect} is not a valid step ID. Did you mean any of these ?\e[0m\n" \
146
+ "\e[32m#{valid_ids.join("\n")}\e[0m"
147
+
148
+ super(message)
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -76,13 +76,10 @@ module Trailblazer
76
76
  outputs[signal]
77
77
  end
78
78
 
79
- # Common reasons to raise IllegalSignalError are
80
- # * Returning invalid signal from custom Macros
81
- # * Returning invalid signal from steps which are not taskWrapped, for example: `step task: method(:validate)`
82
- #
83
- # Rest assured, it won't be raised in case of below scenarios where they can return any value,
84
- # * Steps with instance method signature, for example, `step :load_user`
85
- # * Steps with proc signature, for example `step ->(ctx, **){}`
79
+ # Common reasons to raise IllegalSignalError are when returning signals from
80
+ # * macros which are not registered
81
+ # * subprocesses where parent process have not registered that signal
82
+ # * ciruit interface steps, for example: `step task: method(:validate)`
86
83
  class IllegalSignalError < RuntimeError
87
84
  attr_reader :task, :signal
88
85
 
@@ -90,9 +87,9 @@ module Trailblazer
90
87
  @task = task
91
88
  @signal = signal
92
89
 
93
- message = "#{exec_context.class}: \n\t" \
94
- "\sUnrecognized Signal `#{signal.inspect}` returned from #{task.inspect}. Registered signals are, \n" \
95
- "- #{outputs.keys.join("\n- ")}"
90
+ message = "#{exec_context.class}: \n" \
91
+ "\e[31mUnrecognized Signal `#{signal.inspect}` returned from #{task.inspect}. Registered signals are, \e[0m\n" \
92
+ "\e[32m#{outputs.keys.map(&:inspect).join("\n")}\e[0m"
96
93
 
97
94
  super(message)
98
95
  end
@@ -1,33 +1,32 @@
1
1
  module Trailblazer
2
- module Activity::State
3
- # Compile-time
4
- #
5
- # DISCUSS: we could replace parts with Hamster::Hash.
2
+ class Activity
3
+ # Config API allows you to read and write immutably to the activity's
4
+ # {:config} field. Most of the times, this only contains {:wrap_static}.
6
5
  module Config
7
6
  module_function
8
7
 
9
- def set(state, *args)
8
+ def set(config, *args)
10
9
  if args.size == 2
11
10
  key, value = *args
12
11
 
13
- state = state.merge(key => value)
12
+ config = config.merge(key => value)
14
13
  else
15
14
  directive, key, value = *args
16
15
 
17
- state = state.merge( directive => {}.freeze ) unless state.key?(directive)
16
+ config = config.merge( directive => {}.freeze ) unless config.key?(directive)
18
17
 
19
- directive_hash = state[directive].merge(key => value)
20
- state = state.merge( directive => directive_hash.freeze )
18
+ directive_hash = config[directive].merge(key => value)
19
+ config = config.merge( directive => directive_hash.freeze )
21
20
  end
22
21
 
23
- state
22
+ config
24
23
  end
25
24
 
26
- def get(state, *args)
25
+ def get(config, *args)
27
26
  directive, key = *args
28
27
 
29
- return state[directive] if args.size == 1
30
- return state[directive][key] if state.key?(directive)
28
+ return config[directive] if args.size == 1
29
+ return config[directive][key] if config.key?(directive)
31
30
 
32
31
  nil
33
32
  end
@@ -76,7 +76,8 @@ class Trailblazer::Activity
76
76
  end.flatten(1)
77
77
  end
78
78
 
79
- # Invoke each task's extensions (usually coming from the DSL or some macro).
79
+ # Invoke each task's extensions (usually coming from the DSL user or some macro).
80
+ # We're expecting each {ext} to be a {TaskWrap::Extension::WrapStatic} instance.
80
81
  def self.config(implementation, config:)
81
82
  implementation.each do |id, task|
82
83
  task.extensions.each { |ext| config = ext.(config: config, id: id, task: task) } # DISCUSS: ext must return new {Config}.
@@ -11,5 +11,3 @@ module Trailblazer
11
11
  end # Schema
12
12
  end
13
13
  end
14
- require "trailblazer/activity/schema/implementation"
15
- require "trailblazer/activity/schema/intermediate"
@@ -0,0 +1,90 @@
1
+ module Trailblazer
2
+ class Activity
3
+ #
4
+ # Example with tracing:
5
+ #
6
+ # Call the task_wrap circuit:
7
+ # |-- Start
8
+ # |-- Trace.capture_args [optional]
9
+ # |-- Call (call actual task) id: "task_wrap.call_task"
10
+ # |-- Trace.capture_return [optional]
11
+ # |-- Wrap::End
12
+ module TaskWrap
13
+ # inserts must be
14
+ # An {Extension} can be used for {:wrap_runtime}. It expects a collection of
15
+ # "friendly interface" arrays.
16
+ #
17
+ # TaskWrap.Extension([ [task, id: "my_logger", append: "task_wrap.call_task"] ])
18
+ #
19
+ # If you want a {wrap_static} extension, wrap it using `Extension.WrapStatic.new`.
20
+ def self.Extension(*inserts, merge: nil)
21
+ if merge
22
+ return Extension::WrapStatic.new(extension: Extension.new(*merge))
23
+ # TODO: remove me once we drop the pre-friendly interface.
24
+ end
25
+
26
+ Extension.build(*inserts)
27
+ end
28
+
29
+ # An {Extension} is a collection of ADDS objects to be inserted into a taskWrap.
30
+ # It gets called either at
31
+ # * compile-time and adds its steps to the wrap_static (see Extension::WrapStatic)
32
+ # * run-time in {TaskWrap::Runner} and adds its steps dynamically at runtime to the
33
+ # step's taskWrap
34
+ class Extension
35
+ # Build a taskWrap extension from the "friendly interface" {[task, id:, ...]}
36
+ def self.build(*inserts)
37
+ # For performance reasons we're computing the ADDS here and not in {#call}.
38
+ extension_rows = Activity::Adds::FriendlyInterface.adds_for(inserts)
39
+
40
+ new(*extension_rows)
41
+ end
42
+
43
+ def initialize(*extension_rows)
44
+ extension_rows = deprecated_extension_for(extension_rows) # TODO: remove me soon!
45
+
46
+ @extension_rows = extension_rows # those rows are simple ADDS instructions.
47
+ end
48
+
49
+ # Merges {extension_rows} into the {Pipeline} instance.
50
+ # This is usually used in step extensions or at runtime for {wrap_runtime}.
51
+ def call(task_wrap_pipeline)
52
+ Adds.apply_adds(task_wrap_pipeline, @extension_rows)
53
+ end
54
+
55
+ # TODO: remove me once we drop the pre-friendly interface.
56
+ def deprecated_extension_for(extension_rows)
57
+ return extension_rows unless extension_rows.find { |ext| ext.is_a?(Array) }
58
+
59
+ warn "[Trailblazer] You are using the old API for taskWrap extensions.
60
+ Please update to the new TaskWrap.Extension() API: # FIXME !!!!!"
61
+
62
+ extension_rows.collect do |ary|
63
+ {
64
+ insert: ary[0..1],
65
+ row: Pipeline.Row(*ary[2])
66
+ }
67
+ end
68
+ end
69
+
70
+ # Extension are used at compile-time with {wrap_static}, mostly with the {dsl} gem.
71
+ # {WrapStatic} extensions are called for setup through {Intermediate.config} at compile-time.
72
+ # Each extension alters the activity's wrap_static taskWrap.
73
+ class WrapStatic
74
+ def initialize(extension:)
75
+ @extension = extension
76
+ end
77
+
78
+ def call(config:, task:, **)
79
+ # Add the extension's task(s) to the activity's {wrap_static} taskWrap
80
+ # by using the immutable {Config} interface.
81
+ before_pipe = Config.get(config, :wrap_static, task.circuit_task)
82
+
83
+ Config.set(config, :wrap_static, task.circuit_task, @extension.(before_pipe))
84
+ end
85
+ end # WrapStatic
86
+
87
+ end # Extension
88
+ end # TaskWrap
89
+ end
90
+ end
@@ -3,67 +3,62 @@ class Trailblazer::Activity
3
3
  # This "circuit" is optimized for
4
4
  # a) merging speed at run-time, since features like tracing will be applied here.
5
5
  # b) execution speed. Every task in the real circuit is wrapped with one of us.
6
+ #
7
+ # It doesn't come with built-in insertion mechanics (except for {Pipeline.prepend}).
8
+ # Please add/remove steps using the {Activity::Adds} methods.
6
9
  class Pipeline
7
10
  def initialize(sequence)
8
11
  @sequence = sequence # [[id, task], ..]
9
12
  end
10
13
 
14
+ # Execute the pipeline and call all its steps, passing around the {wrap_ctx}.
11
15
  def call(wrap_ctx, original_args)
12
16
  @sequence.each { |(_id, task)| wrap_ctx, original_args = task.(wrap_ctx, original_args) }
13
17
 
14
18
  return wrap_ctx, original_args
15
19
  end
16
20
 
17
- attr_reader :sequence
18
-
19
- def self.insert_before(pipe, before_id, insertion)
20
- index = find_index(pipe, before_id)
21
-
22
- seq = pipe.sequence.dup
23
-
24
- Pipeline.new(seq.insert(index, insertion))
21
+ # Comply with the Adds interface.
22
+ def to_a
23
+ @sequence
25
24
  end
26
25
 
27
- def self.insert_after(pipe, after_id, insertion)
28
- index = find_index(pipe, after_id)
26
+ # TODO: remove me when old tW extension API is deprecated.
27
+ def self.method(name)
28
+ new_name = {
29
+ insert_before: :Prepend,
30
+ insert_after: :Append,
31
+ append: :Append,
32
+ prepend: :Prepend,
33
+ }.fetch(name)
29
34
 
30
- seq = pipe.sequence.dup
35
+ warn "[Trailblazer] Using `Trailblazer::Activity::TaskWrap::Pipeline.method(:#{name})` is deprecated.
36
+ Please use the new API: #FIXME!!!"
31
37
 
32
- Pipeline.new(seq.insert(index+1, insertion))
38
+ Trailblazer::Activity::Adds::Insert.method(new_name)
33
39
  end
34
40
 
35
- def self.append(pipe, _, insertion)
36
- Pipeline.new(pipe.sequence + [insertion])
41
+ def self.Row(id, task)
42
+ Row[id, task]
37
43
  end
38
44
 
39
- def self.prepend(pipe, insertion_id, insertion, replace: 0)
40
- return Pipeline.new(insertion.to_a + pipe.sequence) if insertion_id.nil?
41
-
42
- index = find_index(pipe, insertion_id)
43
-
44
- Pipeline.new(pipe.sequence[0..index-1] + insertion.to_a + pipe.sequence[index+replace..-1])
45
- end
46
-
47
- # @private
48
- def self.find_index(pipe, id)
49
- index = pipe.sequence.find_index { |(seq_id, _)| seq_id == id }
45
+ class Row < Array
46
+ def id
47
+ self[0]
48
+ end
50
49
  end
51
50
 
51
+ # TODO: remove {Merge} when old tW extension API is deprecated.
52
+ class Merge
53
+ def self.new(*inserts)
54
+ warn "[Trailblazer] Using `Trailblazer::Activity::TaskWrap::Pipeline::Merge.new` is deprecated.
55
+ Please use the new TaskWrap.Extension() API: #FIXME!!!"
52
56
 
53
- # Merges {extension_rows} into the {Pipeline} instance.
54
- # This is usually used in step extensions or at runtime for {wrap_runtime}.
55
- #
56
- # {Extension} API
57
- class Merge # TODO: RENAME TO TaskWrap::Extension(::Merge)
58
- def initialize(*extension_rows)
59
- @extension_rows = extension_rows
57
+ # We can safely assume that users calling {Merge.new} are using the old tW extension API, not
58
+ # the "friendly API". That's why we don't go through {Extension.build}.
59
+ TaskWrap::Extension.new(*inserts)
60
60
  end
61
-
62
- def call(task_wrap_pipeline)
63
- @extension_rows.collect { |(insert_function, target_id, row)| task_wrap_pipeline = insert_function.(task_wrap_pipeline, target_id, row) }
64
- task_wrap_pipeline
65
- end
66
- end
61
+ end # Merge
67
62
  end
68
63
  end
69
64
  end
@@ -33,28 +33,7 @@ module Trailblazer
33
33
  # Gets executed in {Intermediate.call} which also provides {config}.
34
34
 
35
35
  def initial_wrap_static(*)
36
- # return initial_sequence
37
- TaskWrap::Pipeline.new([["task_wrap.call_task", TaskWrap.method(:call_task)]])
38
- end
39
-
40
- # Use this in your macros if you want to extend the {taskWrap}.
41
- def Extension(merge:)
42
- Extension.new(merge: Pipeline::Merge.new(*merge))
43
- end
44
-
45
- class Extension
46
- def initialize(merge:)
47
- @merge = merge
48
- end
49
-
50
- # Compile-time:
51
- # Gets called via the {Normalizer} and represents an {:extensions} item.
52
- # Adds/alters the activity's {wrap_static}.
53
- def call(config:, task:, **)
54
- before_pipe = State::Config.get(config, :wrap_static, task.circuit_task)
55
-
56
- State::Config.set(config, :wrap_static, task.circuit_task, @merge.(before_pipe))
57
- end
36
+ Pipeline.new([Pipeline.Row("task_wrap.call_task", TaskWrap.method(:call_task))])
58
37
  end
59
38
  end # TaskWrap
60
39
  end
@@ -62,4 +41,4 @@ end
62
41
  require "trailblazer/activity/task_wrap/pipeline"
63
42
  require "trailblazer/activity/task_wrap/call_task"
64
43
  require "trailblazer/activity/task_wrap/runner"
65
- require "trailblazer/activity/task_wrap/variable_mapping"
44
+ require "trailblazer/activity/task_wrap/extension"
@@ -54,6 +54,38 @@ module Trailblazer
54
54
  Schema = Trailblazer::Activity::Schema
55
55
  TaskWrap = Trailblazer::Activity::TaskWrap
56
56
 
57
+ # `:seq` is always passed into ctx.
58
+ # @param :seq String What the {:seq} variable in the result ctx looks like. (expected seq)
59
+ # @param :expected_ctx_variables Variables that are added during the call by the asserted activity.
60
+ def assert_call(activity, terminus: :success, seq: "[]", expected_ctx_variables: {}, **ctx_variables)
61
+ # Call without taskWrap!
62
+ signal, (ctx, _) = activity.([{seq: [], **ctx_variables}, _flow_options = {}]) # simply call the activity with the input you want to assert.
63
+
64
+ assert_call_for(signal, ctx, terminus: terminus, seq: seq, **expected_ctx_variables, **ctx_variables)
65
+ end
66
+
67
+ # Use {TaskWrap.invoke} to call the activity.
68
+ def assert_invoke(activity, terminus: :success, seq: "[]", circuit_options: {}, expected_ctx_variables: {}, **ctx_variables) # DISCUSS: only for {activity} gem?
69
+ signal, (ctx, _flow_options) = TaskWrap.invoke(
70
+ activity,
71
+ [
72
+ {seq: [], **ctx_variables},
73
+ {} # flow_options
74
+ ],
75
+ **circuit_options
76
+ )
77
+
78
+ assert_call_for(signal, ctx, terminus: terminus, seq: seq, **ctx_variables, **expected_ctx_variables) # DISCUSS: ordering of variables?
79
+ end
80
+
81
+ def assert_call_for(signal, ctx, terminus: :success, seq: "[]", **ctx_variables)
82
+ assert_equal signal.to_h[:semantic], terminus, "assert_call expected #{terminus} terminus, not #{signal}. Use assert_call(activity, terminus: #{signal.to_h[:semantic].inspect})"
83
+
84
+ assert_equal ctx.inspect, {seq: "%%%"}.merge(ctx_variables).inspect.sub('"%%%"', seq)
85
+
86
+ return ctx
87
+ end
88
+
57
89
  module Implementing
58
90
  extend Activity::Testing.def_tasks(:a, :b, :c, :d, :f, :g)
59
91
 
@@ -67,25 +99,27 @@ module Trailblazer
67
99
  Implementing
68
100
  end
69
101
 
70
- def flat_activity
102
+ def flat_activity(implementing: Implementing)
71
103
  return @_flat_activity if defined?(@_flat_activity)
72
104
 
73
105
  intermediate = Inter.new(
74
106
  {
75
107
  Inter::TaskRef("Start.default") => [Inter::Out(:success, :B)],
76
- Inter::TaskRef(:B, additional: true) => [Inter::Out(:success, :C)],
108
+ Inter::TaskRef(:B, additional: true) => [Inter::Out(:success, :C), Inter::Out(:failure, "End.failure")],
77
109
  Inter::TaskRef(:C) => [Inter::Out(:success, "End.success")],
78
- Inter::TaskRef("End.success", stop_event: true) => [Inter::Out(:success, nil)]
110
+ Inter::TaskRef("End.success", stop_event: true) => [Inter::Out(:success, nil)],
111
+ Inter::TaskRef("End.failure", stop_event: true) => [Inter::Out(:failure, nil)],
79
112
  },
80
- ["End.success"],
113
+ ["End.success", "End.failure"],
81
114
  ["Start.default"], # start
82
115
  )
83
116
 
84
117
  implementation = {
85
118
  "Start.default" => Schema::Implementation::Task(st = Implementing::Start, [Activity::Output(Activity::Right, :success)], []),
86
- :B => Schema::Implementation::Task(b = Implementing.method(:b), [Activity::Output(Activity::Right, :success)], []),
87
- :C => Schema::Implementation::Task(c = Implementing.method(:c), [Activity::Output(Activity::Right, :success)], []),
119
+ :B => Schema::Implementation::Task(b = implementing.method(:b), [Activity::Output(Activity::Right, :success), Activity::Output(Activity::Left, :failure)], []),
120
+ :C => Schema::Implementation::Task(c = implementing.method(:c), [Activity::Output(Activity::Right, :success)], []),
88
121
  "End.success" => Schema::Implementation::Task(_es = Implementing::Success, [Activity::Output(Implementing::Success, :success)], []), # DISCUSS: End has one Output, signal is itself?
122
+ "End.failure" => Schema::Implementation::Task(Implementing::Failure, [Activity::Output(Implementing::Failure, :failure)], []), # DISCUSS: End has one Output, signal is itself?
89
123
  }
90
124
 
91
125
  schema = Inter.(intermediate, implementation)
@@ -1,7 +1,7 @@
1
1
  module Trailblazer
2
2
  module Version
3
3
  module Activity
4
- VERSION = '0.13.0'.freeze
4
+ VERSION = '0.14.0'.freeze
5
5
  end
6
6
  end
7
7
  end
@@ -14,12 +14,8 @@ module Trailblazer
14
14
  )
15
15
  end
16
16
 
17
- # Reader and writer method for an Activity.
18
- # The writer {dsl[:key] = "value"} exposes immutable behavior and will replace the old
19
- # @state with a new, modified copy.
20
- #
21
- # Always use the accessors to avoid leaking state to other components
22
- # due to mutable write operations.
17
+ # DISCUSS: we could remove this reader in the future
18
+ # and use {Activity.to_h[:config]}.
23
19
  def [](*key)
24
20
  @schema[:config][*key]
25
21
  end
@@ -36,10 +32,13 @@ end
36
32
 
37
33
  require "trailblazer/activity/structures"
38
34
  require "trailblazer/activity/schema"
35
+ require "trailblazer/activity/schema/implementation"
36
+ require "trailblazer/activity/schema/intermediate"
39
37
  require "trailblazer/activity/circuit"
40
38
  require "trailblazer/activity/config"
41
39
  require "trailblazer/activity/introspect"
42
40
  require "trailblazer/activity/task_wrap"
41
+ require "trailblazer/activity/adds"
43
42
  require "trailblazer/activity/task_builder"
44
43
 
45
44
  require "trailblazer/option"
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency "minitest", "~> 5.0"
25
25
  spec.add_development_dependency "minitest-line"
26
26
  spec.add_development_dependency "rake"
27
- spec.add_development_dependency "trailblazer-developer", ">= 0.0.7"
27
+ spec.add_development_dependency "trailblazer-developer", ">= 0.0.23"
28
28
 
29
29
  spec.required_ruby_version = '>= 2.1.0'
30
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trailblazer-activity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-13 00:00:00.000000000 Z
11
+ date: 2022-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trailblazer-context
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: 0.0.7
103
+ version: 0.0.23
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: 0.0.7
110
+ version: 0.0.23
111
111
  description:
112
112
  email:
113
113
  - apotonick@gmail.com
@@ -115,6 +115,7 @@ executables: []
115
115
  extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
+ - ".github/dependabot.yml"
118
119
  - ".github/workflows/ci.yml"
119
120
  - ".gitignore"
120
121
  - CHANGES.md
@@ -125,6 +126,7 @@ files:
125
126
  - Rakefile
126
127
  - lib/trailblazer-activity.rb
127
128
  - lib/trailblazer/activity.rb
129
+ - lib/trailblazer/activity/adds.rb
128
130
  - lib/trailblazer/activity/circuit.rb
129
131
  - lib/trailblazer/activity/config.rb
130
132
  - lib/trailblazer/activity/introspect.rb
@@ -135,9 +137,9 @@ files:
135
137
  - lib/trailblazer/activity/task_builder.rb
136
138
  - lib/trailblazer/activity/task_wrap.rb
137
139
  - lib/trailblazer/activity/task_wrap/call_task.rb
140
+ - lib/trailblazer/activity/task_wrap/extension.rb
138
141
  - lib/trailblazer/activity/task_wrap/pipeline.rb
139
142
  - lib/trailblazer/activity/task_wrap/runner.rb
140
- - lib/trailblazer/activity/task_wrap/variable_mapping.rb
141
143
  - lib/trailblazer/activity/testing.rb
142
144
  - lib/trailblazer/activity/version.rb
143
145
  - trailblazer-activity.gemspec
@@ -1,83 +0,0 @@
1
- class Trailblazer::Activity
2
- module TaskWrap
3
- # Creates taskWrap steps to map variables before and after the actual step.
4
- # We hook into the Normalizer, process `:input` and `:output` directives and
5
- # translate them into a {DSL::Extension}.
6
- #
7
- # Note that the two options are not the only way to create filters, you can use the
8
- # more low-level {Scoped()} from the `dsl` gem, too, and write your own filter logic.
9
- module VariableMapping
10
- # Places filters before/after the {call_task}.
11
- # Note that {input} and {output} are automatically wrapped.
12
- def self.merge_instructions_for(input, output, id:)
13
- [
14
- [TaskWrap::Pipeline.method(:insert_before), "task_wrap.call_task", ["task_wrap.input", TaskWrap::Input.new(input, id: id)]],
15
- [TaskWrap::Pipeline.method(:insert_after), "task_wrap.call_task", ["task_wrap.output", TaskWrap::Output.new(output, id: id)]],
16
- ]
17
- end
18
- end
19
-
20
- # TaskWrap step to compute the incoming {Context} for the wrapped task.
21
- # This allows renaming, filtering, hiding, of the options passed into the wrapped task.
22
- #
23
- # Both Input and Output are typically to be added before and after task_wrap.call_task.
24
- #
25
- # @note Assumption: we always have :input _and_ :output, where :input produces a Context and :output decomposes it.
26
-
27
- # Calls your {@filter} and replaces the original ctx with your returned one.
28
- class Input
29
- def initialize(filter, id:)
30
- @filter = filter
31
- @id = id
32
- end
33
-
34
- # {input.call()} is invoked in the taskWrap pipeline.
35
- # {original_args} are the actual args passed to the wrapped task: [ [ctx, ..], circuit_options ]
36
- # We now swap the ctx in {original_args} and our filtered one. The original "outside" ctx is keyed in
37
- # {wrap_ctx} with the filter ID.
38
- def call(wrap_ctx, original_args)
39
- # let user compute new ctx for the wrapped task.
40
- input_ctx = apply_filter(*original_args)
41
-
42
- # decompose the original_args since we want to modify them.
43
- (original_ctx, original_flow_options), original_circuit_options = original_args
44
-
45
- wrap_ctx = wrap_ctx.merge(@id => original_ctx) # remember the original ctx by the key {@id}.
46
-
47
- # instead of the original Context, pass on the filtered `input_ctx` in the wrap.
48
- return wrap_ctx, [[input_ctx, original_flow_options], original_circuit_options]
49
- end
50
-
51
- private
52
-
53
- # Invoke the @filter callable with the original circuit interface.
54
- def apply_filter((ctx, original_flow_options), original_circuit_options)
55
- @filter.([ctx, original_flow_options], **original_circuit_options) # returns {new_ctx}.
56
- end
57
- end
58
-
59
- # TaskWrap step to compute the outgoing {Context} from the wrapped task.
60
- # This allows renaming, filtering, hiding, of the options returned from the wrapped task.
61
- class Output
62
- def initialize(filter, id:)
63
- @filter = filter
64
- @id = id
65
- end
66
-
67
- # Runs your filter and replaces the ctx in `wrap_ctx[:return_args]` with the filtered one.
68
- def call(wrap_ctx, original_args)
69
- (original_ctx, _original_flow_options), original_circuit_options = original_args
70
-
71
- returned_ctx, returned_flow_options = wrap_ctx[:return_args] # this is the Context returned from {call}ing the wrapped user task.
72
- original_ctx = wrap_ctx[@id] # grab the original ctx from before which was set in the {:input} filter.
73
- # let user compute the output.
74
- output_ctx = @filter.(returned_ctx, [original_ctx, returned_flow_options], **original_circuit_options)
75
-
76
- wrap_ctx = wrap_ctx.merge( return_args: [output_ctx, returned_flow_options] )
77
-
78
- # and then pass on the "new" context.
79
- return wrap_ctx, original_args
80
- end
81
- end
82
- end # Wrap
83
- end