trailblazer-activity 0.14.0 → 0.15.1

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: 2c710a0a76320ef3d041b5abeff53c24b34b1923caff08e3c92656919af62431
4
- data.tar.gz: b235c975eff4e9174110270e73c75e56ec3d10fd3038616039a5de27322043d3
3
+ metadata.gz: f042c2ed7dab6bafd3932cfbc357ada176de13aed6a91ba05be74f0d9d000ede
4
+ data.tar.gz: 471066060ff8ad800f77e26e0f1cc7090ff1b0a8820971c41de93f1760a6cf74
5
5
  SHA512:
6
- metadata.gz: 38bff15d697d6362b36df62be35a05248510ac6c4326962a1e12c0c3fb041da30aa39c1c6ab8c591d77cbcb186534a4284bfeba33b6cc79e4ef684ef7837e9b9
7
- data.tar.gz: 586078a93ac567f69c079b017f054513e4069b8613e63badb19a0b61a8f72fbdfb22e3fb98fa8d97c233a4ee1b2818537d9e4df72d2fe0c7a2b7dcdb0b4a5bf4
6
+ metadata.gz: 02726f73b7af8b91ee4b34bc87dbb0a15246eb35d520ae2cd165519382d0578cad791466ff81e85ed4f95e31cf3988562115c40a6f90ab0c80b96c776c444809
7
+ data.tar.gz: dfb7ad6686ed5f0a8668a032c2b65357b0e5c857afa6459a9f1a101e728aa454c38cc1814133172aabbf8ee63ef350dda9c33f2997527161e566e93b1c10da44
@@ -6,7 +6,7 @@ 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', 3.1, head, jruby, jruby-head]
9
+ ruby: [2.5, 2.6, 2.7, '3.0', 3.1, 3.2, head, jruby, jruby-head]
10
10
  runs-on: ubuntu-latest
11
11
  steps:
12
12
  - uses: actions/checkout@v3
data/CHANGES.md CHANGED
@@ -1,3 +1,46 @@
1
+ # 0.15.1
2
+
3
+ * Introduce `Extension.WrapStatic()` as a consistent interface for creating wrap_static extensions
4
+ exposing the friendly interface.
5
+ * Deprecate `Extension(merge: ...)` since we have `Extension.WrapStatic` now.
6
+ * Better deprecation warnings for extensions using `Insert` and not the friendly interface.
7
+
8
+ # 0.15.0
9
+
10
+ * Rename `Circuit::Run` to `Circuit::Runner` for consistency with `TaskWrap::Runner`.
11
+ * Add `:flat_activity` keyword argument to `Testing.nested_activity`, so you can inject any activity for `:D`.
12
+ Also, allow to change `:D`'s ID with the `:d_id` option.
13
+ * Introduce `Deprecate.warn` to have consistent deprecation warnings across all gems.
14
+ * Introduce `Activity.call` as a top-level entry point and abstraction for `TaskWrap.invoke`.
15
+
16
+ ## TaskWrap
17
+
18
+ * Remove the `:wrap_static` keyword argument for `TaskWrap.invoke` and replace it with `:container_activity`.
19
+ * Make `TaskWrap.initial_wrap_static` return `INITIAL_TASK_WRAP` instead of recompiling it for every `#invoke`.
20
+ * Introduce `TaskWrap.container_activity_for` to build "host activities" that are used to provide a wrap_static to
21
+ the actually run activity. This is also used in the `Each()` macro and other places.
22
+ * Allow `append: nil` for friendly interface.
23
+
24
+ ```ruby
25
+ TaskWrap.Extension([method(:add_1), id: "user.add_1", append: nil])`.
26
+ ```
27
+ This appends the step to the end of the pipeline.
28
+
29
+ ## Introspect
30
+
31
+ * Add `Introspect.find_path` to retrieve a `Graph::Node` and its hosting activity from a deeply nested graph.
32
+ Note that this method is still considered private.
33
+ * Add `Introspect::TaskMap` as a slim interface for introspecting `Activity` instances. Note that `Graph` might get
34
+ moved to `developer` as it is very specific to rendering circuits.
35
+
36
+ ## TaskAdapter
37
+
38
+ * Rename `Activity::TaskBuilder.Binary()` to `Activity::Circuit::TaskAdapter.for_step()`. It returns a `TaskAdapter`
39
+ instance, which is a bit more descriptive than a `Task` instance.
40
+ * Add `Circuit.Step(callable_with_step_interface)` which accepts a step-interface callable and, when called, returns
41
+ its result and the `ctx`. This is great for deciders in macros where you don't want the step's result on the `ctx`.
42
+ * Add `TaskWrap::Pipeline::TaskBuilder`.
43
+
1
44
  # 0.14.0
2
45
 
3
46
  * Remove `Pipeline.insert_before` and friends. Pipeline is now altered using ADDS mechanics, just
@@ -44,8 +44,9 @@ module Trailblazer
44
44
  end
45
45
 
46
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]
47
+ def self.build_adds(task, id:, prepend: "task_wrap.call_task", append: false)
48
+ insert, insert_id =
49
+ append === false ? [:Prepend, prepend] : [:Append, append]
49
50
 
50
51
  {
51
52
  insert: [Activity::Adds::Insert.method(insert), insert_id],
@@ -0,0 +1,113 @@
1
+ module Trailblazer
2
+ class Activity
3
+ # Circuit::TaskAdapter: Uses Circuit::Step to translate incoming, and returns a circuit-interface
4
+ # compatible return set.
5
+ class Circuit
6
+ # Create a `Circuit::Step` instance. Mostly this is used inside a `TaskAdapter`.
7
+ #
8
+ # @param [callable_with_step_interface] Any kind of callable object or `:instance_method` that receives
9
+ # a step interface.
10
+ # @param [:option] If true, the user's callable argument is wrapped in `Trailblazer::Option`.
11
+ # @return [Circuit::Step, Circuit::Step::Option] Returns a callable circuit-step.
12
+ # @see https://trailblazer.to/2.1/docs/activity#activity-internals-step-interface
13
+ def self.Step(callable_with_step_interface, option: false)
14
+ if option
15
+ return Step::Option.new(Trailblazer::Option(callable_with_step_interface), callable_with_step_interface)
16
+ end
17
+
18
+ Step.new(callable_with_step_interface, callable_with_step_interface) # DISCUSS: maybe we can ditch this, only if performance is cool here, but where would we use that?
19
+ end
20
+
21
+ # {Step#call} translates the incoming circuit-interface to the step-interface,
22
+ # and returns the return value of the user's callable. By design, it is *not* circuit-interface compatible.
23
+ class Step
24
+ def initialize(step, user_proc, **)
25
+ @step = step
26
+ @user_proc = user_proc
27
+ end
28
+
29
+ # Translate the circuit interface to the step's step-interface. However,
30
+ # this only translates the calling interface, not the returning.
31
+ def call((ctx, flow_options), **circuit_options)
32
+ result = @step.(ctx, **ctx.to_hash)
33
+ # in an immutable environment we should return the ctx from the step.
34
+ return result, ctx
35
+ end
36
+
37
+ # In {Step::Option}, {@step} is expected to be wrapped in a {Trailblazer::Option}.
38
+ # To remember: when calling an Option instance, you need to pass {:keyword_arguments} explicitely,
39
+ # because of beautiful Ruby 2.5 and 2.6.
40
+ #
41
+ # This is often needed for "decider" chunks where the user can run either a method or a callable
42
+ # but you only want back the return value, not a Binary circuit-interface return set.
43
+ class Option < Step
44
+ def call((ctx, _flow_options), **circuit_options)
45
+ result = @step.(ctx, keyword_arguments: ctx.to_hash, **circuit_options) # circuit_options contains :exec_context.
46
+ # in an immutable environment we should return the ctx from the step.
47
+ return result, ctx
48
+ end
49
+ end
50
+ end
51
+
52
+ class TaskAdapter
53
+ # Returns a `TaskAdapter` instance always exposes the complete circuit-interface,
54
+ # and can be used directly in a {Circuit}.
55
+ #
56
+ # @note This used to be called `TaskBuilder::Task`.
57
+ # @param [step] Any kind of callable object or `:instance_method` that receives
58
+ # a step interface.
59
+ # @param [:option] If true, the user's callable argument is wrapped in `Trailblazer::Option`.
60
+ # @return [TaskAdapter] a circuit-interface compatible object to use in a `Circuit`.
61
+ def self.for_step(step, binary: true, **options_for_step)
62
+ circuit_step = Circuit.Step(step, **options_for_step)
63
+
64
+ new(circuit_step)
65
+ end
66
+
67
+ # @param [circuit_step] Exposes a Circuit::Step.call([ctx, flow_options], **circuit_options) interface
68
+ def initialize(circuit_step, **)
69
+ @circuit_step = circuit_step
70
+ end
71
+
72
+ def call((ctx, flow_options), **circuit_options)
73
+ result, ctx = @circuit_step.([ctx, flow_options], **circuit_options)
74
+
75
+ # Return an appropriate signal which direction to go next.
76
+ signal = TaskAdapter.binary_signal_for(result, Activity::Right, Activity::Left)
77
+
78
+ return signal, [ctx, flow_options]
79
+ end
80
+
81
+ # Translates the return value of the user step into a valid signal.
82
+ # Note that it passes through subclasses of {Activity::Signal}.
83
+ def self.binary_signal_for(result, on_true, on_false)
84
+ if result.is_a?(Class) && result < Activity::Signal
85
+ result
86
+ else
87
+ result ? on_true : on_false
88
+ end
89
+ end
90
+
91
+ def inspect # TODO: make me private!
92
+ user_step = @circuit_step.instance_variable_get(:@user_proc) # DISCUSS: to we want Step#to_h?
93
+
94
+ %{#<Trailblazer::Activity::TaskBuilder::Task user_proc=#{Trailblazer::Activity::Introspect.render_task(user_step)}>}
95
+ end
96
+ alias_method :to_s, :inspect
97
+ end
98
+ end # Circuit
99
+
100
+ # TODO: remove when we drop compatibility.
101
+ module TaskBuilder
102
+ # @deprecated Use {Trailblazer::Activity::Circuit::TaskAdapter.for_step()} instead.
103
+ def self.Binary(user_proc)
104
+ Activity::Circuit::TaskAdapter.for_step(user_proc, option: true)
105
+ end
106
+
107
+ class << self
108
+ extend Gem::Deprecate
109
+ deprecate :Binary, "Trailblazer::Activity::Circuit::TaskAdapter.for_step()", 2023, 12
110
+ end
111
+ end
112
+ end # Activity
113
+ end
@@ -24,7 +24,7 @@ module Trailblazer
24
24
 
25
25
  # @param args [Array] all arguments to be passed to the task's `call`
26
26
  # @param task [callable] task to call
27
- Run = ->(task, args, **circuit_options) { task.(args, **circuit_options) }
27
+ Runner = ->(task, args, **circuit_options) { task.(args, **circuit_options) }
28
28
 
29
29
  # Runs the circuit until we hit a stop event.
30
30
  #
@@ -37,8 +37,8 @@ module Trailblazer
37
37
  # @return [last_signal, options, flow_options, *args]
38
38
  #
39
39
  # NOTE: returned circuit_options are discarded when calling the runner.
40
- def call(args, start_task: @start_task, runner: Run, **circuit_options)
41
- circuit_options = circuit_options.merge( runner: runner ).freeze # TODO: set the :runner option via arguments_for_call to save the merge?
40
+ def call(args, start_task: @start_task, runner: Runner, **circuit_options)
41
+ circuit_options = circuit_options.merge(runner: runner) # TODO: set the :runner option via arguments_for_call to save the merge?
42
42
  task = start_task
43
43
 
44
44
  loop do
@@ -49,7 +49,7 @@ module Trailblazer
49
49
  )
50
50
 
51
51
  # Stop execution of the circuit when we hit a stop event (< End). This could be an task's End or Suspend.
52
- return [ last_signal, args ] if @stop_events.include?(task) # DISCUSS: return circuit_options here?
52
+ return [last_signal, args] if @stop_events.include?(task)
53
53
 
54
54
  if (next_task = next_for(task, last_signal))
55
55
  task = next_task
@@ -0,0 +1,21 @@
1
+ module Trailblazer
2
+ class Activity
3
+ module Deprecate
4
+ module_function
5
+
6
+ def warn(caller_location, message)
7
+ location = caller_location ? location_for(caller_location) : nil
8
+ warning = [location, message].compact.join(" ")
9
+
10
+ Kernel.warn %{[Trailblazer] #{warning}\n}
11
+ end
12
+
13
+ def location_for(caller_location)
14
+ caller_location = caller_location
15
+ line_no = caller_location.lineno
16
+
17
+ %{#{caller_location.absolute_path}:#{line_no}}
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,15 +1,54 @@
1
1
  module Trailblazer
2
2
  class Activity
3
3
  # The Introspect API provides inflections for `Activity` instances.
4
- # It abstracts internals about circuits and provides a convenient API to third-parties such as
5
- # tracing, rendering an activity, or finding particular tasks.
4
+ #
5
+ # It abstracts internals about circuits and provides a convenient API to third-parties
6
+ # such as tracing, rendering an activity, or finding particular tasks.
6
7
  module Introspect
8
+ # A TaskMap is a way to introspect an {Activity}. It allows finding a {TaskAttributes}
9
+ # instance by its ID given at compile-time, or its task.
10
+ #
11
+ # It is much simpler and faster than the Graph interface that might get (re)moved.
12
+ def self.TaskMap(activity)
13
+ schema = activity.to_h
14
+ nodes = schema[:nodes]
15
+
16
+ task_map_tuples =
17
+ nodes.collect do |node_attributes|
18
+ [
19
+ task = node_attributes[:task],
20
+ TaskMap::TaskAttributes(id: node_attributes[:id], task: task)
21
+ ]
22
+ end
23
+
24
+ TaskMap[task_map_tuples].freeze
25
+ end
26
+
27
+ class TaskMap < Hash
28
+ TaskAttributes = Struct.new(:id, :task) # TODO: extend at some point.
29
+
30
+ def self.TaskAttributes(id:, task:)
31
+ TaskMap::TaskAttributes.new(id, task).freeze
32
+ end
33
+
34
+ def find_by_id(id)
35
+ tuple = find { |task, attrs| attrs[:id] == id } or return
36
+ tuple[1]
37
+ end
38
+ end
39
+
7
40
  # TODO: order of step/fail/pass in Node would be cool to have
8
41
 
42
+ # TODO: Remove Graph. This is only useful to render the full
43
+ # circuit, which is a very specific task that could sit in `developer`,
44
+ # instead.
45
+ # Some thoughts here:
46
+ # * where do we need Schema.outputs? and where task.outputs?
47
+ #
48
+ #
9
49
  # @private This API is still under construction.
10
50
  class Graph
11
51
  def initialize(activity)
12
- @activity = activity
13
52
  @schema = activity.to_h or raise
14
53
  @circuit = @schema[:circuit]
15
54
  @map = @circuit.to_h[:map]
@@ -22,6 +61,7 @@ module Trailblazer
22
61
  find_with_block(&block)
23
62
  end
24
63
 
64
+ # TODO: convert to {#to_a}.
25
65
  def collect(strategy: :circuit)
26
66
  @map.keys.each_with_index.collect { |task, i| yield find_with_block { |node| node.task == task }, i }
27
67
  end
@@ -43,8 +83,15 @@ module Trailblazer
43
83
  node_for(existing)
44
84
  end
45
85
 
86
+ # Build a {Graph::Node} with outputs etc.
46
87
  def node_for(node_attributes)
47
- Node(node_attributes.task, node_attributes.id, node_attributes.outputs, outgoings_for(node_attributes), node_attributes.data)
88
+ Node(
89
+ node_attributes.task,
90
+ node_attributes.id,
91
+ node_attributes.outputs, # [#<struct Trailblazer::Activity::Output signal=Trailblazer::Activity::Right, semantic=:success>]
92
+ outgoings_for(node_attributes),
93
+ node_attributes.data,
94
+ )
48
95
  end
49
96
 
50
97
  def Node(*args)
@@ -69,6 +116,26 @@ module Trailblazer
69
116
  Graph.new(*args)
70
117
  end
71
118
 
119
+ # @private
120
+ def self.find_path(activity, segments)
121
+ raise ArgumentError.new(%{[Trailblazer] Please pass #{activity}.to_h[:activity] into #find_path.}) unless activity.kind_of?(Trailblazer::Activity)
122
+
123
+ task_attributes = TaskMap::TaskAttributes(id: nil, task: activity)
124
+ last_graph, last_activity = nil, TaskWrap.container_activity_for(activity) # needed for empty/root path
125
+
126
+ segments.each do |segment|
127
+ task_map = Introspect.TaskMap(activity)
128
+ task, task_attributes = (task_map.find { |task, attributes| attributes[:id] == segment } or return) # DISCUSS: should we abstract these internals of {TaskMap} in {find_by_id}?
129
+
130
+ last_activity = activity
131
+ last_graph = task_map
132
+
133
+ activity = task
134
+ end
135
+
136
+ return task_attributes, last_activity, last_graph
137
+ end
138
+
72
139
  def self.render_task(proc)
73
140
  if proc.is_a?(Method)
74
141
 
@@ -4,7 +4,9 @@ class Trailblazer::Activity
4
4
  # Implementation structures
5
5
  Task = Struct.new(:circuit_task, :outputs, :extensions)
6
6
 
7
- def self.Task(task, outputs, extensions=[]); Task.new(task, outputs, extensions) end
7
+ def self.Task(task, outputs, extensions=[])
8
+ Task.new(task, outputs, extensions)
9
+ end
8
10
  end
9
11
  end
10
12
  end
@@ -22,6 +22,7 @@ class Trailblazer::Activity
22
22
  nodes = node_attributes(intermediate, implementation)
23
23
  outputs = outputs(intermediate.stop_task_ids, nodes)
24
24
  config = config(implementation, config: config)
25
+
25
26
  Schema.new(circuit, outputs, nodes, config)
26
27
  end
27
28
 
@@ -50,7 +51,7 @@ class Trailblazer::Activity
50
51
  # Compute the connections for {circuit_task}.
51
52
  def self.connections_for(outs, task_outputs, implementation)
52
53
  Hash[
53
- outs.collect { |required_out|
54
+ outs.collect { |required_out| # Intermediate::Out, it's abstract.
54
55
  [
55
56
  for_semantic(task_outputs, required_out.semantic).signal,
56
57
  implementation.fetch(required_out.target).circuit_task
@@ -60,11 +61,12 @@ class Trailblazer::Activity
60
61
  end
61
62
 
62
63
  def self.node_attributes(intermediate, implementation)
63
- intermediate.wiring.collect do |task_ref, outputs| # id, Task{circuit_task, outputs}
64
- task = implementation.fetch(task_ref.id)
65
- data = task_ref[:data] # TODO: allow adding data from implementation.
64
+ intermediate.wiring.collect do |task_ref, outputs|
65
+ id = task_ref.id
66
+ implementation_task = implementation.fetch(id)
67
+ data = task_ref[:data] # TODO: allow adding data from implementation.
66
68
 
67
- NodeAttributes.new(task_ref.id, task.outputs, task.circuit_task, data)
69
+ NodeAttributes.new(id, implementation_task.outputs, implementation_task.circuit_task, data)
68
70
  end
69
71
  end
70
72
 
@@ -1,9 +1,11 @@
1
1
  module Trailblazer
2
2
  class Activity
3
- NodeAttributes = Struct.new(:id, :outputs, :task, :data)
3
+ # In NodeAttributes we store data from Intermediate and Implementing compile-time.
4
+ # This would be lost otherwise.
5
+ NodeAttributes = Struct.new(:id, :outputs, :task, :data) # TODO: rename to Schema::Task::Attributes.
4
6
 
5
- # Schema is primitive data structure + an invoker (usually coming from Activity etc)
6
7
  class Schema < Struct.new(:circuit, :outputs, :nodes, :config)
8
+ # {:nodes} is passed directly from {compile_activity}. We need to store this data here.
7
9
 
8
10
  # @!method to_h()
9
11
  # Returns a hash containing the schema's components.
@@ -14,11 +14,14 @@ module Trailblazer
14
14
  # An {Extension} can be used for {:wrap_runtime}. It expects a collection of
15
15
  # "friendly interface" arrays.
16
16
  #
17
- # TaskWrap.Extension([ [task, id: "my_logger", append: "task_wrap.call_task"] ])
17
+ # TaskWrap.Extension([task, id: "my_logger", append: "task_wrap.call_task"], [...])
18
18
  #
19
19
  # If you want a {wrap_static} extension, wrap it using `Extension.WrapStatic.new`.
20
20
  def self.Extension(*inserts, merge: nil)
21
21
  if merge
22
+ Deprecate.warn caller_locations[0], "The :merge option for TaskWrap.Extension is deprecated and will be removed in 0.16.
23
+ Please refer to https://trailblazer.to/2.1/docs/activity.html#activity-taskwrap-static and have a great day."
24
+
22
25
  return Extension::WrapStatic.new(extension: Extension.new(*merge))
23
26
  # TODO: remove me once we drop the pre-friendly interface.
24
27
  end
@@ -56,8 +59,8 @@ module Trailblazer
56
59
  def deprecated_extension_for(extension_rows)
57
60
  return extension_rows unless extension_rows.find { |ext| ext.is_a?(Array) }
58
61
 
59
- warn "[Trailblazer] You are using the old API for taskWrap extensions.
60
- Please update to the new TaskWrap.Extension() API: # FIXME !!!!!"
62
+ Deprecate.warn caller_locations[3], "You are using the old API for taskWrap extensions.
63
+ Please update to the new TaskWrap.Extension() API."
61
64
 
62
65
  extension_rows.collect do |ary|
63
66
  {
@@ -67,6 +70,13 @@ Please update to the new TaskWrap.Extension() API: # FIXME !!!!!"
67
70
  end
68
71
  end
69
72
 
73
+ # Create extensions from the friendly interface that can alter the wrap_static
74
+ # of a step in an activity. The returned extensionn can be passed directly via {:extensions}
75
+ # to the compiler, or when using the `#step` DSL.
76
+ def self.WrapStatic(*inserts)
77
+ WrapStatic.new(extension: TaskWrap.Extension(*inserts))
78
+ end
79
+
70
80
  # Extension are used at compile-time with {wrap_static}, mostly with the {dsl} gem.
71
81
  # {WrapStatic} extensions are called for setup through {Intermediate.config} at compile-time.
72
82
  # Each extension alters the activity's wrap_static taskWrap.
@@ -59,6 +59,27 @@ Please use the new TaskWrap.Extension() API: #FIXME!!!"
59
59
  TaskWrap::Extension.new(*inserts)
60
60
  end
61
61
  end # Merge
62
+
63
+ # Implements adapter for a callable in a Pipeline.
64
+ class TaskAdapter < Circuit::TaskAdapter
65
+ # Returns a {Pipeline::TaskAdapter} instance that can be used directly in a Pipeline.
66
+ # When `call`ed, it returns a Pipeline-interface return set.
67
+ #
68
+ # @see Circuit::TaskAdapter.for_step
69
+ def self.for_step(callable, **)
70
+ circuit_step = Circuit.Step(callable, option: false) # Since we don't have {:exec_context} in Pipeline, Option doesn't make much sense.
71
+
72
+ TaskAdapter.new(circuit_step) # return a {Pipeline::TaskAdapter}
73
+ end
74
+
75
+ def call(wrap_ctx, args)
76
+ _result, _new_wrap_ctx = @circuit_step.([wrap_ctx, args]) # For funny reasons, the Circuit::Step's call interface is compatible to the Pipeline's.
77
+
78
+ # DISCUSS: we're mutating wrap_ctx, that's the whole point of this abstraction (plus kwargs).
79
+
80
+ return wrap_ctx, args
81
+ end
82
+ end # TaskAdapter
62
83
  end
63
84
  end
64
85
  end
@@ -14,14 +14,14 @@ module Trailblazer
14
14
 
15
15
  # Compute runtime arguments necessary to execute a taskWrap per task of the activity.
16
16
  # This method is the top-level entry, called only once for the entire activity graph.
17
- # [:wrap_static] The taskWrap used for the topmost activity/operation.
18
- def invoke(activity, args, wrap_runtime: {}, wrap_static: initial_wrap_static, **circuit_options) # FIXME: why do we need this method?
17
+ # [:container_activity] the top-most "activity". This only has to look like an Activity
18
+ # and exposes a #[] interface so [:wrap_static] can be read and it's compatible to {Trace}.
19
+ # It is the virtual activity that "hosts" the actual {activity}.
20
+ def invoke(activity, args, wrap_runtime: {}, container_activity: container_activity_for(activity), **circuit_options)
19
21
  circuit_options = circuit_options.merge(
20
22
  runner: TaskWrap::Runner,
21
23
  wrap_runtime: wrap_runtime,
22
- # This {:activity} structure is currently (?) only needed in {TaskWrap.wrap_static_for}, where we
23
- # access {activity[:wrap_static]} to compile the effective taskWrap.
24
- activity: {wrap_static: {activity => wrap_static}, nodes: {}}, # for Runner. Ideally we'd have a list of all static_wraps here (even nested).
24
+ activity: container_activity, # for Runner. Ideally we'd have a list of all static_wraps here (even nested).
25
25
  )
26
26
 
27
27
  # signal, (ctx, flow), circuit_options =
@@ -31,14 +31,27 @@ module Trailblazer
31
31
  # {:extension} API
32
32
  # Extend the static taskWrap from a macro or DSL call.
33
33
  # Gets executed in {Intermediate.call} which also provides {config}.
34
+ def initial_wrap_static
35
+ INITIAL_WRAP_STATIC
36
+ end
34
37
 
35
- def initial_wrap_static(*)
36
- Pipeline.new([Pipeline.Row("task_wrap.call_task", TaskWrap.method(:call_task))])
38
+ # This is the top-most "activity" that hosts the actual activity being run.
39
+ # The data structure is used in {TaskWrap.wrap_static_for}, where we
40
+ # access {activity[:wrap_static]} to compile the effective taskWrap.
41
+ #
42
+ # It's also needed in Trace/Introspect and mimicks the host containing the actual activity.
43
+ #
44
+ # DISCUSS: we could cache that on Strategy/Operation level.
45
+ # merging the **config hash is 1/4 slower than before.
46
+ def container_activity_for(activity, wrap_static: initial_wrap_static, **config)
47
+ {
48
+ wrap_static: {activity => wrap_static},
49
+ **config
50
+ }
37
51
  end
52
+
53
+ INITIAL_WRAP_STATIC = Pipeline.new([Pipeline.Row("task_wrap.call_task", TaskWrap.method(:call_task))].freeze)
38
54
  end # TaskWrap
39
55
  end
40
56
  end
41
- require "trailblazer/activity/task_wrap/pipeline"
42
- require "trailblazer/activity/task_wrap/call_task"
43
- require "trailblazer/activity/task_wrap/runner"
44
- require "trailblazer/activity/task_wrap/extension"
57
+
@@ -65,7 +65,7 @@ module Trailblazer
65
65
  end
66
66
 
67
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?
68
+ def assert_invoke(activity, terminus: :success, seq: "[]", circuit_options: {}, expected_ctx_variables: {}, **ctx_variables)
69
69
  signal, (ctx, _flow_options) = TaskWrap.invoke(
70
70
  activity,
71
71
  [
@@ -94,7 +94,7 @@ module Trailblazer
94
94
  Success = Activity::End(:success)
95
95
  end
96
96
 
97
- # TODO: Remove this once all it's references are removed
97
+ # TODO: Remove this once all its references are removed
98
98
  def implementing
99
99
  Implementing
100
100
  end
@@ -127,14 +127,13 @@ module Trailblazer
127
127
  @_flat_activity = Activity.new(schema)
128
128
  end
129
129
 
130
- def nested_activity
131
- return @_nested_activity if defined?(@_nested_activity)
132
-
130
+ # FIXME: why is this in Testing? We don't need this anywhere but in this gem.
131
+ def nested_activity(flat_activity: bc, d_id: :D)
133
132
  intermediate = Inter.new(
134
133
  {
135
134
  Inter::TaskRef("Start.default") => [Inter::Out(:success, :B)],
136
- Inter::TaskRef(:B, more: true) => [Inter::Out(:success, :D)],
137
- Inter::TaskRef(:D) => [Inter::Out(:success, :E)],
135
+ Inter::TaskRef(:B, more: true) => [Inter::Out(:success, d_id)],
136
+ Inter::TaskRef(d_id) => [Inter::Out(:success, :E)],
138
137
  Inter::TaskRef(:E) => [Inter::Out(:success, "End.success")],
139
138
  Inter::TaskRef("End.success", stop_event: true) => [Inter::Out(:success, nil)]
140
139
  },
@@ -145,14 +144,14 @@ module Trailblazer
145
144
  implementation = {
146
145
  "Start.default" => Schema::Implementation::Task(st = Implementing::Start, [Activity::Output(Activity::Right, :success)], []),
147
146
  :B => Schema::Implementation::Task(b = Implementing.method(:b), [Activity::Output(Activity::Right, :success)], []),
148
- :D => Schema::Implementation::Task(c = bc, [Activity::Output(Implementing::Success, :success)], []),
147
+ d_id => Schema::Implementation::Task(flat_activity, [Activity::Output(Implementing::Success, :success)], []),
149
148
  :E => Schema::Implementation::Task(e = Implementing.method(:f), [Activity::Output(Activity::Right, :success)], []),
150
149
  "End.success" => Schema::Implementation::Task(_es = Implementing::Success, [Activity::Output(Implementing::Success, :success)], []), # DISCUSS: End has one Output, signal is itself?
151
150
  }
152
151
 
153
152
  schema = Inter.(intermediate, implementation)
154
153
 
155
- @_nested_activity = Activity.new(schema)
154
+ Activity.new(schema)
156
155
  end
157
156
 
158
157
  alias_method :bc, :flat_activity
@@ -1,7 +1,7 @@
1
1
  module Trailblazer
2
2
  module Version
3
3
  module Activity
4
- VERSION = '0.14.0'.freeze
4
+ VERSION = '0.15.1'
5
5
  end
6
6
  end
7
7
  end
@@ -10,7 +10,7 @@ module Trailblazer
10
10
  def call(args, **circuit_options)
11
11
  @schema[:circuit].(
12
12
  args,
13
- **(circuit_options.merge(activity: self))
13
+ **circuit_options.merge(activity: self)
14
14
  )
15
15
  end
16
16
 
@@ -27,6 +27,12 @@ module Trailblazer
27
27
  def inspect
28
28
  %{#<Trailblazer::Activity:0x#{object_id}>}
29
29
  end
30
+
31
+ # Canonical entry-point to invoke an {Activity} or Strategy such as {Activity::Railway}
32
+ # with its taskWrap.
33
+ def self.call(activity, ctx)
34
+ TaskWrap.invoke(activity, [ctx, {}])
35
+ end
30
36
  end # Activity
31
37
  end
32
38
 
@@ -35,12 +41,16 @@ require "trailblazer/activity/schema"
35
41
  require "trailblazer/activity/schema/implementation"
36
42
  require "trailblazer/activity/schema/intermediate"
37
43
  require "trailblazer/activity/circuit"
44
+ require "trailblazer/activity/circuit/task_adapter"
38
45
  require "trailblazer/activity/config"
39
46
  require "trailblazer/activity/introspect"
47
+ require "trailblazer/activity/task_wrap/pipeline"
48
+ require "trailblazer/activity/task_wrap/call_task"
40
49
  require "trailblazer/activity/task_wrap"
50
+ require "trailblazer/activity/task_wrap/runner"
51
+ require "trailblazer/activity/task_wrap/extension"
41
52
  require "trailblazer/activity/adds"
42
- require "trailblazer/activity/task_builder"
43
-
53
+ require "trailblazer/activity/deprecate"
44
54
  require "trailblazer/option"
45
55
  require "trailblazer/context"
46
56
 
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.14.0
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-22 00:00:00.000000000 Z
11
+ date: 2023-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trailblazer-context
@@ -128,13 +128,14 @@ files:
128
128
  - lib/trailblazer/activity.rb
129
129
  - lib/trailblazer/activity/adds.rb
130
130
  - lib/trailblazer/activity/circuit.rb
131
+ - lib/trailblazer/activity/circuit/task_adapter.rb
131
132
  - lib/trailblazer/activity/config.rb
133
+ - lib/trailblazer/activity/deprecate.rb
132
134
  - lib/trailblazer/activity/introspect.rb
133
135
  - lib/trailblazer/activity/schema.rb
134
136
  - lib/trailblazer/activity/schema/implementation.rb
135
137
  - lib/trailblazer/activity/schema/intermediate.rb
136
138
  - lib/trailblazer/activity/structures.rb
137
- - lib/trailblazer/activity/task_builder.rb
138
139
  - lib/trailblazer/activity/task_wrap.rb
139
140
  - lib/trailblazer/activity/task_wrap/call_task.rb
140
141
  - lib/trailblazer/activity/task_wrap/extension.rb
@@ -1,53 +0,0 @@
1
- module Trailblazer
2
- module Activity::TaskBuilder
3
- # every step is wrapped by this proc/decider. this is executed in the circuit as the actual task.
4
- # Step calls step.(options, **options, flow_options)
5
- # Output signal binary: true=>Right, false=>Left.
6
- # Passes through all subclasses of Direction.~~~~~~~~~~~~~~~~~
7
- def self.Binary(user_proc)
8
- Task.new(Trailblazer::Option(user_proc), user_proc)
9
- end
10
-
11
- # Translates the return value of the user step into a valid signal.
12
- # Note that it passes through subclasses of {Signal}.
13
- def self.binary_signal_for(result, on_true, on_false)
14
- if result.is_a?(Class) && result < Activity::Signal
15
- result
16
- else
17
- result ? on_true : on_false
18
- end
19
- end
20
-
21
- # Wraps a {task} (that usually expects the task interface) into a circuit interface
22
- # that can be used directly in a {Circuit}.
23
- # We expect {task} to be exposing an {Option()} interface when calling it.
24
- class Task
25
- def initialize(task, user_proc)
26
- @task = task
27
- @user_proc = user_proc
28
- freeze
29
- end
30
-
31
- def call((ctx, flow_options), **circuit_options)
32
- # Execute the user step with TRB's kw args.
33
- # {@task} is/implements {Trailblazer::Option} interface.
34
- result = call_option(@task, [ctx, flow_options], **circuit_options)
35
-
36
- # Return an appropriate signal which direction to go next.
37
- signal = Activity::TaskBuilder.binary_signal_for(result, Activity::Right, Activity::Left)
38
-
39
- return signal, [ctx, flow_options]
40
- end
41
-
42
- # Invoke the original {user_proc} that is wrapped in an {Option()}.
43
- private def call_option(task_with_option_interface, (ctx, _flow_options), **circuit_options)
44
- task_with_option_interface.(ctx, keyword_arguments: ctx.to_hash, **circuit_options) # circuit_options contains :exec_context.
45
- end
46
-
47
- def inspect # TODO: make me private!
48
- %{#<Trailblazer::Activity::TaskBuilder::Task user_proc=#{Trailblazer::Activity::Introspect.render_task(@user_proc)}>}
49
- end
50
- alias_method :to_s, :inspect
51
- end
52
- end
53
- end