trailblazer-activity 0.14.0 → 0.15.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: 2c710a0a76320ef3d041b5abeff53c24b34b1923caff08e3c92656919af62431
4
- data.tar.gz: b235c975eff4e9174110270e73c75e56ec3d10fd3038616039a5de27322043d3
3
+ metadata.gz: 6b06f440b4b9b94230fad09bc1b2024ee3297d3b454cae381f01053af65dfe8d
4
+ data.tar.gz: 1cd2bae0d2bd7a7f18cacee0d54af07fa4e8783b75b2852816c6882bb5f17b6a
5
5
  SHA512:
6
- metadata.gz: 38bff15d697d6362b36df62be35a05248510ac6c4326962a1e12c0c3fb041da30aa39c1c6ab8c591d77cbcb186534a4284bfeba33b6cc79e4ef684ef7837e9b9
7
- data.tar.gz: 586078a93ac567f69c079b017f054513e4069b8613e63badb19a0b61a8f72fbdfb22e3fb98fa8d97c233a4ee1b2818537d9e4df72d2fe0c7a2b7dcdb0b4a5bf4
6
+ metadata.gz: 4844b0b023f93fc12f218932a7d574d96e94684adaab4a186f85cb378cb7e7e92b889bec061af70bc6731d83a56a0fe5ff2651f5f250a0e4d091b454304193e5
7
+ data.tar.gz: 3a41f612cecfabdc2e5d0fef50411f5db1a5c6867a8c88ce784e569bfcc2b17b62bfda7a4104b8bbbb68cb157c311961622e7f64b9e5cc811ff09ee1db133296
data/CHANGES.md CHANGED
@@ -1,3 +1,39 @@
1
+ # 0.15.0
2
+
3
+ * Rename `Circuit::Run` to `Circuit::Runner` for consistency with `TaskWrap::Runner`.
4
+ * Add `:flat_activity` keyword argument to `Testing.nested_activity`, so you can inject any activity for `:D`.
5
+ Also, allow to change `:D`'s ID with the `:d_id` option.
6
+ * Introduce `Deprecate.warn` to have consistent deprecation warnings across all gems.
7
+ * Introduce `Activity.call` as a top-level entry point and abstraction for `TaskWrap.invoke`.
8
+
9
+ ## TaskWrap
10
+
11
+ * Remove the `:wrap_static` keyword argument for `TaskWrap.invoke` and replace it with `:container_activity`.
12
+ * Make `TaskWrap.initial_wrap_static` return `INITIAL_TASK_WRAP` instead of recompiling it for every `#invoke`.
13
+ * Introduce `TaskWrap.container_activity_for` to build "host activities" that are used to provide a wrap_static to
14
+ the actually run activity. This is also used in the `Each()` macro and other places.
15
+ * Allow `append: nil` for friendly interface.
16
+
17
+ ```ruby
18
+ TaskWrap.Extension([method(:add_1), id: "user.add_1", append: nil])`.
19
+ ```
20
+ This appends the step to the end of the pipeline.
21
+
22
+ ## Introspect
23
+
24
+ * Add `Introspect.find_path` to retrieve a `Graph::Node` and its hosting activity from a deeply nested graph.
25
+ Note that this method is still considered private.
26
+ * Add `Introspect::TaskMap` as a slim interface for introspecting `Activity` instances. Note that `Graph` might get
27
+ moved to `developer` as it is very specific to rendering circuits.
28
+
29
+ ## TaskAdapter
30
+
31
+ * Rename `Activity::TaskBuilder.Binary()` to `Activity::Circuit::TaskAdapter.for_step()`. It returns a `TaskAdapter`
32
+ instance, which is a bit more descriptive than a `Task` instance.
33
+ * Add `Circuit.Step(callable_with_step_interface)` which accepts a step-interface callable and, when called, returns
34
+ its result and the `ctx`. This is great for deciders in macros where you don't want the step's result on the `ctx`.
35
+ * Add `TaskWrap::Pipeline::TaskBuilder`.
36
+
1
37
  # 0.14.0
2
38
 
3
39
  * 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,7 +14,7 @@ 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)
@@ -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.0'.freeze
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.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: 2022-08-22 00:00:00.000000000 Z
11
+ date: 2022-12-14 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