trailblazer-activity 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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