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,7 +1,7 @@
1
1
  require "hirb"
2
2
 
3
3
  module Trailblazer
4
- class Activity < Module
4
+ class Activity
5
5
 
6
6
  # Task < Array
7
7
  # [ input, ..., output ]
@@ -11,19 +11,23 @@ module Trailblazer
11
11
  module Present
12
12
  module_function
13
13
 
14
- def call(stack, level=1, tree=[])
15
- tree(stack.to_a, level, tree)
14
+ def default_renderer(stack:, level:, input:, name:, **)
15
+ [ level, %{#{name}} ]
16
16
  end
17
17
 
18
- def tree(stack, level, tree)
19
- tree_for(stack, level, tree)
18
+ def call(stack, level: 1, tree: [], renderer: method(:default_renderer), **options)
19
+ tree(stack.to_a, level, tree: tree, renderer: renderer, **options)
20
+ end
21
+
22
+ def tree(stack, level, tree: tree, **options)
23
+ tree_for(stack, level, options.merge(tree: tree))
20
24
 
21
25
  Hirb::Console.format_output(tree, class: :tree, type: :directory)
22
26
  end
23
27
 
24
- def tree_for(stack, level, tree)
25
- stack.each do |task| # always a Stack::Task[input, ..., output]
26
- input, output, nested = input_output_nested_for_task(task)
28
+ def tree_for(stack, level, tree:, renderer: ,**options)
29
+ stack.each do |lvl| # always a Stack::Task[input, ..., output]
30
+ input, output, nested = Trace::Level.input_output_nested_for_level(lvl)
27
31
 
28
32
  task = input.task
29
33
 
@@ -32,45 +36,16 @@ module Trailblazer
32
36
  name = (node = graph.find { |node| node[:task] == task }) ? node[:id] : task
33
37
  name ||= task # FIXME: bullshit
34
38
 
35
- tree << [ level, name ]
39
+ tree << renderer.(stack: stack, level: level, input: input, name: name, **options)
36
40
 
37
41
  if nested.any? # nesting
38
- tree_for(nested, level + 1, tree)
42
+ tree_for(nested, level + 1, options.merge(tree: tree, renderer: renderer))
39
43
  end
40
44
 
41
45
  tree
42
46
  end
43
47
  end
44
48
 
45
- # DISCUSS: alternatively, we can have Task<input: output: data: >
46
- def input_output_nested_for_task(task)
47
- input = task[0]
48
- output = task[-1]
49
-
50
- output, nested = output.is_a?(Entity::Output) ? [output, task-[input, output]] : [nil, task[1..-1]]
51
-
52
- return input, output, nested
53
- end
54
-
55
- def to_name(debug_item)
56
- track = debug_item[2]
57
- klass = track.class == Class ? track : track.class
58
- color = color_map[klass]
59
-
60
- return debug_item[0].to_s unless color
61
- colorify(debug_item[0], color)
62
- end
63
-
64
- def to_options(debug_item)
65
- debug_item[4]
66
- end
67
-
68
-
69
-
70
- def colorify(string, color)
71
- "\e[#{color_table[color]}m#{string}\e[0m"
72
- end
73
-
74
49
  def color_map
75
50
  {
76
51
  Trailblazer::Activity::Start => :blue,
@@ -0,0 +1,13 @@
1
+ module Trailblazer
2
+ class Activity
3
+ NodeAttributes = Struct.new(:id, :outputs, :task, :data)
4
+
5
+ # Schema is primitive data structure + an invoker (usually coming from Activity etc)
6
+ class Schema < Struct.new(:circuit, :outputs, :nodes, :config)
7
+
8
+ # @!method to_h()
9
+ # Returns a hash containing the schema's components.
10
+
11
+ end # Schema
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ class Trailblazer::Activity
2
+ class Schema
3
+ module Implementation
4
+ # Implementation structures
5
+ Task = Struct.new(:circuit_task, :outputs, :extensions)
6
+
7
+ def self.Task(task, outputs, extensions=[]); Task.new(task, outputs, extensions) end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,94 @@
1
+ class Trailblazer::Activity
2
+ class Schema
3
+ # An {Intermediate} structure defines the *structure* of the circuit. It usually
4
+ # comes from a DSL or a visual editor.
5
+ class Intermediate < Struct.new(:wiring, :stop_task_ids, :start_task_ids)
6
+ TaskRef = Struct.new(:id, :data) # TODO: rename to NodeRef
7
+ Out = Struct.new(:semantic, :target)
8
+
9
+ def self.TaskRef(id, data={}); TaskRef.new(id, data) end
10
+ def self.Out(*args); Out.new(*args) end
11
+
12
+ # Compiles a {Schema} instance from an {intermediate} structure and
13
+ # the {implementation} object references.
14
+ #
15
+ # Intermediate structure, Implementation, calls extensions, passes {}config # TODO
16
+ def self.call(intermediate, implementation)
17
+ config_default = {wrap_static: Hash.new(TaskWrap.initial_wrap_static)}.freeze # DISCUSS: this really doesn't have to be here, but works for now and we want it in 99%.
18
+
19
+ circuit = circuit(intermediate, implementation)
20
+ nodes = node_attributes(intermediate, implementation)
21
+ outputs = outputs(intermediate.stop_task_ids, nodes)
22
+ config = config(implementation, config: config_default)
23
+ schema = Schema.new(circuit, outputs, nodes, config)
24
+ end
25
+
26
+ # From the intermediate "template" and the actual implementation, compile a {Circuit} instance.
27
+ def self.circuit(intermediate, implementation)
28
+ end_events = intermediate.stop_task_ids
29
+
30
+ wiring = Hash[
31
+ intermediate.wiring.collect do |task_ref, outs|
32
+ task = implementation.fetch(task_ref.id)
33
+
34
+ [
35
+ task.circuit_task,
36
+ end_events.include?(task_ref.id) ? {} : connections_for(outs, task.outputs, implementation)
37
+ ]
38
+ end
39
+ ]
40
+
41
+ Circuit.new(
42
+ wiring,
43
+ intermediate.stop_task_ids.collect { |id| implementation.fetch(id).circuit_task },
44
+ start_task: intermediate.start_task_ids.collect { |id| implementation.fetch(id).circuit_task }[0]
45
+ )
46
+ end
47
+
48
+ # Compute the connections for {circuit_task}.
49
+ def self.connections_for(outs, task_outputs, implementation)
50
+ Hash[
51
+ outs.collect { |required_out|
52
+ [
53
+ for_semantic(task_outputs, required_out.semantic).signal,
54
+ implementation.fetch(required_out.target).circuit_task
55
+ ]
56
+ }.compact
57
+ ]
58
+ end
59
+
60
+ def self.node_attributes(intermediate, implementation)
61
+ intermediate.wiring.collect do |task_ref, outputs| # id, Task{circuit_task, outputs}
62
+ task = implementation.fetch(task_ref.id)
63
+ data = task_ref[:data] # TODO: allow adding data from implementation.
64
+
65
+ NodeAttributes.new(task_ref.id, task.outputs, task.circuit_task, data)
66
+ end
67
+ end
68
+
69
+ # intermediate/implementation independent.
70
+ def self.outputs(stop_task_ids, nodes_attributes)
71
+ stop_task_ids.collect do |id|
72
+ # Grab the {outputs} of the stop nodes.
73
+ nodes_attributes.find { |node_attrs| id == node_attrs.id }.outputs
74
+ end.flatten(1)
75
+ end
76
+
77
+ # Invoke each task's extensions (usually coming from the DSL or some macro).
78
+ def self.config(implementation, config:)
79
+ implementation.each do |id, task|
80
+ task.extensions.each { |ext| config = ext.(config: config, id: id, task: task) } # DISCUSS: ext must return new {Config}.
81
+ end
82
+
83
+ config
84
+ end
85
+
86
+ private
87
+
88
+ # Apply to any array.
89
+ def self.for_semantic(outputs, semantic)
90
+ outputs.find { |out| out.semantic == semantic } or raise "`#{semantic}` not found"
91
+ end
92
+ end # Intermediate
93
+ end
94
+ end
@@ -1,57 +1,57 @@
1
- module Trailblazer
2
- class Activity < Module
3
- # Generic run-time structures that are built via the DSL.
1
+ module Trailblazer
2
+ class Activity
3
+ # Generic run-time structures that are built via the DSL.
4
4
 
5
- # Builds an {Activity::End} instance.
6
- def self.End(semantic)
7
- End.new(semantic: semantic)
8
- end
9
-
10
- # Any instance of subclass of End will halt the circuit's execution when hit.
5
+ # Builds an {Activity::End} instance.
6
+ def self.End(semantic)
7
+ End.new(semantic: semantic)
8
+ end
11
9
 
12
- # An End event is a simple structure typically found as the last task invoked
13
- # in an activity. The special behavior is that it
14
- # a) maintains a semantic that is used to further connect that very event
15
- # b) its `End#call` method returns the end instance itself as the signal.
16
- class End
17
- def initialize(semantic:, **options)
18
- @options = options.merge(semantic: semantic)
19
- end
10
+ # Any instance of subclass of End will halt the circuit's execution when hit.
20
11
 
21
- def call(args, circuit_options)
22
- return self, args, circuit_options
23
- end
12
+ # An End event is a simple structure typically found as the last task invoked
13
+ # in an activity. The special behavior is that it
14
+ # a) maintains a semantic that is used to further connect that very event
15
+ # b) its `End#call` method returns the end instance itself as the signal.
16
+ class End
17
+ def initialize(semantic:, **options)
18
+ @options = options.merge(semantic: semantic)
19
+ end
24
20
 
25
- def to_h
26
- @options
27
- end
21
+ def call(args, circuit_options)
22
+ return self, args, circuit_options
23
+ end
28
24
 
29
- def to_s
30
- %{#<#{self.class.name} #{@options.collect{ |k,v| "#{k}=#{v.inspect}" }.join(" ")}>}
31
- end
25
+ def to_h
26
+ @options
27
+ end
32
28
 
33
- alias_method :inspect, :to_s
29
+ def to_s
30
+ %{#<#{self.class.name} #{@options.collect{ |k,v| "#{k}=#{v.inspect}" }.join(" ")}>}
34
31
  end
35
32
 
36
- class Start < End
37
- def call(args, circuit_options)
38
- return Activity::Right, args, circuit_options
39
- end
33
+ alias_method :inspect, :to_s
34
+ end
35
+
36
+ class Start < End
37
+ def call(args, circuit_options)
38
+ return Activity::Right, args, circuit_options
40
39
  end
40
+ end
41
41
 
42
- class Signal; end
43
- class Right < Signal; end
44
- class Left < Signal; end
42
+ class Signal; end
43
+ class Right < Signal; end
44
+ class Left < Signal; end
45
45
 
46
- # signal: actual signal emitted by the task
47
- # color: the mapping, where this signal will travel to. This can be e.g. Left=>:success. The polarization when building the graph.
48
- # "i am traveling towards :success because ::step said so!"
49
- # semantic: the original "semantic" or role of the signal, such as :success. This usually comes from the activity hosting this output.
50
- Output = Struct.new(:signal, :semantic)
46
+ # signal: actual signal emitted by the task
47
+ # color: the mapping, where this signal will travel to. This can be e.g. Left=>:success. The polarization when building the graph.
48
+ # "i am traveling towards :success because ::step said so!"
49
+ # semantic: the original "semantic" or role of the signal, such as :success. This usually comes from the activity hosting this output.
50
+ Output = Struct.new(:signal, :semantic)
51
51
 
52
- # Builds an {Activity::Output} instance.
53
- def self.Output(signal, color)
54
- Output.new(signal, color).freeze
55
- end
52
+ # Builds an {Activity::Output} instance.
53
+ def self.Output(signal, semantic)
54
+ Output.new(signal, semantic).freeze
56
55
  end
57
56
  end
57
+ end
@@ -1,5 +1,5 @@
1
1
  module Trailblazer
2
- class Activity < Module
2
+ class Activity
3
3
  #
4
4
  # Example with tracing:
5
5
  #
@@ -10,31 +10,44 @@ module Trailblazer
10
10
  # |-- Trace.capture_return [optional]
11
11
  # |-- Wrap::End
12
12
  module TaskWrap
13
- # The actual activity that implements the taskWrap.
14
- def self.initial_activity
15
- Module.new do
16
- extend Trailblazer::Activity::Path(
17
- name: "taskWrap",
18
- normalizer_class: Magnetic::DefaultNormalizer,
19
- plus_poles: Magnetic::PlusPoles.initial( :success => Magnetic::Builder::Path.default_outputs[:success] ) # DefaultNormalizer doesn't give us default PlusPoles.
20
- )
21
-
22
- task TaskWrap.method(:call_task), id: "task_wrap.call_task" # ::call_task is defined in task_wrap/call_task.
23
- end
24
- end
13
+ module_function
25
14
 
26
15
  # Compute runtime arguments necessary to execute a taskWrap per task of the activity.
27
- def self.invoke(activity, args, wrap_runtime: {}, **circuit_options)
16
+ def invoke(activity, args, wrap_runtime: {}, **circuit_options)
28
17
  circuit_options = circuit_options.merge(
29
18
  runner: TaskWrap::Runner,
30
19
  wrap_runtime: wrap_runtime,
31
-
32
- activity: { adds: [], circuit: {} }, # for Runner
20
+ activity: {wrap_static: {activity => initial_wrap_static}, nodes: {}}, # for Runner. Ideally we'd have a list of all static_wraps here (even nested).
33
21
  )
34
22
 
35
23
  # signal, (ctx, flow), circuit_options =
36
24
  Runner.(activity, args, circuit_options)
37
25
  end
26
+
27
+ # {:extension} API
28
+ # Extend the static taskWrap from a macro or DSL call.
29
+ # Gets executed in {Intermediate.call} which also provides {config}.
30
+
31
+ def initial_wrap_static(*)
32
+ initial_sequence = TaskWrap::Pipeline.new([["task_wrap.call_task", TaskWrap.method(:call_task)]])
33
+ end
34
+
35
+ # Use this in your macros if you want to extend the {taskWrap}.
36
+ def Extension(merge:)
37
+ Extension.new(merge: Pipeline::Merge.new(*merge))
38
+ end
39
+
40
+ class Extension
41
+ def initialize(merge:)
42
+ @merge = merge
43
+ end
44
+
45
+ def call(config:, task:, **)
46
+ before_pipe = State::Config.get(config, :wrap_static, task.circuit_task)
47
+
48
+ State::Config.set(config, :wrap_static, task.circuit_task, @merge.(before_pipe))
49
+ end
50
+ end
38
51
  end # TaskWrap
39
52
  end
40
53
  end
@@ -1,10 +1,10 @@
1
- class Trailblazer::Activity < Module
1
+ class Trailblazer::Activity
2
2
  module TaskWrap
3
3
  # TaskWrap step that calls the actual wrapped task and passes all `original_args` to it.
4
4
  #
5
5
  # It writes to wrap_ctx[:return_signal], wrap_ctx[:return_args]
6
- def self.call_task((wrap_ctx, original_args), **circuit_options)
7
- task = wrap_ctx[:task]
6
+ def self.call_task(wrap_ctx, original_args)
7
+ task = wrap_ctx[:task]
8
8
 
9
9
  # Call the actual task we're wrapping here.
10
10
  # puts "~~~~wrap.call: #{task}"
@@ -13,7 +13,7 @@ class Trailblazer::Activity < Module
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 )
15
15
 
16
- return Right, [ wrap_ctx, original_args ]
16
+ return wrap_ctx, original_args
17
17
  end
18
18
  end # Wrap
19
19
  end
@@ -0,0 +1,37 @@
1
+ class Trailblazer::Activity
2
+ module TaskWrap
3
+ # Allows to inject attributes for a task and defaults them if not.
4
+ # Per default, the defaulting is scoped, meaning only the task will see it.
5
+ module Inject
6
+ module Defaults
7
+ module_function
8
+
9
+ def Extension(defaults)
10
+ # Returns new ctx.
11
+ input = ->(original_ctx) do
12
+ defaulted_options = defaults_for(defaults, original_ctx)
13
+
14
+ Trailblazer.Context(original_ctx.merge(defaulted_options))
15
+ end
16
+
17
+ output = ->(original_ctx, new_ctx) { # FIXME: use Unscope
18
+ _, mutable_data = new_ctx.decompose
19
+
20
+ # we are only interested in the {mutable_data} part since the disposed part
21
+ # represents the injected/defaulted data.
22
+ original_ctx.merge(mutable_data)
23
+ }
24
+
25
+ VariableMapping::Extension(input, output, id: input)
26
+ end
27
+
28
+ # go through all defaultable options and default them if appropriate.
29
+ def defaults_for(defaults, original_ctx)
30
+ Hash[
31
+ defaults.collect { |k, v| [k, original_ctx[k] || v] } # FIXME: doesn't allow {false/nil} currently.
32
+ ]
33
+ end
34
+ end
35
+ end # Inject
36
+ end
37
+ end
@@ -0,0 +1,55 @@
1
+ class Trailblazer::Activity
2
+ module TaskWrap
3
+ # This "circuit" is optimized for
4
+ # a) merging speed at run-time, since features like tracing will be applied here.
5
+ # b) execution speed. Every task in the real circuit is wrapped with one of us.
6
+ class Pipeline
7
+ def initialize(sequence)
8
+ @sequence = sequence # [[id, task], ..]
9
+ end
10
+
11
+ def call(wrap_ctx, original_args)
12
+ @sequence.each { |(id, task)| wrap_ctx, original_args = task.(wrap_ctx, original_args) }
13
+
14
+ return wrap_ctx, original_args
15
+ end
16
+
17
+ attr_reader :sequence
18
+
19
+ def self.insert_before(pipe, before_id, insertion)
20
+ index = pipe.sequence.find_index { |(id, _)| id == before_id }
21
+
22
+ seq = pipe.sequence.dup
23
+
24
+ Pipeline.new(seq.insert(index, insertion))
25
+ end
26
+
27
+ def self.insert_after(pipe, after_id, insertion)
28
+ index = pipe.sequence.find_index { |(id, _)| id == after_id }
29
+
30
+ seq = pipe.sequence.dup
31
+
32
+ Pipeline.new(seq.insert(index+1, insertion))
33
+ end
34
+
35
+ def self.append(pipe, _, insertion) # TODO: test me.
36
+ Pipeline.new(pipe.sequence + [insertion])
37
+ end
38
+
39
+ # Merges {extension_rows} into the {task_wrap_pipeline}.
40
+ # This is usually used in step extensions or at runtime for {wrap_runtime}.
41
+ #
42
+ # {Extension} API
43
+ class Merge # TODO: RENAME TO TaskWrap::Extension(::Merge)
44
+ def initialize(*extension_rows)
45
+ @extension_rows = extension_rows
46
+ end
47
+
48
+ def call(task_wrap_pipeline)
49
+ @extension_rows.collect { |(insert_function, target_id, row)| task_wrap_pipeline = insert_function.(task_wrap_pipeline, target_id, row) }
50
+ task_wrap_pipeline
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end