trailblazer-activity 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df4bfa29ae42857b380ae366a8256d60b536f072
4
- data.tar.gz: 562bf7c08d36f471eb5d609b4148067d0b95bd06
3
+ metadata.gz: cda2a49909b44be7195fed23b15bb82f6d4d4ac4
4
+ data.tar.gz: 3d17e44f5c91ca1a61797d1f3d5f8d515362c2ad
5
5
  SHA512:
6
- metadata.gz: 7776c71ee513ab9638b81becb2b27cdca56e505d0d50d42ba28e61729409da6ab7e96d2356ee0454be5ea34094851f8fe7130da6be17e8de02994fd327c5d132
7
- data.tar.gz: be1df204fd6f067d45533d1d881d6f9afbad3d4f7a29273506e23fb67ce8eb022b2ab6d5ebc8566a2179a00e7a610d0656eec2dd8354b78272fc20b3920241da
6
+ metadata.gz: 70ca9ad73d4993f6295ee2dfc40b527c46fc25fc7e3384ec6dd7f57f726db5c7599affda8cfec180430020cb8f6a4340892921008bd0a1b3e54f4fa362e3dcc8
7
+ data.tar.gz: 13400c97fecca6f83d0282205af04c7f60f7136b4f5f6a7175ed5035277a12e9bf9a22044102577fcca26187fa6badf5185246a3dba61c87e1eab5f06b98ac57
data/.travis.yml CHANGED
@@ -8,6 +8,6 @@ rvm:
8
8
  - 2.4.0
9
9
  matrix:
10
10
  include:
11
- - rvm: jruby-9.1.7.0
11
+ - rvm: jruby-9.1.13.0
12
12
  env: JRUBY_OPTS="--profile.api"
13
13
  before_install: gem install bundler
data/CHANGES.md CHANGED
@@ -1,3 +1,27 @@
1
+ # 0.2.0
2
+
3
+ * The `Activity#call` API is now
4
+
5
+ ```ruby
6
+ signal, options, _ignored_circuit_options = Activity.( options, **circuit_options )
7
+ ```
8
+
9
+ The third return value, which is typically the `circuit_options`, is _ignored_ and for all task calls in this `Activity`, an identical, unchangeable set of `circuit_options` is passed to. This dramatically reduces unintended behavior with the task_wrap, tracing, etc. and usually simplifies tasks.
10
+
11
+ The new API allows using bare `Activity` instances as tasks without any clumsy nesting work, making nesting very simple.
12
+
13
+ A typical task will look as follows.
14
+
15
+ ```ruby
16
+ ->( (options, flow_options), **circuit_args ) do
17
+ [ signal, [options, flow_options], *this_will_be_ignored ]
18
+ end
19
+ ```
20
+
21
+ A task can only emit a signal and "options" (whatever data structure that may be), and can *not* change the `circuit_options` which usually contain activity-wide "global" configuration.
22
+
23
+
24
+
1
25
  # 0.1.6
2
26
 
3
27
  * `Nested` now is `Subprocess` because it literally does nothing else but calling a _process_ (or activity).
data/Gemfile CHANGED
@@ -7,3 +7,4 @@ gem "minitest-line"
7
7
  gem "benchmark-ips"
8
8
 
9
9
  gem "trailblazer-test", git: "https://github.com/trailblazer/trailblazer-test"
10
+
data/NOTES ADDED
@@ -0,0 +1,36 @@
1
+ # NOT lowest level. if you need that, use your own proc.
2
+ # TODO: how do we track what goes into the callable?
3
+ # adjust what goes into it (e.g. without direction or with kw args)?
4
+ # pre contract -> step -> post contract (are these all just steps, in "mini nested pipe"?)
5
+ #
6
+ #
7
+ # aka "Atom".
8
+ def self.Task(instance: :context, method: :call, id:nil)
9
+
10
+
11
+ # * ingoing contract (could be implemented as a nested pipe with 3 steps. that would allow us
12
+ # to compile it to native ruby method calls later)
13
+ ->(direction, options, **flow_options) {
14
+ instance = flow_options[:context] if instance==:context # TODO; implement different :context (e.g. :my_context).
15
+
16
+
17
+
18
+ # * incoming args
19
+ # step_args = [args] # TODO: overridable.
20
+ step_args = [ options, **options ]
21
+
22
+ # ** call the actual thing
23
+ res = instance.send(method, *step_args) # what goes in? kws?
24
+
25
+ # * interpret result (e.g. true=>Right) (should we keep doing that in the tie? so the op has it easier with success, etc?)
26
+ # * outgoing contract
27
+ # * outgoing args
28
+
29
+ [ *res, flow_options ]
30
+
31
+
32
+ # * tracing: incoming, outgoing, direction, etc. - do we want that in tasks, too?
33
+
34
+
35
+ }
36
+ end
data/README.md CHANGED
@@ -1,163 +1,109 @@
1
- # Circuit
1
+ # Activity
2
2
 
3
- _The Circuit of Life._
4
-
5
- Circuit provides a simplified [flowchart](https://en.wikipedia.org/wiki/Flowchart) implementation with terminals (for example, start or end state), connectors and tasks (processes). It allows to define the flow (the actual *circuit*) and execute it.
6
-
7
- Circuit refrains from implementing deciders. The decisions are encoded in the output signals of tasks.
8
-
9
- `Circuit` and `workflow` use [BPMN](http://www.bpmn.org/) lingo and concepts for describing processes and flows. This document can be found in the [Trailblazer documentation](http://trailblazer.to/gems/workflow/circuit.html), too.
10
-
11
- > The `circuit` gem is the lowest level of abstraction and is used in `operation` and `workflow`, which both provide higher-level APIs for the Railway pattern and complex BPMN workflows.
3
+ An _activity_ is a collection of connected _tasks_ with one _start event_ and one (or many) _end_ events.
12
4
 
13
5
  ## Installation
14
6
 
15
- To use circuits, activities and nested tasks, you need one gem, only.
7
+ To use activities you need one gem, only.
16
8
 
17
9
  ```ruby
18
- gem "trailblazer-circuit"
10
+ gem "trailblazer-activity"
19
11
  ```
20
12
 
21
- The `trailblazer-circuit` gem is often just called the `circuit` gem. It ships with the `operation` gem and implements the internal Railway.
22
-
23
13
  ## Overview
24
14
 
25
- The following diagram illustrates a common use-case for `circuit`, the task of publishing a blog post.
26
-
27
- <img src="http://trailblazer.to/images/diagrams/blog-bpmn1.png">
28
-
29
- After writing and spell-checking, the author has the chance to publish the post or, in case of typos, go back, correct, and go through the same flow, again. Note that there's only a handful of defined transistions, or connections. An author, for example, is not allowed to jump from "correct" into "publish" without going through the check.
30
-
31
- The `circuit` gem allows you to define this *activity* and takes care of implementing the control flow, running the activity and making sure no invalid paths are taken.
15
+ > Since TRB 2.1, we use [BPMN](http://www.bpmn.org/) lingo and concepts for describing workflows and processes.
32
16
 
33
- Your job is solely to implement the tasks and deciders put into this activity - you don't have to take care of executing it in the right order, and so on.
17
+ An activity is a workflow that contains one or several tasks. It is the main concept to organize control flow in Trailblazer.
34
18
 
35
- ## Definition
36
-
37
- In order to define an activity, you can use the BPMN editor of your choice and run it through the Trailblazer circuit generator, use our online tool (if [you're a PRO member](http://pro.trailblazer.to)) or simply define it using plain Ruby.
38
-
39
- ```ruby
40
- activity = Activity.from_hash do |start, _end|
41
- {
42
- start => { Circuit::Right => Blog::Write },
43
- Blog::Write => { Circuit::Right => Blog::SpellCheck },
44
- Blog::SpellCheck => { Circuit::Right => Blog::Publish, Circuit::Left => Blog::Correct },
45
- Blog::Correct => { Circuit::Right => Blog::SpellCheck },
46
- Blog::Publish => { Circuit::Right => _end }
47
- }
48
- end
49
- ```
19
+ The following diagram illustrates an exemplary workflow where a user writes and publishes a blog post.
50
20
 
51
- The `Activity` function is a convenient tool to create an activity. Note that the yielded object allows to access *events* from the activity, such as the `Start` and `End` event that are created per default.
21
+ <img src="http://trb.to/images/diagrams/blog-bpmn1.png">
52
22
 
53
- This defines the control flow - the next step is to actually implement the tasks in this activity.
23
+ After writing and spell-checking, the author has the chance to publish the post or, in case of typos, go back, correct, and go through the same flow, again. Note that there's only a handful of defined transistions, or connections. An author, for example, is not allowed to jump from "correct" into "publish" without going through the check.
54
24
 
55
- ## Task
25
+ The `activity` gem allows you to define this *activity* and takes care of implementing the control flow, running the activity and making sure no invalid paths are taken.
56
26
 
57
- A *task* usually maps to a particular box in your diagram. Its API is very simple: a task needs to expose a `call` method, allowing it to be a lambda or any other callable object.
27
+ Your job is solely to implement the tasks and deciders put into this activity - Trailblazer makes sure it is executed it in the right order, and so on.
58
28
 
59
- ```ruby
60
- module Blog
61
- Write = ->(direction, options, *flow) do
62
- options[:content] = options[:content].strip
63
- [ Circuit::Right, options, *flow ]
64
- end
65
- end
66
- ```
29
+ To eventually run this activity, three things have to be done.
67
30
 
68
- It receives all arguments returned from the task run before. This means a task should return everything it receives.
31
+ 1. The activity needs be defined. Easiest is to use the [Activity.from_hash builder](#activity-fromhash).
32
+ 2. It's the programmer's job (that's you!) to implement the actual tasks (the "boxes"). Use [tasks for that](#task).
33
+ 3. After defining and implementing, you can run the activity with any data [by `call`ing it](#activity-call).
69
34
 
70
- To transport data across the flow, you can change the return value. In this example, we use one global hash `options` that is passed from task to task and used for writing.
35
+ ## Operation vs. Activity
71
36
 
72
- The first return value is crucial: it dictates what will be the next step when executing the flow.
37
+ An `Activity` allows to define and maintain a graph, that at runtime will be used as a "circuit". Or, in other words, it defines the boxes, circles, arrows and signals between them, and makes sure when running the activity, the circuit with your rules will be executed.
73
38
 
74
- For example, the `SpellCheck` task needs to decide which route to take.
39
+ Please note that an `Operation` simply provides a neat DSL for creating an `Activity` with a railway-oriented wiring (left and right track). An operation _always_ maintains an activity internally.
75
40
 
76
41
  ```ruby
77
- SpellCheck = ->(direction, options, *flow) do
78
- direction = SpellChecker.error_count(options[:content]) ? Circuit::Right : Circuit::Left
79
- [ direction, options, *flow ]
42
+ class Create < Trailblazer::Operation
43
+ step :exists?, pass_fast: true
44
+ step :policy
45
+ step :validate
46
+ fail :log_err
47
+ step :persist
48
+ fail :log_db_err
49
+ step :notify
80
50
  end
81
51
  ```
82
52
 
83
- It's as simple as returning the appropriate signal.
53
+ Check the operation above. The DSL to create the activity with its graph is very different to `Activity`, but the outcome is a simple activity instance.
84
54
 
85
- > You can use any object as a direction signal and return it, as long as it's defined in the circuit.
55
+ <img src="http://trb.to/images/graph/op-vs-activity.png">
86
56
 
87
- ## Call
57
+ When `call`ing an operation, several transformations on the arguments are applied, and those are passed to the `Activity#call` invocation. After the activity finished, its output is transformed into a `Result` object.
88
58
 
89
- After defining circuit and implementing the tasks, the circuit can be executed using its very own `call` method.
59
+ ## Activity
90
60
 
91
- ```ruby
92
- direction, options, flow = activity.(
93
- nil,
94
- { content: "Let's start writing " } # gets trimmed in Write.
95
- )
96
- ```
97
-
98
- The first argument is where to start the circuit. Usually, this will be the activity's `Start` event accessable via `activity[:Start]`.
61
+ To understand how an activity works and what it performs in your application logic, it's easiest to see how activities are defined, and used.
99
62
 
100
- All options are passed straight to the first task, which in turn has to make sure it returns an appropriate result set.
63
+ ## Activity: From_Hash
101
64
 
102
- The activity's return set is the last run task and all arguments from the last task.
103
-
104
- ```ruby
105
- direction #=> #<End: default {}>
106
- options #=> {:content=>"Let's start writing"}
107
- ```
108
-
109
- As opposed to higher abstractions such as `Operation`, it is completely up to the developer what interfaces they provide to tasks and their return values. What is a mutable hash here could be an explicit array of return values in another implementation style, and so on.
110
-
111
- ## Tracing
112
-
113
- For debugging or simply understanding the flows of circuits, you can use tracing.
65
+ Instead of using an operation, you can manually define activities by using the `Activity.from_hash` builder.
114
66
 
115
67
  ```ruby
116
68
  activity = Activity.from_hash do |start, _end|
117
- # Blog::Write=>"Blog::Write",Blog::SpellCheck=>"Blog::SpellCheck",Blog::Correct=>"Blog::Correct", Blog::Publish=>"Blog::Publish" }) { |evt|
118
69
  {
119
- start => { Circuit::Right => Blog::Write },
120
- Blog::Write => { Circuit::Right => Blog::SpellCheck },
121
- Blog::SpellCheck => { Circuit::Right => Blog::Publish, Circuit::Left => Blog::Correct },
122
- Blog::Correct => { Circuit::Right => Blog::SpellCheck },
123
- Blog::Publish => { Circuit::Right => _end }
70
+ start => { Trailblazer::Circuit::Right => Blog::Write },
71
+ Blog::Write => { Trailblazer::Circuit::Right => Blog::SpellCheck },
72
+ Blog::SpellCheck => { Trailblazer::Circuit::Right => Blog::Publish,
73
+ Trailblazer::Circuit::Left => Blog::Correct },
74
+ Blog::Correct => { Trailblazer::Circuit::Right => Blog::SpellCheck },
75
+ Blog::Publish => { Trailblazer::Circuit::Right => _end }
124
76
  }
125
77
  end
126
78
  ```
127
79
 
128
- The second argument to `Activity` takes debugging information, so you can set readable names for tasks.
129
80
 
130
- When invoking the activity, the `:runner` option will activate tracing and write debugging information about any executed task onto the `:stack` array.
81
+ The block yields a generic start and end event instance. You then connect every _task_ in that hash (hash keys) to another task or event via the emitted _signal_.
131
82
 
132
- ```ruby
133
- stack, _ = Circuit::Trace.( activity,
134
- nil,
135
- { content: "Let's start writing" }
136
- )
137
- ```
83
+ ## Activity: Call
138
84
 
139
- The `stack` can then be passed to a presenter.
85
+ To run the activity, you want to `call` it.
140
86
 
87
+ ```ruby
88
+ my_options = {}
89
+ last_signal, options, flow_options, _ = activity.( nil, my_options, {} )
141
90
  ```
142
- Circuit::Trace::Present.tree(stack)
143
- |--> #<Start: default {}>{:content=>"Let's start writing"}
144
- |--> Blog::Write{:content=>"Let's start writing"}
145
- |--> Blog::SpellCheck{:content=>"Let's start writing"}
146
- |--> Blog::Publish{:content=>"Let's start writing"}
147
- `--> #<End: default {}>{:content=>"Let's start writing"}
148
- ```
149
91
 
150
- Tracing is extremely efficient to find out what is going wrong and supersedes cryptic debuggers by many times. Note that tracing also works for deeply nested circuits.
92
+ 1. The `start` event is `call`ed and per default returns the generic _signal_`Trailblazer::Circuit::Right`.
93
+ 2. This emitted (or returned) signal is connected to the next task `Blog::Write`, which is now `call`ed.
94
+ 3. `Blog::Write` emits another `Right` signal that leads to `Blog::SpellCheck` being `call`ed.
95
+ 4. `Blog::SpellCheck` defines two outgoing signals and hence can decide what next task to call by emitting either `Right` if the spell check was ok, or `Left` if the post contains typos.
96
+ 5. ...and so on.
97
+
98
+ <img src="http://trb.to/images/graph/blogpost-activity.png">
151
99
 
152
- > 🌅 In future versions of Trailblazer, our own debugger will take advantage of the explicit, traceable nature of circuits and also integrate with Ruby's exception handling.
153
- > Also, more options will make debugging of complex, nested workflows easier.
100
+ Visualizing an activity as a graph makes it very straight-forward to understanding the mechanics of the flow.
154
101
 
155
- ## Event
156
102
 
157
- * how to add more ends, etc.
103
+ Note how signals translate to edges (or connections) in the graph, and tasks become vertices (or nodes).
158
104
 
159
- ## Nested
105
+ The return values are the `last_signal`, which is usually the end event (they return themselves as a signal), the last `options` that usually contains all kinds of data from running the activity, and additional args.
160
106
 
161
- ## Operation
107
+ ## More
162
108
 
163
- If you need a higher abstraction of `circuit`, check out Trailblazer's [operation](http://trailblazer.to/gems/operation/2.0/api.html) implemenation which provides a simple Railway-oriented interface to create linear circuits.
109
+ The [full documentation](http://trb.to/gems/activity/0.2/api.html) for this gem and many more interesting things can be found on the Trailblazer website.
@@ -1,21 +1,22 @@
1
1
  module Trailblazer
2
2
  class Activity
3
- # Builder for running a nested process from a specific `start_at` position.
4
- def self.Subprocess(*args, &block)
5
- Subprocess.new(*args, &block)
3
+ # A {Subprocess} is an instance of an abstract {Activity} that can be `call`ed.
4
+ # It is the runtime instance that runs from a specific start event.
5
+ def self.Subprocess(*args)
6
+ Subprocess.new(*args)
6
7
  end
7
8
 
8
9
  # Subprocess allows to have tasks with a different call interface and start event.
9
- # @param activity Activity interface
10
+ # @param activity any object with an {Activity interface}
10
11
  class Subprocess
11
- def initialize(activity, start_at: nil, call: :call, &block)
12
- @activity, @start_at, @call, @block = activity, start_at, call, block
12
+ def initialize(activity, call: :call, **options)
13
+ @activity = activity
14
+ @options = options
15
+ @call = call
13
16
  end
14
17
 
15
- def call(start_at, *args)
16
- return @block.(activity: activity, start_at: @start_at, args: args) if @block
17
-
18
- @activity.public_send(@call, @start_at, *args)
18
+ def call(args, **circuit_options)
19
+ @activity.public_send(@call, args, circuit_options.merge(@options))
19
20
  end
20
21
 
21
22
  # @private
@@ -23,3 +24,7 @@ module Trailblazer
23
24
  end
24
25
  end
25
26
  end
27
+
28
+ # circuit.( args, runner: Runner, start_at: raise, **circuit_flow_options )
29
+
30
+ # subprocess.( options, flow_options, *args, start_event:<Event>, last_signal: signal )
@@ -8,18 +8,26 @@ module Trailblazer
8
8
  #
9
9
  # Hooks into the TaskWrap.
10
10
  module Trace
11
- def self.call(activity, direction, options, flow_options={}, &block)
11
+ def self.call(activity, (options), *args, &block)
12
12
  tracing_flow_options = {
13
- runner: Wrap::Runner,
14
13
  stack: Trace::Stack.new,
15
- wrap_runtime: ::Hash.new(Trace.wirings),
16
14
  # Note that we don't pass :wrap_static here, that's handled by Task.__call__.
17
15
  introspection: {}, # usually set that in Activity::call.
18
16
  }
19
17
 
20
- direction, options, flow_options = call_circuit( activity, direction, options, tracing_flow_options.merge(flow_options), &block )
18
+ tracing_circuit_options = {
19
+ runner: Wrap::Runner,
20
+ wrap_runtime: ::Hash.new(Trace.wirings), # FIXME: this still overrides existing wrap_runtimes.
21
+ wrap_static: ::Hash.new( Trailblazer::Activity::Wrap.initial_activity ) # FIXME
22
+ }
23
+
24
+ last_signal, (options, flow_options) = call_circuit( activity, [
25
+ options,
26
+ # tracing_flow_options.merge(flow_options),
27
+ tracing_flow_options,
28
+ ], tracing_circuit_options, &block )
21
29
 
22
- return flow_options[:stack].to_a, direction, options, flow_options
30
+ return flow_options[:stack].to_a, last_signal, options, flow_options
23
31
  end
24
32
 
25
33
  # TODO: test alterations with any wrap_circuit.
@@ -36,20 +44,26 @@ module Trailblazer
36
44
  ]
37
45
  end
38
46
 
39
- def self.capture_args(direction, options, flow_options, wrap_config, original_flow_options)
47
+ # def self.capture_args(direction, options, flow_options, wrap_config, original_flow_options)
48
+ def self.capture_args((wrap_config, original_args), **circuit_options)
49
+ (original_options, original_flow_options, _) = original_args[0]
50
+
40
51
  original_flow_options[:stack].indent!
41
52
 
42
- original_flow_options[:stack] << [ wrap_config[:task], :args, nil, options.dup, original_flow_options[:introspection] ]
53
+ original_flow_options[:stack] << [ wrap_config[:task], :args, nil, {}, original_flow_options[:introspection] ]
43
54
 
44
- [ direction, options, flow_options, wrap_config, original_flow_options ]
55
+ [ Circuit::Right, [wrap_config, original_args ], **circuit_options ]
45
56
  end
46
57
 
47
- def self.capture_return(direction, options, flow_options, wrap_config, original_flow_options)
48
- original_flow_options[:stack] << [ wrap_config[:task], :return, flow_options[:result_direction], options.dup ]
58
+ def self.capture_return((wrap_config, original_args), **circuit_options)
59
+ (original_options, original_flow_options, _) = original_args[0]
60
+
61
+ original_flow_options[:stack] << [ wrap_config[:task], :return, wrap_config[:result_direction], {} ]
49
62
 
50
63
  original_flow_options[:stack].unindent!
51
64
 
52
- [ direction, options, flow_options, wrap_config, original_flow_options ]
65
+
66
+ [ Circuit::Right, [wrap_config, original_args ], **circuit_options ]
53
67
  end
54
68
 
55
69
  # Mutable/stateful per design. We want a (global) stack!
@@ -1,5 +1,5 @@
1
1
  module Trailblazer
2
2
  class Activity
3
- VERSION = "0.1.6"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
@@ -5,31 +5,40 @@ class Trailblazer::Activity
5
5
  #
6
6
  # Here, we extend this, and wrap the task `call` into its own pipeline, so we can add external behavior per task.
7
7
  module Runner
8
+ # Runner signature: call( task, direction, options, static_wraps )
9
+ #
8
10
  # @api private
9
- # Runner signature: call( task, direction, options, flow_options, static_wraps )
10
- def self.call(task, direction, options, flow_options, static_wraps = Hash.new(Wrap.initial_activity))
11
- wrap_config = { task: task }
12
- runtime_wraps = flow_options[:wrap_runtime] || raise("Please provide :wrap_runtime")
11
+ # @interface Runner
12
+ def self.call(task, (options, *args), wrap_runtime: raise, wrap_static: raise, **circuit_options)
13
+ wrap_ctx = { task: task }
13
14
 
14
- task_wrap_activity = apply_wirings(task, static_wraps, runtime_wraps)
15
+ # this activity is "wrapped around" the actual `task`.
16
+ task_wrap_activity = apply_wirings(task, wrap_static, wrap_runtime)
15
17
 
16
- # Call the task_wrap circuit:
17
- # |-- Start
18
- # |-- Trace.capture_args [optional]
19
- # |-- Call (call actual task) id: "task_wrap.call_task"
20
- # |-- Trace.capture_return [optional]
21
- # |-- Wrap::End
22
- # Pass empty flow_options to the task_wrap, so it doesn't infinite-loop.
18
+ # We save all original args passed into this Runner.call, because we want to return them later after this wrap
19
+ # is finished.
20
+ original_args = [ [options, *args], circuit_options.merge( wrap_runtime: wrap_runtime, wrap_static: wrap_static ) ]
23
21
 
24
22
  # call the wrap for the task.
25
- ret = task_wrap_activity.( nil, options, {}, wrap_config, flow_options )
23
+ wrap_end_signal, ( wrap_ctx, _ ) = task_wrap_activity.(
24
+ [ wrap_ctx, original_args ] # we omit circuit_options here on purpose, so the wrapping activity uses the plain Runner.
25
+ )
26
+
27
+ # don't return the wrap's end signal, but the one from call_task.
28
+ # return all original_args for the next "real" task in the circuit (this includes circuit_options).
26
29
 
27
- [ *ret, static_wraps ] # return everything plus the static_wraps for the next task in the circuit.
30
+ # raise if wrap_ctx[:result_args][2] != static_wraps
31
+
32
+ # TODO: make circuit ignore all returned but the first
33
+ [ wrap_ctx[:result_direction], wrap_ctx[:result_args] ]
28
34
  end
29
35
 
30
36
  private
31
37
 
32
38
  # Compute the task's wrap by applying alterations both static and from runtime.
39
+ #
40
+ # NOTE: this is for performance reasons: we could have only one hash containing everything but that'd mean
41
+ # unnecessary computations at `call`-time since steps might not even be executed.
33
42
  def self.apply_wirings(task, wrap_static, wrap_runtime)
34
43
  wrap_activity = wrap_static[task] # find static wrap for this specific task, or default wrap activity.
35
44
 
@@ -41,33 +50,30 @@ class Trailblazer::Activity
41
50
 
42
51
  # The call_task method implements one default step `Call` in the Wrap::Activity circuit.
43
52
  # It calls the actual, wrapped task.
44
- def self.call_task(direction, options, flow_options, wrap_config, original_flow_options)
45
- task = wrap_config[:task]
53
+ def self.call_task((wrap_ctx, original_args), **circuit_options)
54
+ task = wrap_ctx[:task]
46
55
 
47
56
  # Call the actual task we're wrapping here.
48
- wrap_config[:result_direction], options, flow_options = task.( direction, options, original_flow_options )
57
+ puts "~~~~wrap.call: #{task}"
58
+ wrap_ctx[:result_direction], wrap_ctx[:result_args] = task.( *original_args )
49
59
 
50
- [ direction, options, flow_options, wrap_config, original_flow_options ]
60
+ # DISCUSS: do we want original_args here to be passed on, or the "effective" result_args which are different to original_args now?
61
+ [ Trailblazer::Circuit::Right, [ wrap_ctx, original_args ], **circuit_options ]
51
62
  end
52
63
 
53
64
  Call = method(:call_task)
54
65
 
55
- class End < Trailblazer::Circuit::End
56
- def call(direction, options, flow_options, wrap_config, *args)
57
- [ wrap_config[:result_direction], options, flow_options ] # note how we don't return the appended internal args.
58
- end
59
- end
60
-
61
66
  # Wrap::Activity is the actual circuit that implements the Task wrap. This circuit is
62
67
  # also known as `task_wrap`.
63
68
  #
64
69
  # Example with tracing:
65
70
  #
66
- # |-- Start
67
- # |-- Trace.capture_args [optional]
68
- # |-- Call (call actual task)
69
- # |-- Trace.capture_return [optional]
70
- # |-- End
71
+ # Call the task_wrap circuit:
72
+ # |-- Start
73
+ # |-- Trace.capture_args [optional]
74
+ # |-- Call (call actual task) id: "task_wrap.call_task"
75
+ # |-- Trace.capture_return [optional]
76
+ # |-- Wrap::End
71
77
 
72
78
  # Activity = Trailblazer::Activity::Activity({ id: "task.wrap" }, end: { default: End.new(:default) }) do |act|
73
79
  # {
@@ -79,7 +85,7 @@ class Trailblazer::Activity
79
85
  def self.initial_activity
80
86
  Trailblazer::Activity.from_wirings(
81
87
  [
82
- [ :attach!, target: [ End.new(:default), type: :event, id: "End.default" ], edge: [ Trailblazer::Circuit::Right, {} ] ],
88
+ [ :attach!, target: [ Trailblazer::Circuit::End.new(:default), type: :event, id: "End.default" ], edge: [ Trailblazer::Circuit::Right, {} ] ],
83
89
  [ :insert_before!, "End.default", node: [ Call, id: "task_wrap.call_task" ], outgoing: [ Trailblazer::Circuit::Right, {} ], incoming: ->(*) { true } ]
84
90
  ]
85
91
  )
@@ -75,14 +75,9 @@ module Trailblazer
75
75
  end
76
76
 
77
77
  def initialize(graph)
78
- @graph = graph
79
- @start_event = @graph[:_wrapped]
80
- @circuit = to_circuit(@graph) # graph is an immutable object.
81
- end
82
-
83
- # Calls the internal circuit. `start_at` defaults to the Activity's start event if `nil` is given.
84
- def call(start_at, *args)
85
- @circuit.( start_at || @start_event, *args )
78
+ @graph = graph
79
+ @default_start_event = graph[:_wrapped]
80
+ @circuit = to_circuit( @graph ) # graph is an immutable object.
86
81
  end
87
82
 
88
83
  def end_events
@@ -97,6 +92,13 @@ module Trailblazer
97
92
  ::Hash[ graph.find_all { |node| graph.successors(node).size == 0 }.collect { |node| [ node[:_wrapped], { role: node[:role] } ] } ]
98
93
  end
99
94
 
95
+ def call(args, start_event: default_start_event, **circuit_options)
96
+ @circuit.(
97
+ args,
98
+ circuit_options.merge( task: start_event) , # this passes :runner to the {Circuit}.
99
+ )
100
+ end
101
+
100
102
  # @private
101
103
  attr_reader :circuit
102
104
  # @private
@@ -104,6 +106,8 @@ module Trailblazer
104
106
 
105
107
  private
106
108
 
109
+ attr_reader :default_start_event
110
+
107
111
  def to_circuit(graph)
108
112
  Circuit.new(graph.to_h( include_leafs: false ), end_events, {})
109
113
  end
@@ -121,5 +125,8 @@ module Trailblazer
121
125
  @graph.find_all { |node| node[:_wrapped] == task }.first
122
126
  end
123
127
  end
128
+
129
+ module Interface
130
+ end
124
131
  end
125
132
  end
@@ -42,3 +42,17 @@ class Trailblazer::Circuit
42
42
  target
43
43
  end
44
44
  end
45
+
46
+ module Trailblazer::Activity::Inspect
47
+ # The "matcher":
48
+ # Finds out appropriate serializer and calls it.
49
+ def self.call(inspected)
50
+ Instance(inspected)
51
+ end
52
+
53
+ # Serializes an object instance with hex IDs.
54
+ # <Trailblazer::Circuit::Start: @name=:default, @options={}>
55
+ def self.Instance(object)
56
+ object.inspect.gsub(/0x\w+/, "")
57
+ end
58
+ end
@@ -19,28 +19,36 @@ module Trailblazer
19
19
  @name = name
20
20
  end
21
21
 
22
- Run = ->(activity, direction, *args) { activity.(direction, *args) }
22
+ # @param args [Array] all arguments to be passed to the task's `call`
23
+ # @param task [callable] task to call
24
+ Run = ->(task, args, **circuit_options) { task.(args, **circuit_options) }
23
25
 
24
- # Runs the circuit. Stops when hitting a End event or subclass thereof.
25
- # This method throws exceptions when the return value of a task doesn't match
26
+ # Runs the circuit until we hit a stop event.
27
+ #
28
+ # This method throws exceptions when the returned value of a task doesn't match
26
29
  # any wiring.
27
30
  #
28
- # @param activity A task from the circuit where to start
29
- # @param args An array of options passed to the first task.
30
- def call(activity, options, flow_options={}, *args)
31
- direction = nil
32
- runner = flow_options[:runner] || Run
33
-
31
+ # @param task An event or task of this circuit from where to start
32
+ # @param options anything you want to pass to the first task
33
+ # @param flow_options Library-specific flow control data
34
+ # @return [last_signal, options, flow_options, *args]
35
+ #
36
+ # DISCUSS: returned circuit_options are ignored when calling the runner.
37
+ def call(args, task: raise, runner: Run, **circuit_options)
34
38
  loop do
35
- direction, options, flow_options, *args = runner.( activity, direction, options, flow_options, *args )
39
+ last_signal, args, _ignored_circuit_options = runner.(
40
+ task,
41
+ args,
42
+ circuit_options.merge( runner: runner ) # options for runner, to be discarded.
43
+ )
36
44
 
37
- # Stop execution of the circuit when we hit a stop event (< End). This could be an activity's End or Suspend.
38
- return [ direction, options, flow_options, *args ] if @stop_events.include?(activity)
45
+ # Stop execution of the circuit when we hit a stop event (< End). This could be an task's End or Suspend.
46
+ return [ last_signal, args ] if @stop_events.include?(task) # DISCUSS: return circuit_options here?
39
47
 
40
- activity = next_for(activity, direction) do |next_activity, in_map|
41
- activity_name = @name[activity] || activity # TODO: this must be implemented only once, somewhere.
42
- raise IllegalInputError.new("#{@name[:id]} #{activity_name}") unless in_map
43
- raise IllegalOutputSignalError.new("from #{@name[:id]}: `#{activity_name}`===>[ #{direction.inspect} ]") unless next_activity
48
+ task = next_for(task, last_signal) do |next_task, in_map|
49
+ task_name = @name[task] || task # TODO: this must be implemented only once, somewhere.
50
+ raise IllegalInputError.new("#{@name[:id]} #{task_name}") unless in_map
51
+ raise IllegalOutputSignalError.new("from #{@name[:id]}: `#{task_name}`===>[ #{last_signal.inspect} ]") unless next_task
44
52
  end
45
53
  end
46
54
  end
@@ -51,16 +59,16 @@ module Trailblazer
51
59
  end
52
60
 
53
61
  private
54
- def next_for(last_activity, emitted_direction)
62
+ def next_for(last_task, emitted_signal)
55
63
  # p @map
56
64
  in_map = false
57
- cfg = @map.keys.find { |t| t == last_activity } and in_map = true
65
+ cfg = @map.keys.find { |t| t == last_task } and in_map = true
58
66
  cfg = @map[cfg] if cfg
59
67
  cfg ||= {}
60
- next_activity = cfg[emitted_direction]
61
- yield next_activity, in_map
68
+ next_task = cfg[emitted_signal]
69
+ yield next_task, in_map
62
70
 
63
- next_activity
71
+ next_task
64
72
  end
65
73
 
66
74
  class IllegalInputError < RuntimeError
@@ -77,13 +85,13 @@ module Trailblazer
77
85
  @options = options
78
86
  end
79
87
 
80
- def call(direction, *args)
88
+ def call(*args)
81
89
  [ self, *args ]
82
90
  end
83
91
  end
84
92
 
85
93
  class Start < End
86
- def call(direction, *args)
94
+ def call(*args)
87
95
  [ Right, *args ]
88
96
  end
89
97
  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.1.6
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-09-15 00:00:00.000000000 Z
11
+ date: 2017-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hirb
@@ -92,6 +92,7 @@ files:
92
92
  - ".travis.yml"
93
93
  - CHANGES.md
94
94
  - Gemfile
95
+ - NOTES
95
96
  - README.md
96
97
  - Rakefile
97
98
  - lib/trailblazer-activity.rb