trailblazer-activity 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +101 -0
  3. data/.rubocop.yml +4 -13
  4. data/.rubocop_todo.yml +474 -476
  5. data/.travis.yml +3 -2
  6. data/CHANGES.md +10 -0
  7. data/Gemfile +5 -4
  8. data/README.md +2 -0
  9. data/Rakefile +1 -1
  10. data/lib/trailblazer/activity.rb +29 -92
  11. data/lib/trailblazer/activity/circuit.rb +74 -0
  12. data/lib/trailblazer/activity/config.rb +4 -6
  13. data/lib/trailblazer/activity/introspect.rb +33 -129
  14. data/lib/trailblazer/activity/present.rb +14 -39
  15. data/lib/trailblazer/activity/schema.rb +13 -0
  16. data/lib/trailblazer/activity/schema/implementation.rb +10 -0
  17. data/lib/trailblazer/activity/schema/intermediate.rb +94 -0
  18. data/lib/trailblazer/activity/structures.rb +43 -43
  19. data/lib/trailblazer/activity/task_wrap.rb +29 -16
  20. data/lib/trailblazer/activity/task_wrap/call_task.rb +4 -4
  21. data/lib/trailblazer/activity/task_wrap/inject.rb +37 -0
  22. data/lib/trailblazer/activity/task_wrap/pipeline.rb +55 -0
  23. data/lib/trailblazer/activity/task_wrap/runner.rb +10 -19
  24. data/lib/trailblazer/activity/task_wrap/variable_mapping.rb +25 -97
  25. data/lib/trailblazer/activity/testing.rb +64 -22
  26. data/lib/trailblazer/activity/trace.rb +88 -41
  27. data/lib/trailblazer/activity/version.rb +4 -2
  28. data/trailblazer-activity.gemspec +5 -9
  29. metadata +18 -55
  30. data/lib/trailblazer/activity/dsl/add_task.rb +0 -22
  31. data/lib/trailblazer/activity/dsl/helper.rb +0 -68
  32. data/lib/trailblazer/activity/dsl/magnetic.rb +0 -36
  33. data/lib/trailblazer/activity/dsl/magnetic/builder.rb +0 -101
  34. data/lib/trailblazer/activity/dsl/magnetic/builder/default_normalizer.rb +0 -26
  35. data/lib/trailblazer/activity/dsl/magnetic/builder/fast_track.rb +0 -118
  36. data/lib/trailblazer/activity/dsl/magnetic/builder/normalizer.rb +0 -113
  37. data/lib/trailblazer/activity/dsl/magnetic/builder/path.rb +0 -105
  38. data/lib/trailblazer/activity/dsl/magnetic/builder/railway.rb +0 -97
  39. data/lib/trailblazer/activity/dsl/magnetic/builder/state.rb +0 -58
  40. data/lib/trailblazer/activity/dsl/magnetic/finalizer.rb +0 -51
  41. data/lib/trailblazer/activity/dsl/magnetic/generate.rb +0 -62
  42. data/lib/trailblazer/activity/dsl/magnetic/merge.rb +0 -16
  43. data/lib/trailblazer/activity/dsl/magnetic/process_options.rb +0 -76
  44. data/lib/trailblazer/activity/dsl/magnetic/structure/alterations.rb +0 -44
  45. data/lib/trailblazer/activity/dsl/magnetic/structure/plus_poles.rb +0 -85
  46. data/lib/trailblazer/activity/dsl/magnetic/structure/polarization.rb +0 -23
  47. data/lib/trailblazer/activity/dsl/record.rb +0 -11
  48. data/lib/trailblazer/activity/dsl/schema/dependencies.rb +0 -46
  49. data/lib/trailblazer/activity/dsl/schema/sequence.rb +0 -46
  50. data/lib/trailblazer/activity/dsl/strategy/build_state.rb +0 -32
  51. data/lib/trailblazer/activity/dsl/strategy/fast_track.rb +0 -24
  52. data/lib/trailblazer/activity/dsl/strategy/path.rb +0 -26
  53. data/lib/trailblazer/activity/dsl/strategy/plan.rb +0 -36
  54. data/lib/trailblazer/activity/dsl/strategy/railway.rb +0 -23
  55. data/lib/trailblazer/activity/interface.rb +0 -16
  56. data/lib/trailblazer/activity/task_wrap/merge.rb +0 -23
  57. data/lib/trailblazer/activity/task_wrap/trace.rb +0 -44
  58. data/lib/trailblazer/circuit.rb +0 -71
@@ -1,4 +1,4 @@
1
- class Trailblazer::Activity < Module
1
+ class Trailblazer::Activity
2
2
  module TaskWrap
3
3
  # The runner is passed into Activity#call( runner: Runner ) and is called for every task in the circuit.
4
4
  # It runs the TaskWrap per task.
@@ -12,18 +12,15 @@ class Trailblazer::Activity < Module
12
12
  def self.call(task, args, circuit_options)
13
13
  wrap_ctx = { task: task }
14
14
 
15
- # this activity is "wrapped around" the actual `task`.
16
- task_wrap_activity = merge_static_with_runtime(task, circuit_options)
15
+ # this pipeline is "wrapped around" the actual `task`.
16
+ task_wrap_pipeline = merge_static_with_runtime(task, circuit_options) || raise
17
17
 
18
18
  # We save all original args passed into this Runner.call, because we want to return them later after this wrap
19
19
  # is finished.
20
20
  original_args = [ args, circuit_options ]
21
21
 
22
22
  # call the wrap {Activity} around the task.
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 default, plain Runner.
25
- {}
26
- )
23
+ wrap_ctx, _ = task_wrap_pipeline.(wrap_ctx, original_args) # we omit circuit_options here on purpose, so the wrapping activity uses the default, plain Runner.
27
24
 
28
25
  # don't return the wrap's end signal, but the one from call_task.
29
26
  # return all original_args for the next "real" task in the circuit (this includes circuit_options).
@@ -39,24 +36,18 @@ class Trailblazer::Activity < Module
39
36
  # unnecessary computations at `call`-time since steps might not even be executed.
40
37
  # TODO: make this faster.
41
38
  def self.merge_static_with_runtime(task, wrap_runtime:, **circuit_options)
42
- wrap_activity = TaskWrap.wrap_static_for(
43
- task,
44
- circuit_options
45
- ) # find static wrap for this specific task, or default wrap activity.
39
+ static_wrap = TaskWrap.wrap_static_for(task, circuit_options) # find static wrap for this specific task [, or default wrap activity].
46
40
 
47
41
  # Apply runtime alterations.
48
- # Grab the additional wirings for the particular `task` from `wrap_runtime` (returns default otherwise).
49
- wrap_runtime[task] ? Trailblazer::Activity::Path::Plan.merge(wrap_activity, wrap_runtime[task]) : wrap_activity
42
+ # Grab the additional wirings for the particular `task` from `wrap_runtime`.
43
+ (dynamic_wrap = wrap_runtime[task]) ? dynamic_wrap.(static_wrap) : static_wrap
50
44
  end
51
45
  end # Runner
52
46
 
53
47
  # Retrieve the static wrap config from {activity}.
54
- # I do not like this part too much, I'd prefer computing the {:wrap_static} at compile-time for the entire
55
- # object graph (including nesteds) and simply pass it through to all Runner calls.
56
- # TODO: simplify that. See above
57
- def self.wrap_static_for(task, activity:, default_activity: TaskWrap.initial_activity, **)
58
- wrap_static = activity[:wrap_static] || {}
59
- wrap_static[task] || default_activity
48
+ def self.wrap_static_for(task, activity:, **)
49
+ wrap_static = activity[:wrap_static]
50
+ wrap_static[task] or raise "#{task}"
60
51
  end
61
52
  end
62
53
  end
@@ -1,6 +1,4 @@
1
- require "trailblazer/context"
2
-
3
- class Trailblazer::Activity < Module
1
+ class Trailblazer::Activity
4
2
  module TaskWrap
5
3
  # Creates taskWrap steps to map variables before and after the actual step.
6
4
  # We hook into the Normalizer, process `:input` and `:output` directives and
@@ -9,55 +7,25 @@ class Trailblazer::Activity < Module
9
7
  # Note that the two options are not the only way to create filters, you can use the
10
8
  # more low-level {Scoped()} etc., too, and write your own filter logic.
11
9
  module VariableMapping
12
- # DSL step for Magnetic::Normalizer.
13
- # Translates `:input` and `:output` into VariableMapping taskWrap extensions.
14
- def self.normalizer_step_for_input_output(ctx, *)
15
- options, io_config = Magnetic::Options.normalize( ctx[:options], [:input, :output] )
16
-
17
- return if io_config.empty?
18
-
19
- ctx[:options] = options # without :input and :output
20
- ctx[:options] = options.merge(Trailblazer::Activity::TaskWrap::VariableMapping(io_config) => true)
21
- end
22
-
23
10
  # The taskWrap extension that's included into the static taskWrap for a task.
24
- def self.extension_for(input, output)
25
- Trailblazer::Activity::DSL::Extension.new(
26
- Merge.new(
27
- Module.new do
28
- extend Path::Plan()
11
+ def self.Extension(input, output, id: input.object_id)
12
+ input = Trailblazer::Option(input)
13
+ output = Trailblazer::Option(output)
29
14
 
30
- task input, id: "task_wrap.input", before: "task_wrap.call_task"
31
- task output, id: "task_wrap.output", before: "End.success", group: :end
32
- end
33
- )
15
+ Trailblazer::Activity::TaskWrap::Extension(
16
+ merge: merge_for(input, output, id: id),
34
17
  )
35
18
  end
36
- end
37
19
 
38
- # @private
39
- def self.filter_for(filter)
40
- if filter.is_a?(::Array) || filter.is_a?(::Hash)
41
- TaskWrap::DSL.filter_from_dsl(filter)
42
- else
43
- filter
20
+ # DISCUSS: do we want the automatic wrapping of {input} and {output}?
21
+ def self.merge_for(input, output, id:) # TODO: rename
22
+ [
23
+ [TaskWrap::Pipeline.method(:insert_before), "task_wrap.call_task", ["task_wrap.input", TaskWrap::Input.new(input, id: id)]],
24
+ [TaskWrap::Pipeline.method(:insert_after), "task_wrap.call_task", ["task_wrap.output", TaskWrap::Output.new(output, id: id)]],
25
+ ]
44
26
  end
45
27
  end
46
28
 
47
-
48
- # Returns an Extension instance to be thrown into the `step` DSL arguments.
49
- def self.VariableMapping(input:, output:)
50
- input = Input.new(
51
- Input::Scoped.new(
52
- Trailblazer::Option::KW( filter_for(input) ) ) )
53
-
54
- output = Output.new(
55
- Output::Unscoped.new(
56
- Trailblazer::Option::KW( filter_for(output) ) ) )
57
-
58
- VariableMapping.extension_for(input, output)
59
- end
60
-
61
29
  # TaskWrap step to compute the incoming {Context} for the wrapped task.
62
30
  # This allows renaming, filtering, hiding, of the options passed into the wrapped task.
63
31
  #
@@ -67,23 +35,25 @@ class Trailblazer::Activity < Module
67
35
 
68
36
  # Calls your {@filter} and replaces the original ctx with your returned one.
69
37
  class Input
70
- def initialize(filter)
38
+ def initialize(filter, id:)
71
39
  @filter = filter
40
+ @id = id
72
41
  end
73
42
 
43
+ # {input.call()} is invoked in the circuit.
74
44
  # `original_args` are the actual args passed to the wrapped task: [ [options, ..], circuit_options ]
75
45
  #
76
- def call( (wrap_ctx, original_args), circuit_options )
46
+ def call(wrap_ctx, original_args)
77
47
  # let user compute new ctx for the wrapped task.
78
48
  input_ctx = apply_filter(*original_args)
79
49
 
80
- wrap_ctx = wrap_ctx.merge( vm_original_ctx: original_args[0][0] ) # remember the original ctx
81
-
82
50
  # decompose the original_args since we want to modify them.
83
51
  (original_ctx, original_flow_options), original_circuit_options = original_args
84
52
 
53
+ wrap_ctx = wrap_ctx.merge(@id => original_ctx) # remember the original ctx by the key {@id}.
54
+
85
55
  # instead of the original Context, pass on the filtered `input_ctx` in the wrap.
86
- return Trailblazer::Activity::Right, [ wrap_ctx, [[input_ctx, original_flow_options], original_circuit_options] ]
56
+ return wrap_ctx, [[input_ctx, original_flow_options], original_circuit_options]
87
57
  end
88
58
 
89
59
  private
@@ -91,71 +61,29 @@ class Trailblazer::Activity < Module
91
61
  def apply_filter((ctx, original_flow_options), original_circuit_options)
92
62
  @filter.( ctx, original_circuit_options ) # returns {new_ctx}.
93
63
  end
94
-
95
- class Scoped
96
- def initialize(filter)
97
- @filter = filter
98
- end
99
-
100
- def call(original_ctx, circuit_options)
101
- Trailblazer::Context( # TODO: make this interchangeable so we can work on faster contexts?
102
- @filter.(original_ctx, **circuit_options)
103
- )
104
- end
105
- end
106
- end
107
-
108
- module DSL
109
- # The returned filter compiles a new hash for Scoped/Unscoped that only contains
110
- # the desired i/o variables.
111
- def self.filter_from_dsl(map)
112
- hsh = DSL.hash_for(map)
113
-
114
- ->(incoming_ctx, kwargs) { Hash[hsh.collect { |from_name, to_name| [to_name, incoming_ctx[from_name]] }] }
115
- end
116
-
117
- def self.hash_for(ary)
118
- return ary if ary.instance_of?(::Hash)
119
- Hash[ary.collect { |name| [name, name] }]
120
- end
121
64
  end
122
65
 
123
66
  # TaskWrap step to compute the outgoing {Context} from the wrapped task.
124
67
  # This allows renaming, filtering, hiding, of the options returned from the wrapped task.
125
68
  class Output
126
- def initialize(filter)
69
+ def initialize(filter, id:)
127
70
  @filter = filter
71
+ @id = id
128
72
  end
129
73
 
130
74
  # Runs your filter and replaces the ctx in `wrap_ctx[:return_args]` with the filtered one.
131
- def call( (wrap_ctx, original_args), **circuit_options )
75
+ def call(wrap_ctx, original_args)
132
76
  (original_ctx, original_flow_options), original_circuit_options = original_args
133
77
 
134
- returned_ctx, _ = wrap_ctx[:return_args] # this is the Context returned from `call`ing the task.
135
- original_ctx = wrap_ctx[:vm_original_ctx]
78
+ returned_ctx, _ = wrap_ctx[:return_args] # this is the Context returned from {call}ing the wrapped user task.
79
+ original_ctx = wrap_ctx[@id] # grab the original ctx from before which was set in the {:input} filter.
136
80
  # let user compute the output.
137
81
  output_ctx = @filter.(original_ctx, returned_ctx, **original_circuit_options)
138
82
 
139
83
  wrap_ctx = wrap_ctx.merge( return_args: [output_ctx, original_flow_options] )
140
84
 
141
85
  # and then pass on the "new" context.
142
- return Trailblazer::Activity::Right, [ wrap_ctx, original_args ]
143
- end
144
-
145
- private
146
-
147
- # Merge the resulting {@filter.()} hash back into the original ctx.
148
- # DISCUSS: do we need the original_ctx as a filter argument?
149
- class Unscoped
150
- def initialize(filter)
151
- @filter = filter
152
- end
153
-
154
- def call(original_ctx, new_ctx, **circuit_options)
155
- original_ctx.merge(
156
- @filter.(new_ctx, **circuit_options)
157
- )
158
- end
86
+ return wrap_ctx, original_args
159
87
  end
160
88
  end
161
89
  end # Wrap
@@ -1,32 +1,74 @@
1
1
  # DISCUSS: move to trailblazer-activity-test ?
2
2
 
3
3
  # Helpers to quickly create steps and tasks.
4
- module Trailblazer::Activity::Testing
5
- # Creates a module with one step method for each name.
6
- #
7
- # @example
8
- # extend T.def_steps(:create, :save)
9
- def self.def_steps(*names)
10
- Module.new do
11
- names.each do |name|
12
- define_method(name) do | ctx, ** |
13
- ctx[:seq] << name
14
- ctx.key?(name) ? ctx[name] : true
4
+ module Trailblazer
5
+ module Activity::Testing
6
+ # Creates a module with one step method for each name.
7
+ #
8
+ # @example
9
+ # extend T.def_steps(:create, :save)
10
+ def self.def_steps(*names)
11
+ Module.new do
12
+ module_function
13
+ names.each do |name|
14
+ define_method(name) do | ctx, ** |
15
+ ctx[:seq] << name
16
+ ctx.key?(name) ? ctx[name] : true
17
+ end
15
18
  end
16
19
  end
17
20
  end
18
- end
19
21
 
20
- # Creates a method instance with a task interface.
21
- #
22
- # @example
23
- # task task: T.def_task(:create)
24
- def self.def_task(name)
25
- Module.new do
26
- define_singleton_method(name) do | (ctx, flow_options), ** |
27
- ctx[:seq] << name
28
- return Trailblazer::Activity::Right, [ctx, flow_options]
22
+ # Creates a method instance with a task interface.
23
+ #
24
+ # @example
25
+ # task task: T.def_task(:create)
26
+ def self.def_task(name)
27
+ Module.new do
28
+ define_singleton_method(name) do | (ctx, flow_options), ** |
29
+ ctx[:seq] << name
30
+ return Activity::Right, [ctx, flow_options]
31
+ end
32
+ end.method(name)
33
+ end
34
+
35
+ def self.def_tasks(*names)
36
+ Module.new do
37
+ module_function
38
+ names.each do |name|
39
+ define_method(name) do | (ctx, flow_options), ** |
40
+ ctx[:seq] << name
41
+ result = ctx.key?(name) ? ctx[name] : true
42
+
43
+ return (result ? Activity::Right : Activity::Left), [ctx, flow_options]
44
+ end
45
+ end
29
46
  end
30
- end.method(name)
47
+ end
48
+
49
+ module Assertions
50
+ def Cct(activity)
51
+ cct = Trailblazer::Developer::Render::Circuit.(activity)
52
+ end
53
+
54
+ # Tests {:circuit} and {:outputs} fields so far.
55
+ def assert_process_for(process, *args)
56
+ semantics, circuit = args[0..-2], args[-1]
57
+
58
+ inspects = semantics.collect { |semantic| %{#<struct Trailblazer::Activity::Output signal=#<Trailblazer::Activity::End semantic=#{semantic.inspect}>, semantic=#{semantic.inspect}>} }
59
+
60
+ process.to_h[:outputs].inspect.must_equal %{[#{inspects.join(", ")}]}
61
+
62
+ assert_circuit(process, circuit)
63
+
64
+ process
65
+ end
66
+
67
+ def assert_circuit(schema, circuit)
68
+ cct = Cct(schema)
69
+ cct = cct.gsub("#<Trailblazer::Activity::TaskBuilder::Task user_proc=", "<*")
70
+ cct.must_equal %{#{circuit}}
71
+ end
72
+ end
31
73
  end
32
74
  end
@@ -1,77 +1,123 @@
1
1
  module Trailblazer
2
- class Activity < Module # Trace#call will call the activities and trace what steps are called, options passed,
3
- # and the order and nesting.
4
- #
5
- # stack, _ = Trailblazer::Activity::Trace.(activity, activity[:Start], { id: 1 })
6
- # puts Trailblazer::Activity::Present.tree(stack) # renders the trail.
7
- #
8
- # Hooks into the taskWrap.
2
+ class Activity
9
3
  module Trace
10
4
  class << self
11
- # {:argumenter} API
12
- # FIXME: needs Introspect.arguments_for_call
13
- # FIXME: needs TaskWrap.arguments_for_call
5
+ # Public entry point to activate tracing when running {activity}.
6
+ def call(activity, (ctx, flow_options), circuit_options={})
7
+ activity, (ctx, flow_options), circuit_options = Trace.arguments_for_call( activity, [ctx, flow_options], circuit_options ) # only run once for the entire circuit!
8
+
9
+ signal, (ctx, flow_options) = Activity::TaskWrap.invoke(activity, [ctx, flow_options], circuit_options)
10
+
11
+ return flow_options[:stack], signal, [ctx, flow_options]
12
+ end
13
+
14
+ alias_method :invoke, :call
15
+
14
16
  def arguments_for_call(activity, (options, flow_options), **circuit_options)
15
17
  tracing_flow_options = {
16
- stack: Trace::Stack.new,
18
+ stack: Trace::Stack.new,
17
19
  }
18
20
 
19
21
  tracing_circuit_options = {
20
- wrap_runtime: ::Hash.new(Trace.wirings), # FIXME: this still overrides existing :wrap_runtime.
22
+ wrap_runtime: ::Hash.new(Trace.merge_plan), # DISCUSS: this overrides existing {:wrap_runtime}.
21
23
  }
22
24
 
23
25
  return activity, [ options, tracing_flow_options.merge(flow_options) ], circuit_options.merge(tracing_circuit_options)
24
26
  end
27
+ end
25
28
 
26
- def call(activity, (options, flow_options), circuit_options={})
27
- activity, (options, flow_options), circuit_options = Trace.arguments_for_call( activity, [options, flow_options], circuit_options ) # only run once for the entire circuit!
29
+ module_function
30
+ # Insertions for the trace tasks that capture the arguments just before calling the task,
31
+ # and before the TaskWrap is finished.
32
+ #
33
+ # @private
34
+ def merge_plan
35
+ TaskWrap::Pipeline::Merge.new(
36
+ [TaskWrap::Pipeline.method(:insert_before), "task_wrap.call_task", ["task_wrap.capture_args", Trace.method(:capture_args)]],
37
+ [TaskWrap::Pipeline.method(:append), nil, ["task_wrap.capture_return", Trace.method(:capture_return)]],
38
+ )
39
+ end
28
40
 
29
- last_signal, (options, flow_options) =
30
- Activity::TaskWrap.invoke(activity, [options, flow_options], circuit_options)
41
+ # taskWrap step to capture incoming arguments of a step.
42
+ def capture_args(wrap_config, original_args)
43
+ original_args = capture_for(wrap_config[:task], *original_args)
31
44
 
32
- return flow_options[:stack], last_signal, [options, flow_options]
33
- end
45
+ return wrap_config, original_args
46
+ end
34
47
 
35
- alias_method :invoke, :call
48
+ # taskWrap step to capture outgoing arguments from a step.
49
+ def capture_return(wrap_config, original_args)
50
+ (original_options, original_flow_options, _) = original_args[0]
36
51
 
37
- # Insertions for the trace tasks that capture the arguments just before calling the task,
38
- # and before the TaskWrap is finished.
39
- #
40
- # Note that the TaskWrap steps are implemented in Activity::TaskWrap::Trace.
41
- #
42
- # @private
43
- def wirings
44
- Module.new do
45
- extend Activity::Path::Plan()
46
-
47
- task TaskWrap::Trace.method(:capture_args), id: "task_wrap.capture_args", before: "task_wrap.call_task"
48
- task TaskWrap::Trace.method(:capture_return), id: "task_wrap.capture_return", before: "End.success", group: :end
49
- end
50
- end
51
- end
52
+ original_flow_options[:stack] << Entity::Output.new(
53
+ wrap_config[:task], {}, wrap_config[:return_signal]
54
+ ).freeze
55
+
56
+ original_flow_options[:stack].unindent!
52
57
 
53
- Entity = Struct.new(:task, :activity, :data)
54
- class Entity::Input < Entity
58
+
59
+ return wrap_config, original_args
55
60
  end
56
61
 
57
- class Entity::Output < Entity
62
+ # It's important to understand that {flow[:stack]} is mutated by design. This is needed so
63
+ # in case of exceptions we still have a "global" trace - unfortunately Ruby doesn't allow
64
+ # us a better way.
65
+ def capture_for(task, (ctx, flow), activity:, **circuit_options)
66
+ flow[:stack].indent!
67
+
68
+ flow[:stack] << Entity::Input.new(
69
+ task, activity, [ctx, ctx.inspect]
70
+ ).freeze
71
+
72
+ return [ctx, flow], circuit_options.merge(activity: activity)
58
73
  end
59
74
 
60
- class Task < Array
75
+ # Structures used in {capture_args} and {capture_return}.
76
+ # These get pushed onto one {Level} in a {Stack}.
77
+ #
78
+ # Level[
79
+ # Level[ ==> this is a scalar task
80
+ # Entity::Input
81
+ # Entity::Output
82
+ # ]
83
+ # Level[ ==> nested task
84
+ # Entity::Input
85
+ # Level[
86
+ # Entity::Input
87
+ # Entity::Output
88
+ # ]
89
+ # Entity::Output
90
+ # ]
91
+ # ]
92
+ Entity = Struct.new(:task, :activity, :data)
93
+ Entity::Input = Class.new(Entity)
94
+ Entity::Output = Class.new(Entity)
95
+
96
+ class Level < Array
61
97
  def inspect
62
- %{<Task>#{super}}
98
+ %{<Level>#{super}}
99
+ end
100
+
101
+ # @param level {Trace::Level}
102
+ def self.input_output_nested_for_level(level)
103
+ input = level[0]
104
+ output = level[-1]
105
+
106
+ output, nested = output.is_a?(Entity::Output) ? [output, level-[input, output]] : [nil, level[1..-1]]
107
+
108
+ return input, output, nested
63
109
  end
64
110
  end
65
111
 
66
112
  # Mutable/stateful per design. We want a (global) stack!
67
113
  class Stack
68
114
  def initialize
69
- @nested = []
115
+ @nested = Level.new
70
116
  @stack = [ @nested ]
71
117
  end
72
118
 
73
119
  def indent!
74
- current << indented = Task.new
120
+ current << indented = Level.new
75
121
  @stack << indented
76
122
  end
77
123
 
@@ -88,6 +134,7 @@ module Trailblazer
88
134
  end
89
135
 
90
136
  private
137
+
91
138
  def current
92
139
  @stack.last
93
140
  end