trailblazer-activity 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14060aa9266dec50a2538bfddacb622d1fcefeba94b92fade757e039ea8aab4f
4
- data.tar.gz: 4bcd28d5b77dc9023c43edb0d762a4ac228fa36012a275f3830f56db9e71e795
3
+ metadata.gz: 656825c80059310cf4a2d89a2214a20618bbd688c182285e8d3c24c8475b8233
4
+ data.tar.gz: 4fb358255fe07a12fcb94172a05f466ce3ac0cb66d3b9502a3622d2a4392a71f
5
5
  SHA512:
6
- metadata.gz: 95e528103b854970aa5b168ce89e8164ae0bdd64ffa21deec24fbd05aa6ca8700d2aa252f5ee3b7b12d2ffe9e592fbb6f3424a9208aa86be96b167fb7bc0c036
7
- data.tar.gz: '09e520f7372cd6d604dc5444484bc561e29e4c1462fd2298bc6d9d4d6a1571eadb37dd7150988cf94a53fe0b218a94529e0fd38e5e2fb31a57ca3ae017640dbc'
6
+ metadata.gz: 74361dffc29b0108e198a3a2bee9637b6519b2ad56b4fb9172aceffc92bee1bcb5dc5e1417dab33d9ba48340749bd5f4306bd16a2c22a1f5eb66cfea4991f04b
7
+ data.tar.gz: ad2fd6a2d6d4df1a2c8e167fd395ef05899f7389b95f8219239d4533c75632001992ff38eb240b300d1b2260544b85abd276372783f8a4c2480afc2a3e45a1a5
data/.travis.yml CHANGED
@@ -1,14 +1,8 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- # - 2.0
5
- - 2.1
6
- - 2.2
7
- - 2.3.3
8
- - 2.4.3
9
- - 2.5.0
10
- matrix:
11
- include:
12
- - rvm: jruby-head
13
- env: JRUBY_OPTS="--profile.api"
4
+ - 2.5.1
5
+ - 2.4.4
6
+ - 2.3.7
7
+ - 2.2.10
14
8
  before_install: gem install bundler
data/CHANGES.md CHANGED
@@ -1,3 +1,25 @@
1
+ # 0.6.0
2
+
3
+ * The `:task` option in `Circuit::call` is now named `:start_task` for consistency.
4
+ * Removed the `:argumenter` option for `Activity::call`. Instead, an `Activity` passes itself via the `:activity` option.
5
+ * Removed the `:extension` option. Instead, any option from the DSL that `is_a?(DSL::Extension)` will be processed in `add_task!`.
6
+ * Replace argumenters with `TaskWrap::invoke`. This simplifies the whole `call` process, and moves all initialization of args to the top.
7
+ * Added `Introspect::Graph::find`.
8
+ * Removed `Introspect::Enumerator` in favor of the `Graph` API.
9
+
10
+ # 0.5.4
11
+
12
+ * Introducing `Introspect::Enumerator` and removing `Introspect.find`. `Enumerator` contains `Enumerable` and exposes all necessary utility methods.
13
+
14
+ # 0.5.3
15
+
16
+ * In Path(), allow referencing an existing task, instead of creating an end event.
17
+ This avoids having to use two `Output() => ..` and is much cleaner.
18
+
19
+ ```ruby
20
+ Path( end_id: :find_model) do .. end
21
+ ```
22
+
1
23
  # 0.5.2
2
24
 
3
25
  * In `Path()`, we removed the `#path` method in favor of a cleaner `task` DSL method. We now use the default plus_poles `success` and `failure` everywhere for consistency. This means that a `task` has two outputs, and if you referenced `Output(:success)`, that would be only one of them. We're planning to have `pass` back which has one `success` plus_pole, only. This change makes the DSL wiring behavior much more consistent.
@@ -12,10 +12,10 @@ class Trailblazer::Activity < Module
12
12
  self[:circuit] = circuit
13
13
  self[:outputs] = outputs
14
14
 
15
- _, local_options = returned_options
15
+ _, local_options, connections, sequence_options, extension_options = returned_options
16
16
 
17
17
  # {Extension API} call all extensions.
18
- local_options[:extension].collect { |ext| ext.( self, *returned_options, original_dsl_args: [name, task, options, block] ) } if local_options[:extension]
18
+ extension_options.keys.collect { |ext| ext.( self, *returned_options, original_dsl_args: [name, task, options, block] ) }
19
19
  end
20
20
  end
21
21
  end
@@ -15,6 +15,11 @@ module Trailblazer
15
15
  # @api private
16
16
  OutputSemantic = Struct.new(:value)
17
17
  Track = Struct.new(:color)
18
+ Extension = Struct.new(:callable) do
19
+ def call(*args, &block)
20
+ callable.(*args, &block)
21
+ end
22
+ end
18
23
 
19
24
  # Shortcut functions for the DSL. These have no state.
20
25
  module Helper
@@ -5,8 +5,8 @@ module Trailblazer
5
5
  # task Callable, id: "success", before: "another"
6
6
  class DefaultNormalizer
7
7
  # Declarative::Variables
8
- def self.build(plus_poles:, extension:[], **options)
9
- return new(plus_poles: plus_poles, extension: extension), options
8
+ def self.build(plus_poles:, **options)
9
+ return new(plus_poles: plus_poles), options
10
10
  end
11
11
 
12
12
  def initialize(**default_options)
@@ -19,7 +19,7 @@ module Trailblazer
19
19
 
20
20
  local_options, sequence_options = Options.normalize( local_options, Activity::Schema::Dependencies.sequence_keywords )
21
21
 
22
- return task, local_options, {}, sequence_options
22
+ return task, local_options, {}, sequence_options, {}
23
23
  end
24
24
  end
25
25
  end
@@ -30,9 +30,9 @@ module Trailblazer
30
30
  default_outputs: @default_outputs,
31
31
  }
32
32
 
33
- signal, (ctx, ) = @pipeline.( [ctx] )
33
+ signal, (ctx, ) = @pipeline.( [ctx], {} )
34
34
 
35
- return ctx[:options][:task], ctx[:local_options], ctx[:connection_options], ctx[:sequence_options]
35
+ return ctx[:options][:task], ctx[:local_options], ctx[:connection_options], ctx[:sequence_options], ctx[:extension_options]
36
36
  end
37
37
 
38
38
  # needs the basic Normalizer
@@ -42,15 +42,18 @@ module Trailblazer
42
42
  extend Trailblazer::Activity::Path( normalizer_class: DefaultNormalizer, plus_poles: PlusPoles.new.merge( Builder::Path.default_outputs.values ) ) # FIXME: the DefaultNormalizer actually doesn't need Left.
43
43
 
44
44
  def self.split_options( ctx, task:, options:, ** )
45
- keywords = extract_dsl_keywords(options)
45
+ keywords = extract_dsl_keywords(options)
46
+ extensions = extract_extensions(options)
46
47
 
47
48
  # sort through the "original" user DSL options.
49
+ options, extension_options = Options.normalize( options, extensions ) # DISCUSS:
48
50
  options, local_options = Options.normalize( options, keywords ) # DISCUSS:
49
51
  local_options, sequence_options = Options.normalize( local_options, Activity::Schema::Dependencies.sequence_keywords )
50
52
 
51
53
  ctx[:local_options],
52
54
  ctx[:connection_options],
53
- ctx[:sequence_options] = local_options, options, sequence_options
55
+ ctx[:sequence_options],
56
+ ctx[:extension_options] = local_options, options, sequence_options, extension_options
54
57
  end
55
58
 
56
59
  # Filter out connections, e.g. `Output(:fail_fast) => :success` and return only the keywords like `:id` or `:replace`.
@@ -58,17 +61,19 @@ module Trailblazer
58
61
  options.keys - options.keys.find_all { |k| connection_classes.include?( k.class ) }
59
62
  end
60
63
 
64
+ def self.extract_extensions(options, extensions_classes = [Activity::DSL::Extension])
65
+ options.keys.find_all { |k| extensions_classes.include?( k.class ) }
66
+ end
67
+
61
68
  # FIXME; why don't we use the extensions passed into the initializer?
62
- def self.normalize_extension_option( ctx, local_options:, ** )
63
- local_options[:extension] = (local_options[:extension]||[]) + [ Activity::Introspect.method(:add_introspection), Activity::DSL.method(:record) ] # fixme: this sucks
69
+ def self.initialize_extension_option( ctx, options:, ** )
70
+ ctx[:options] = options.merge( Activity::DSL::Extension.new( Activity::DSL.method(:record) ) => true )
64
71
  end
65
72
 
66
- # Normalizes ctx[:options]
73
+ # Normalizes ctx[:options] (the user-options via the DSL) into the internal Hash format.
67
74
  def self.normalize_for_macro( ctx, task:, options:, task_builder:, ** )
68
75
  ctx[:options] =
69
76
  if task.is_a?(::Hash) # macro.
70
- options = options.merge(extension: (options[:extension]||[])+(task[:extension]||[]) ) # FIXME.
71
-
72
77
  task.merge(options) # Note that the user options are merged over the macro options.
73
78
  else # user step
74
79
  { id: task }
@@ -93,9 +98,9 @@ module Trailblazer
93
98
  .merge(local_options)
94
99
  end
95
100
 
101
+ task Activity::TaskBuilder::Binary( method(:initialize_extension_option) ), id: "initialize_extension_option"
96
102
  task Activity::TaskBuilder::Binary( method(:normalize_for_macro) ), id: "normalize_for_macro"
97
103
  task Activity::TaskBuilder::Binary( method(:split_options) ), id: "split_options"
98
- task Activity::TaskBuilder::Binary( method(:normalize_extension_option) ), id: "normalize_extension_option"
99
104
  task Activity::TaskBuilder::Binary( method(:initialize_plus_poles) ), id: "initialize_plus_poles"
100
105
  # task ->((ctx, _), **) { pp ctx; [Activity::Right, [ctx, _]] }
101
106
  end
@@ -24,11 +24,15 @@ module Trailblazer
24
24
  end
25
25
 
26
26
  # @return [Adds] list of Adds instances that can be chained or added to an existing sequence.
27
- def self.InitialAdds(track_color:raise, end_semantic:raise, start_outputs: {success: self.default_outputs[:success]}, track_end: Activity.End(end_semantic), **)
27
+ def self.InitialAdds(**options)
28
+ StartAdds(**options) + EndAdds(**options)
29
+ end
28
30
 
31
+ # TODO: make this nicer.
32
+ def self.StartAdds(track_color:, end_semantic:, start_outputs: {success: self.default_outputs[:success]}, **)
29
33
  builder_options={ track_color: track_color, end_semantic: end_semantic }
30
34
 
31
- start_adds = adds(
35
+ adds(
32
36
  Activity::Start.new(semantic: :default),
33
37
 
34
38
  TaskPolarizations(builder_options),
@@ -39,20 +43,31 @@ module Trailblazer
39
43
  magnetic_to: [],
40
44
  plus_poles: PlusPoles.initial(start_outputs), # FIXME: this is actually redundant with Normalizer
41
45
  )
46
+ end
42
47
 
43
- end_adds = adds(
44
- track_end,
48
+ # TODO: make this nicer.
49
+ def self.EndAdds(track_color:, end_semantic:, track_end: Activity.End(end_semantic), end_id: nil, **)
50
+ # an end can either be a reference to another task,
51
+ # or a "real" end event.
52
+ if end_id
53
+ [[:magnetic_to,
54
+ [ end_id, [track_color] ] ]
55
+ ]
56
+ else
57
+ builder_options={ track_color: track_color, end_semantic: end_semantic }
45
58
 
46
- EndEventPolarizations(builder_options),
59
+ adds(
60
+ track_end,
47
61
 
48
- {}, { group: :end },
62
+ EndEventPolarizations(builder_options), # only sets :magnetic_to.
49
63
 
50
- id: "End.#{track_color}",
51
- plus_poles: {},
52
- magnetic_to: nil,
53
- )
64
+ {}, { group: :end },
54
65
 
55
- start_adds + end_adds
66
+ id: "End.#{track_color}",
67
+ plus_poles: {},
68
+ magnetic_to: nil,
69
+ )
70
+ end
56
71
  end
57
72
 
58
73
  def self.TaskPolarizations(track_color:, **)
@@ -42,23 +42,23 @@ module Trailblazer
42
42
  def insert(strategy, polarizer, task, options, &block)
43
43
  normalizer = options[:normalizer] || @normalizer # DISCUSS: do this at a deeper point?
44
44
 
45
- task, local_options, connection_options, sequence_options = normalizer.(task, options)
45
+ task, local_options, connection_options, sequence_options, extension_options = normalizer.(task, options)
46
46
 
47
47
  polarizations = strategy.send(polarizer, @builder_options) # Railway.StepPolarizations( @builder_options )
48
48
 
49
- insert_element( polarizations, task, local_options, connection_options, sequence_options, &block )
49
+ insert_element( polarizations, task, local_options, connection_options, sequence_options, extension_options, &block )
50
50
  end
51
51
 
52
52
  private
53
53
 
54
54
  # Internal top-level entry point to add task(s) and connections.
55
55
  # High level interface for DSL calls like ::task or ::step.
56
- def insert_element(polarizations, task, local_options, connection_options, sequence_options, &block)
57
- adds, *returned_options = Builder.adds_for(polarizations, task, local_options, connection_options, sequence_options, &block)
56
+ def insert_element(polarizations, task, local_options, connection_options, sequence_options, extension_options, &block)
57
+ adds, *returned_options = Builder.adds_for(polarizations, task, local_options, connection_options, sequence_options, extension_options, &block)
58
58
  end
59
59
 
60
60
  # @return Adds
61
- def self.adds_for(polarizations, task, local_options, connection_options, sequence_options, &block)
61
+ def self.adds_for(polarizations, task, local_options, connection_options, sequence_options, extension_options, &block)
62
62
  # go through all wiring options such as Output()=>:color.
63
63
  polarizations_from_user_options, additional_adds = process_dsl_options(connection_options, local_options, &block)
64
64
 
@@ -66,7 +66,7 @@ module Trailblazer
66
66
 
67
67
  result = adds(task, polarizations, local_options, sequence_options, local_options)
68
68
 
69
- return result + (local_options[:adds] || []) + additional_adds, task, local_options, connection_options, sequence_options
69
+ return result + (local_options[:adds] || []) + additional_adds, task, local_options, connection_options, sequence_options, extension_options
70
70
  end
71
71
 
72
72
  def self.process_dsl_options(options, id:nil, plus_poles:, **, &block)
@@ -34,7 +34,7 @@ module Trailblazer
34
34
  def self.circuit_hash_to_process(circuit_hash)
35
35
  end_events = end_events_for(circuit_hash)
36
36
 
37
- return Circuit.new(circuit_hash, end_events), end_events
37
+ return Circuit.new(circuit_hash, end_events, start_task: circuit_hash.keys.first), end_events
38
38
  end
39
39
 
40
40
  # Find all end events that don't have outgoing connections.
@@ -55,11 +55,11 @@ module Trailblazer
55
55
  Polarization.new( output: output, color: task.color )
56
56
  ]
57
57
  else # ID: existing step
58
- new_edge = "#{id}-#{output.signal}-#{task}"
58
+ new_edge = "#{id}-#{output.signal}-#{task}" # edge from <id> to <target>
59
59
 
60
60
  [
61
61
  Polarization.new( output: output, color: new_edge ),
62
- [[ :magnetic_to, [ task, [new_edge] ] ]],
62
+ [[ :magnetic_to, [ task, [new_edge] ] ]], # mark target (`task`) as magnetic to the new edge.
63
63
  ]
64
64
  end
65
65
  end
@@ -12,7 +12,6 @@ module Trailblazer
12
12
  builder_class: Magnetic::Builder::Path, # we use the Activity-based Normalizer
13
13
  normalizer_class: Magnetic::Normalizer,
14
14
  default_outputs: Magnetic::Builder::Path.default_outputs, # binary outputs
15
- extension: [ Introspect.method(:add_introspection) ],
16
15
 
17
16
  extend: [
18
17
  # DSL.def_dsl(:task, Magnetic::Builder::Path, :PassPolarizations),
@@ -1,27 +1,52 @@
1
1
  module Trailblazer
2
- class Activity < Module # Introspection is not used at run-time except for rendering diagrams, tracing, and the like.
2
+ class Activity < Module
3
+ # Compile-time.
4
+ # Introspection is not used at run-time except for rendering diagrams, tracing, and the like.
3
5
  module Introspect
4
- # {:argumenter} API
5
- def self.arguments_for_call(activity, (options, flow_options), **circuit_options)
6
- circuit_options = circuit_options.merge( introspect: activity.debug )
7
-
8
- return activity, [ options, flow_options ], circuit_options
6
+ def self.Graph(*args)
7
+ Graph.new(*args)
9
8
  end
10
9
 
11
- # {Extension} API
12
- def self.add_introspection(activity, task, local_options, *returned_options)
13
- activity[:debug, task] = { id: local_options[:id] || task }
14
- end
10
+ # @private This API is still under construction.
11
+ class Graph
12
+ def initialize(activity)
13
+ @activity = activity
14
+ @circuit = activity.to_h[:circuit]
15
+ @adds = activity.to_h[:adds].compact # FIXME: why are there nils in Adds?
16
+ end
17
+
18
+ def find(id=nil, &block)
19
+ return find_by_id(id) unless block_given?
20
+ find_with_block(&block)
21
+ end
22
+
23
+ private
24
+
25
+ def find_by_id(id)
26
+ (_, (id, triplett)) = @adds.find { |(op, (_id, triplett))| _id == id }
27
+
28
+ Node(triplett[1], id, triplett[0])
29
+ end
30
+
31
+ def find_with_block(&block)
32
+ adds = @adds.find { |(op, (id, triplett))| yield( Node(triplett[1], id, triplett[0]) ) }
33
+ return nil unless adds
34
+
35
+ (op, (id, triplett)) = adds
15
36
 
37
+ Node(triplett[1], id, triplett[0])
38
+ end
16
39
 
17
- # @api private
18
- def self.find(activity, &block)
19
- circuit = activity.to_h[:circuit]
40
+ def Node(*args)
41
+ Node.new(*args).freeze
42
+ end
20
43
 
21
- circuit.instance_variable_get(:@map).find(&block)
44
+ Node = Struct.new(:task, :id, :magnetic_to)
22
45
  end
23
46
 
24
47
 
48
+ # FIXME: remove this
49
+ # @private This will be removed shortly.
25
50
  def self.collect(activity, options={}, &block)
26
51
  circuit = activity.to_h[:circuit]
27
52
  circuit_hash = circuit.to_h[:map]
@@ -17,7 +17,10 @@ module Trailblazer
17
17
  stack.each do |captured, *returned|
18
18
  task = captured.task
19
19
 
20
- name = (node = captured.introspection[task]) ? node[:id] : task
20
+ graph = Introspect::Graph(captured.activity)
21
+
22
+ name = (node = graph.find { |node| node[:task] == task }) ? node[:id] : task
23
+ name ||= task # FIXME: bullshit
21
24
 
22
25
  if returned.size == 1 # flat
23
26
  tree << [ level, name ]
@@ -18,8 +18,8 @@
18
18
  @options = options.merge(semantic: semantic)
19
19
  end
20
20
 
21
- def call(*args)
22
- return self, *args
21
+ def call(args, circuit_options)
22
+ return self, args, circuit_options
23
23
  end
24
24
 
25
25
  def to_h
@@ -34,8 +34,8 @@
34
34
  end
35
35
 
36
36
  class Start < End
37
- def call(*args)
38
- return Activity::Right, *args
37
+ def call(args, circuit_options)
38
+ return Activity::Right, args, circuit_options
39
39
  end
40
40
  end
41
41
 
@@ -8,7 +8,7 @@ class Trailblazer::Activity < Module
8
8
 
9
9
  # Call the actual task we're wrapping here.
10
10
  # puts "~~~~wrap.call: #{task}"
11
- return_signal, return_args = task.( *original_args ) # we lose :exec_context here.
11
+ return_signal, return_args = task.(*original_args)
12
12
 
13
13
  # DISCUSS: do we want original_args here to be passed on, or the "effective" return_args which are different to original_args now?
14
14
  wrap_ctx = wrap_ctx.merge( return_signal: return_signal, return_args: return_args )
@@ -11,7 +11,7 @@ module Trailblazer
11
11
  # {:extension API}
12
12
  def call(activity, task, local_options, *returned_options)
13
13
  # we could make the default initial_activity injectable via the DSL, the value would sit in returned_options or local_options.
14
- static_wrap = Activity::TaskWrap.wrap_static_for(task, wrap_static: activity[:wrap_static] || {})
14
+ static_wrap = Activity::TaskWrap.wrap_static_for(task, activity: activity)
15
15
 
16
16
  # # macro might want to apply changes to the static task_wrap (e.g. Inject)
17
17
  new_wrap = Activity::Path::Plan.merge( static_wrap, @extension_plan )
@@ -9,7 +9,7 @@ class Trailblazer::Activity < Module
9
9
  #
10
10
  # @api private
11
11
  # @interface Runner
12
- def self.call(task, args, **circuit_options)
12
+ def self.call(task, args, circuit_options)
13
13
  wrap_ctx = { task: task }
14
14
 
15
15
  # this activity is "wrapped around" the actual `task`.
@@ -18,9 +18,11 @@ class Trailblazer::Activity < Module
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
  # call the wrap {Activity} around the task.
22
23
  wrap_end_signal, ( wrap_ctx, _ ) = task_wrap_activity.(
23
- [ wrap_ctx, original_args ] # we omit circuit_options here on purpose, so the wrapping activity uses the default, plain Runner.
24
+ [ wrap_ctx, original_args ], # we omit circuit_options here on purpose, so the wrapping activity uses the default, plain Runner.
25
+ {}
24
26
  )
25
27
 
26
28
  # don't return the wrap's end signal, but the one from call_task.
@@ -37,7 +39,10 @@ class Trailblazer::Activity < Module
37
39
  # unnecessary computations at `call`-time since steps might not even be executed.
38
40
  # TODO: make this faster.
39
41
  def self.merge_static_with_runtime(task, wrap_runtime:, **circuit_options)
40
- wrap_activity = TaskWrap.wrap_static_for(task, circuit_options) # find static wrap for this specific task, or default wrap activity.
42
+ wrap_activity = TaskWrap.wrap_static_for(
43
+ task,
44
+ circuit_options
45
+ ) # find static wrap for this specific task, or default wrap activity.
41
46
 
42
47
  # Apply runtime alterations.
43
48
  # Grab the additional wirings for the particular `task` from `wrap_runtime` (returns default otherwise).
@@ -45,7 +50,12 @@ class Trailblazer::Activity < Module
45
50
  end
46
51
  end # Runner
47
52
 
48
- def self.wrap_static_for(task, wrap_static:, default_activity: TaskWrap.initial_activity, **)
53
+ # 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] || {}
49
59
  wrap_static[task] || default_activity
50
60
  end
51
61
  end
@@ -2,26 +2,42 @@ class Trailblazer::Activity < Module
2
2
  module TaskWrap
3
3
  # TaskWrap tasks for tracing.
4
4
  module Trace
5
- # def self.capture_args(direction, options, flow_options, wrap_config, original_flow_options)
6
- def self.capture_args((wrap_config, original_args), **circuit_options)
7
- (original_options, original_flow_options), original_circuit_options = original_args
5
+ module_function
8
6
 
9
- original_flow_options[:stack].indent!
7
+ # taskWrap step to capture incoming arguments of a step.
8
+ # def self.capture_args(direction, options, flow_options, wrap_config, original_flow_options)
9
+ def capture_args((wrap_config, original_args), **circuit_options)
10
10
 
11
- original_flow_options[:stack] << Trailblazer::Activity::Trace::Entity.new( wrap_config[:task], :args, nil, {}, original_circuit_options[:introspect] )
11
+ original_args = capture_for(wrap_config[:task], *original_args)
12
12
 
13
- [ Trailblazer::Activity::Right, [wrap_config, original_args], **circuit_options ]
13
+ return Trailblazer::Activity::Right, [wrap_config, original_args], circuit_options
14
14
  end
15
15
 
16
- def self.capture_return((wrap_config, original_args), **circuit_options)
16
+ # taskWrap step to capture outgoing arguments from a step.
17
+ def capture_return((wrap_config, original_args), **circuit_options)
17
18
  (original_options, original_flow_options, _) = original_args[0]
18
19
 
19
- original_flow_options[:stack] << Trailblazer::Activity::Trace::Entity.new( wrap_config[:task], :return, wrap_config[:return_signal], {} )
20
+ original_flow_options[:stack] << Trailblazer::Activity::Trace::Entity.new(
21
+ wrap_config[:task], {}, :return, wrap_config[:return_signal]
22
+ )
20
23
 
21
24
  original_flow_options[:stack].unindent!
22
25
 
23
26
 
24
- [ Trailblazer::Activity::Right, [wrap_config, original_args], **circuit_options ]
27
+ return Trailblazer::Activity::Right, [wrap_config, original_args], circuit_options
28
+ end
29
+
30
+ # It's important to understand that {flow[:stack]} is mutated by design. This is needed so
31
+ # in case of exceptions we still have a "global" trace - unfortunately Ruby doesn't allow
32
+ # us a better way.
33
+ def capture_for(task, (ctx, flow), activity:, **circuit_options)
34
+ flow[:stack].indent!
35
+
36
+ flow[:stack] << Trailblazer::Activity::Trace::Entity.new(
37
+ task, activity, :args, nil, {}
38
+ )
39
+
40
+ return [ctx, flow], circuit_options.merge(activity: activity)
25
41
  end
26
42
  end
27
43
  end
@@ -24,29 +24,17 @@ module Trailblazer
24
24
  end
25
25
 
26
26
  # Compute runtime arguments necessary to execute a taskWrap per task of the activity.
27
- def self.arguments_for_call(activity, (options, flow_options), **circuit_options)
27
+ def self.invoke(activity, args, wrap_runtime: {}, **circuit_options)
28
28
  circuit_options = circuit_options.merge(
29
29
  runner: TaskWrap::Runner,
30
- wrap_runtime: circuit_options[:wrap_runtime] || {},
31
- wrap_static: activity[:wrap_static] || {},
32
- )
33
-
34
- return activity, [ options, flow_options ], circuit_options
35
- end
30
+ wrap_runtime: wrap_runtime,
36
31
 
37
- module NonStatic
38
- def self.arguments_for_call(activity, (options, flow_options), **circuit_options)
39
- circuit_options = circuit_options.merge(
40
- runner: TaskWrap::Runner,
41
- wrap_runtime: circuit_options[:wrap_runtime] || {}, # FIXME:this sucks. (was:) this overwrites wrap_runtime from outside.
42
- wrap_static: ::Hash.new(TaskWrap.initial_activity), # add a default static wrap.
43
- )
32
+ activity: { adds: [], circuit: {} }, # for Runner
33
+ )
44
34
 
45
- return activity, [ options, flow_options ], circuit_options
46
- end
35
+ # signal, (ctx, flow), circuit_options =
36
+ Runner.(activity, args, circuit_options)
47
37
  end
48
-
49
- # better: MyClass < Activity(TaskWrap, ...)
50
- end
38
+ end # TaskWrap
51
39
  end
52
40
  end
@@ -0,0 +1,32 @@
1
+ # DISCUSS: move to trailblazer-activity-test ?
2
+
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
15
+ end
16
+ end
17
+ end
18
+ end
19
+
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]
29
+ end
30
+ end.method(name)
31
+ end
32
+ end
@@ -22,13 +22,10 @@ module Trailblazer
22
22
  return activity, [ options, flow_options.merge(tracing_flow_options) ], circuit_options.merge(tracing_circuit_options)
23
23
  end
24
24
 
25
- def self.call(activity, (options, flow_options), *args)
26
- activity, (options, flow_options), circuit_options = Trace.arguments_for_call( activity, [options, flow_options], {} ) # only run once for the entire circuit!
25
+ def self.call(activity, (options, flow_options), circuit_options={})
26
+ activity, (options, flow_options), circuit_options = Trace.arguments_for_call( activity, [options, flow_options], circuit_options ) # only run once for the entire circuit!
27
27
  last_signal, (options, flow_options) =
28
- activity.(
29
- [options, flow_options],
30
- circuit_options.merge({ argumenter: [ Introspect.method(:arguments_for_call), TaskWrap.method(:arguments_for_call) ] })
31
- )
28
+ Activity::TaskWrap.invoke(activity, [options, flow_options], circuit_options)
32
29
 
33
30
  return flow_options[:stack].to_a, last_signal, [options, flow_options]
34
31
  end
@@ -48,7 +45,7 @@ module Trailblazer
48
45
  end
49
46
  end
50
47
 
51
- Entity = Struct.new(:task, :type, :value, :value2, :introspection)
48
+ Entity = Struct.new(:task, :activity, :type, :value, :value2)
52
49
 
53
50
  # Mutable/stateful per design. We want a (global) stack!
54
51
  class Stack
@@ -1,5 +1,5 @@
1
1
  module Trailblazer
2
2
  class Activity < Module
3
- VERSION = "0.5.2"
3
+ VERSION = "0.6.0"
4
4
  end
5
5
  end
@@ -1,10 +1,4 @@
1
- require "trailblazer/activity/version"
2
-
3
1
  module Trailblazer
4
- def self.Activity(implementation=Activity::Path, options={})
5
- Activity.new(implementation, state)
6
- end
7
-
8
2
  class Activity < Module
9
3
  attr_reader :initial_state
10
4
 
@@ -51,6 +45,12 @@ module Trailblazer
51
45
  end
52
46
  end
53
47
 
48
+ # Reader and writer method for DSL objects.
49
+ # The writer {dsl[:key] = "value"} exposes immutable behavior and will replace the old
50
+ # @state with a new, modified copy.
51
+ #
52
+ # Always use the DSL::Accessor accessors to avoid leaking state to other components
53
+ # due to mutable write operations.
54
54
  module Accessor
55
55
  def []=(*args)
56
56
  @state = State::Config.send(:[]=, @state, *args)
@@ -81,12 +81,12 @@ module Trailblazer
81
81
  require "trailblazer/activity/dsl/magnetic/merge"
82
82
  include Magnetic::Merge # Activity#merge!
83
83
 
84
- def call(args, argumenter: [], **circuit_options) # DISCUSS: the argumenter logic might be moved out.
85
- _, args, circuit_options = argumenter.inject( [self, args, circuit_options] ) { |memo, argumenter| argumenter.(*memo) }
86
-
87
- self[:circuit].( args, circuit_options.merge(argumenter: argumenter) )
84
+ # @private Note that {Activity.call} is considered private until the public API is stable.
85
+ def call(args, circuit_options={})
86
+ self[:circuit].( args, circuit_options.merge(activity: self) )
88
87
  end
89
88
  end
89
+
90
90
  end # Activity
91
91
  end
92
92
 
@@ -14,7 +14,7 @@ module Trailblazer
14
14
  #
15
15
  # This is the "pipeline operator"'s implementation.
16
16
  class Circuit
17
- def initialize(map, stop_events, name: nil, start_task: map.keys.first)
17
+ def initialize(map, stop_events, start_task:, name: nil)
18
18
  @map = map
19
19
  @stop_events = stop_events
20
20
  @name = name
@@ -36,8 +36,9 @@ module Trailblazer
36
36
  # @return [last_signal, options, flow_options, *args]
37
37
  #
38
38
  # NOTE: returned circuit_options are discarded when calling the runner.
39
- def call(args, task: @start_task, runner: Run, **circuit_options)
39
+ def call(args, start_task: @start_task, runner: Run, **circuit_options)
40
40
  circuit_options = circuit_options.merge( runner: runner ).freeze # TODO: set the :runner option via arguments_for_call to save the merge?
41
+ task = start_task
41
42
 
42
43
  loop do
43
44
  last_signal, args, _discarded_circuit_options = runner.(
@@ -49,35 +50,22 @@ module Trailblazer
49
50
  # Stop execution of the circuit when we hit a stop event (< End). This could be an task's End or Suspend.
50
51
  return [ last_signal, args ] if @stop_events.include?(task) # DISCUSS: return circuit_options here?
51
52
 
52
- task = next_for(task, last_signal) do |next_task, in_map|
53
- raise IllegalInputError.new("#{task}") unless in_map
54
- raise IllegalOutputSignalError.new("<#{@name}>[#{task}][ #{last_signal.inspect} ]") unless next_task
55
- end
53
+ task = next_for(task, last_signal) or raise IllegalSignalError.new("<#{@name}>[#{task}][ #{last_signal.inspect} ]")
56
54
  end
57
55
  end
58
56
 
59
57
  # Returns the circuit's components.
60
58
  def to_h
61
- { map: @map, end_events: @stop_events }
59
+ { map: @map, end_events: @stop_events, start_task: @start_task }
62
60
  end
63
61
 
64
62
  private
65
- def next_for(last_task, emitted_signal)
66
- # p @map
67
- in_map = false
68
- cfg = @map.keys.find { |t| t == last_task } and in_map = true
69
- cfg = @map[cfg] if cfg
70
- cfg ||= {}
71
- next_task = cfg[emitted_signal]
72
- yield next_task, in_map
73
-
74
- next_task
75
- end
76
-
77
- class IllegalInputError < RuntimeError
63
+ def next_for(last_task, signal)
64
+ outputs = @map[last_task]
65
+ outputs[signal]
78
66
  end
79
67
 
80
- class IllegalOutputSignalError < RuntimeError
68
+ class IllegalSignalError < RuntimeError
81
69
  end
82
70
  end
83
71
  end
@@ -1 +1,2 @@
1
+ require "trailblazer/activity/version"
1
2
  require "trailblazer/activity"
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.5.2
4
+ version: 0.6.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: 2018-03-23 00:00:00.000000000 Z
11
+ date: 2018-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hirb
@@ -111,7 +111,6 @@ files:
111
111
  - NOTES
112
112
  - README.md
113
113
  - Rakefile
114
- - inspeccccccccct.rb
115
114
  - lib/trailblazer-activity.rb
116
115
  - lib/trailblazer/activity.rb
117
116
  - lib/trailblazer/activity/config.rb
@@ -150,6 +149,7 @@ files:
150
149
  - lib/trailblazer/activity/task_wrap/merge.rb
151
150
  - lib/trailblazer/activity/task_wrap/runner.rb
152
151
  - lib/trailblazer/activity/task_wrap/trace.rb
152
+ - lib/trailblazer/activity/testing.rb
153
153
  - lib/trailblazer/activity/trace.rb
154
154
  - lib/trailblazer/activity/version.rb
155
155
  - lib/trailblazer/circuit.rb
data/inspeccccccccct.rb DELETED
@@ -1,19 +0,0 @@
1
-
2
- module Inspect
3
- def inspect
4
- "nice view!"
5
- end
6
- end
7
-
8
- module A
9
- extend Inspect
10
-
11
- def self.a
12
- end
13
- end
14
-
15
- # jruby 9.1.7
16
- puts A.method(:a).inspect #=> #<Method: A.a>
17
- # 2.5
18
- puts A.method(:a).inspect #=> #<Method: nice view!.a>
19
-