trailblazer-activity 0.2.1 → 0.3.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +4 -0
  3. data/NOTES_ +36 -0
  4. data/README.md +7 -7
  5. data/Rakefile +1 -1
  6. data/lib/trailblazer/activity.rb +137 -96
  7. data/lib/trailblazer/activity/heritage.rb +30 -0
  8. data/lib/trailblazer/activity/introspection.rb +105 -0
  9. data/lib/trailblazer/activity/magnetic.rb +47 -0
  10. data/lib/trailblazer/activity/magnetic/builder.rb +161 -0
  11. data/lib/trailblazer/activity/magnetic/builder/block.rb +37 -0
  12. data/lib/trailblazer/activity/magnetic/builder/fast_track.rb +141 -0
  13. data/lib/trailblazer/activity/magnetic/builder/path.rb +98 -0
  14. data/lib/trailblazer/activity/magnetic/builder/railway.rb +123 -0
  15. data/lib/trailblazer/activity/magnetic/dsl.rb +90 -0
  16. data/lib/trailblazer/activity/magnetic/dsl/alterations.rb +44 -0
  17. data/lib/trailblazer/activity/magnetic/dsl/plus_poles.rb +59 -0
  18. data/lib/trailblazer/activity/magnetic/finalizer.rb +55 -0
  19. data/lib/trailblazer/activity/magnetic/generate.rb +62 -0
  20. data/lib/trailblazer/activity/present.rb +12 -19
  21. data/lib/trailblazer/activity/process.rb +16 -0
  22. data/lib/trailblazer/activity/schema/dependencies.rb +41 -0
  23. data/lib/trailblazer/activity/schema/sequence.rb +46 -0
  24. data/lib/trailblazer/activity/structures.rb +41 -0
  25. data/lib/trailblazer/activity/subprocess.rb +9 -1
  26. data/lib/trailblazer/activity/trace.rb +25 -16
  27. data/lib/trailblazer/activity/version.rb +1 -1
  28. data/lib/trailblazer/activity/wrap.rb +4 -13
  29. data/lib/trailblazer/circuit.rb +4 -35
  30. data/lib/trailblazer/wrap/call_task.rb +2 -2
  31. data/lib/trailblazer/wrap/runner.rb +7 -1
  32. data/lib/trailblazer/wrap/trace.rb +6 -5
  33. metadata +21 -10
  34. data/lib/trailblazer/activity/graph.rb +0 -157
  35. data/lib/trailblazer/circuit/testing.rb +0 -58
  36. data/lib/trailblazer/container_chain.rb +0 -45
  37. data/lib/trailblazer/context.rb +0 -68
  38. data/lib/trailblazer/option.rb +0 -78
  39. data/lib/trailblazer/wrap/inject.rb +0 -32
  40. data/lib/trailblazer/wrap/variable_mapping.rb +0 -92
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 81fa1b9e57d3f63ee5ee4b5caadea44250fcdbad
4
- data.tar.gz: ba3e8fc06d51e146702ac7b6eb6765fe3af6e7ee
3
+ metadata.gz: 8acff4e1b4e5f3e922e9e368cf5885de56dcc2b5
4
+ data.tar.gz: 3a1a10d63d5cbe7236f0ffac21f4774add92954a
5
5
  SHA512:
6
- metadata.gz: 62291d8bbb37376be5e1c6548c35d7df00a7edf654697ac26ed786d55a2bdf49de6130ad0bcfa3a221ff1dbf30935f9ae6d7f2fbe3d143bd4ae42c52d106b110
7
- data.tar.gz: 3503a6d13258343169a5eddbcc99fa63a169f493dcde5e42962e03775b97c9f09bb9ed68aaa9891ee13a262539dda4051ac4b7e5895b3f01a93f3b4acdd08184
6
+ metadata.gz: 6e13c839f0ca6215fe069299de99ead0910557412fb24b40ecc4a1c9e25884108f105bdaa2cc6302a1ad96b83d425932d0cad0d1a2c5e016177eeb087e8278ea
7
+ data.tar.gz: f6a774594e754bb0c4ba4082b4a285421850e6d79450d00a5729d96c85c171627aa35dfc14fead7b47190069cebab893eb6ad57150cf10c186f63bd566842107
data/CHANGES.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 0.2.2
2
+
3
+ * Remove `Activity#end_events`.
4
+
1
5
  # 0.2.1
2
6
 
3
7
  * Restructure all `Wrap`-specific tasks.
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
@@ -67,12 +67,12 @@ Instead of using an operation, you can manually define activities by using the `
67
67
  ```ruby
68
68
  activity = Activity.from_hash do |start, _end|
69
69
  {
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 }
70
+ start => { Trailblazer::Activity::Right => Blog::Write },
71
+ Blog::Write => { Trailblazer::Activity::Right => Blog::SpellCheck },
72
+ Blog::SpellCheck => { Trailblazer::Activity::Right => Blog::Publish,
73
+ Trailblazer::Activity::Left => Blog::Correct },
74
+ Blog::Correct => { Trailblazer::Activity::Right => Blog::SpellCheck },
75
+ Blog::Publish => { Trailblazer::Activity::Right => _end }
76
76
  }
77
77
  end
78
78
  ```
@@ -89,7 +89,7 @@ my_options = {}
89
89
  last_signal, options, flow_options, _ = activity.( nil, my_options, {} )
90
90
  ```
91
91
 
92
- 1. The `start` event is `call`ed and per default returns the generic _signal_`Trailblazer::Circuit::Right`.
92
+ 1. The `start` event is `call`ed and per default returns the generic _signal_`Trailblazer::Activity::Right`.
93
93
  2. This emitted (or returned) signal is connected to the next task `Blog::Write`, which is now `call`ed.
94
94
  3. `Blog::Write` emits another `Right` signal that leads to `Blog::SpellCheck` being `call`ed.
95
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.
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require "rake/testtask"
4
4
  Rake::TestTask.new(:test) do |t|
5
5
  t.libs << "test"
6
6
  t.libs << "lib"
7
- t.test_files = FileList['test/**/*_test.rb']
7
+ t.test_files = FileList['test/dsl/*_test.rb', 'test/wrap/*_test.rb', "test/*_test.rb"]
8
8
  end
9
9
 
10
10
  task :default => :test
@@ -1,136 +1,177 @@
1
1
  require "trailblazer/circuit"
2
2
 
3
- # TODO: move to separate gem.
4
- require "trailblazer/option"
5
- require "trailblazer/context"
6
- require "trailblazer/container_chain"
7
-
8
3
  module Trailblazer
9
4
  class Activity
5
+ module Interface
6
+ def decompose # TODO: test me
7
+ @process.instance_variable_get(:@circuit).to_fields
8
+ end
9
+
10
+ def debug # TODO: TEST ME
11
+ @debug
12
+ end
13
+ end
10
14
 
11
- require "trailblazer/activity/version"
12
- require "trailblazer/activity/graph"
13
- require "trailblazer/activity/subprocess"
15
+ extend Interface
14
16
 
15
- require "trailblazer/activity/wrap"
16
- require "trailblazer/wrap/variable_mapping"
17
- require "trailblazer/wrap/call_task"
18
- require "trailblazer/wrap/trace"
19
- require "trailblazer/wrap/inject"
20
- require "trailblazer/wrap/runner"
17
+ require "trailblazer/activity/version"
18
+ require "trailblazer/activity/structures"
21
19
 
22
- require "trailblazer/activity/trace"
23
- require "trailblazer/activity/present"
20
+ require "trailblazer/activity/subprocess"
24
21
 
25
- # Only way to build an Activity.
26
- def self.from_wirings(wirings, &block)
27
- start_evt = Circuit::Start.new(:default)
28
- start_args = [ start_evt, { type: :event, id: "Start.default" } ]
22
+ require "trailblazer/activity/wrap"
23
+ require "trailblazer/wrap/call_task"
24
+ require "trailblazer/wrap/trace"
25
+ require "trailblazer/wrap/runner"
29
26
 
30
- start = block ? Graph::Start( *start_args, &block ) : Graph::Start(*start_args)
27
+ require "trailblazer/activity/trace"
28
+ require "trailblazer/activity/present"
31
29
 
32
- wirings.each do |wiring|
33
- start.send(*wiring)
34
- end
35
30
 
36
- new(start)
31
+ require "trailblazer/activity/magnetic" # the "magnetic" DSL
32
+ require "trailblazer/activity/schema/sequence"
33
+
34
+ require "trailblazer/activity/process"
35
+ require "trailblazer/activity/introspection"
36
+
37
+ require "trailblazer/activity/heritage"
38
+
39
+ def self.call(args, circuit_options={})
40
+ @process.( args, circuit_options )
37
41
  end
38
42
 
39
- # Build an activity from a hash.
40
- #
41
- # activity = Trailblazer::Activity.from_hash do |start, _end|
42
- # {
43
- # start => { Circuit::Right => Blog::Write },
44
- # Blog::Write => { Circuit::Right => Blog::SpellCheck },
45
- # Blog::SpellCheck => { Circuit::Right => Blog::Publish, Circuit::Left => Blog::Correct },
46
- # Blog::Correct => { Circuit::Right => Blog::SpellCheck },
47
- # Blog::Publish => { Circuit::Right => _end }
48
- # }
49
- # end
50
- def self.from_hash(end_evt=Circuit::End.new(:default), start_evt=Circuit::Start.new(:default), &block)
51
- hash = yield(start_evt, end_evt)
52
- graph = Graph::Start( start_evt, id: "Start.default" )
53
-
54
- hash.each do |source_task, connections|
55
- source = graph.find_all { |node| node[:_wrapped] == source_task }.first or raise "#{source_task} unknown"
56
-
57
- connections.each do |signal, task| # FIXME: id sucks
58
- if existing = graph.find_all { |node| node[:_wrapped] == task }.first
59
- graph.connect!( source: source[:id], target: existing, edge: [signal, {}] )
60
- else
61
- graph.attach!( source: source[:id], target: [task, id: task], edge: [signal, {}] )
62
- end
63
- end
64
- end
43
+ #- modelling
65
44
 
66
- new(graph)
45
+ # @private
46
+ # DISCUSS: #each instead?
47
+ # FIXME: move to Introspection
48
+ def self.find(&block)
49
+ @process.instance_variable_get(:@circuit).instance_variable_get(:@map).find(&block)
50
+ end
51
+
52
+ def self.outputs
53
+ @outputs
67
54
  end
68
55
 
69
- def self.merge(activity, wirings)
70
- graph = activity.graph
56
+ #- DSL part
71
57
 
72
- # TODO: move this to Graph
73
- # replace the old start node with the new one that's created in ::from_wirings.
74
- cloned_graph_ary = graph[:graph].collect { |node, connections| [ node, connections.clone ] }
75
- old_start_connections = cloned_graph_ary.delete_at(0)[1] # FIXME: what if some connection goes back to start?
58
+ def self.build(&block)
59
+ Class.new(self, &block)
60
+ end
76
61
 
77
- from_wirings(wirings) do |start_node, data|
78
- cloned_graph_ary.unshift [ start_node, old_start_connections ] # push new start node onto the graph.
62
+ private
79
63
 
80
- data[:graph] = ::Hash[cloned_graph_ary]
81
- end
64
+ def self.inherited(subclass)
65
+ super
66
+ subclass.initialize!(*subclass.config)
67
+ heritage.(subclass)
82
68
  end
83
69
 
84
- def initialize(graph)
85
- @graph = graph
86
- @default_start_event = graph[:_wrapped]
87
- @circuit = to_circuit( @graph ) # graph is an immutable object.
70
+ def self.initialize!(builder_class, normalizer)
71
+ initialize_activity_dsl!(builder_class, normalizer)
72
+ recompile_process!
88
73
  end
89
74
 
90
- def end_events
91
- outputs.keys
75
+ # builder is stateless, it's up to you to save @adds somewhere.
76
+ def self.initialize_activity_dsl!(builder_class, normalizer)
77
+ @builder, @adds = builder_class.for( normalizer ) # e.g. Path.for(...) which creates a Builder::Path instance.
78
+ @debug = {} # only @adds and @debug are mutable
92
79
  end
93
80
 
94
- # Returns a hash mapping the circuit {Event} to its meta data.
95
- #
96
- # activity.outputs #=> { #<End ..> => { role: :success }, #<End ..> => { role: :failure } }
97
- def outputs
98
- # DISCUSS: add more meta data?
99
- ::Hash[ graph.find_all { |node| graph.successors(node).size == 0 }.collect { |node| [ node[:_wrapped], { role: node[:role] } ] } ]
81
+ def self.recompile_process!
82
+ @process, @outputs = Recompile.( @adds )
100
83
  end
101
84
 
102
- def call(args, start_event: default_start_event, **circuit_options)
103
- @circuit.(
104
- args,
105
- circuit_options.merge( task: start_event) , # this passes :runner to the {Circuit}.
106
- )
85
+ def self.config # FIXME: the normalizer is the same we have in Builder::plan.
86
+ return Magnetic::Builder::Path, Magnetic::Builder::DefaultNormalizer.new(plus_poles: Magnetic::Builder::Path.default_plus_poles)
107
87
  end
108
88
 
109
- # @private
110
- attr_reader :circuit
111
- # @private
112
- attr_reader :graph
89
+ # DSL part
113
90
 
114
- private
91
+ # DISCUSS: make this functions and don't include?
92
+ module DSL
93
+
94
+ # Create a new method (e.g. Activity::step) that delegates to its builder, recompiles
95
+ # the process, etc. Method comes in a module so it can be overridden via modules.
96
+ #
97
+ # This approach assumes you maintain a @adds and a @debug instance variable. and #heritage
98
+ def self.def_dsl!(_name)
99
+ Module.new do
100
+ define_method(_name) do |*args, &block|
101
+ _task(_name, *args, &block) # TODO: similar to Block.
102
+ end
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ def _task(name, *args, &block)
109
+ heritage.record(name, *args, &block)
110
+
111
+ adds, *returned_options = @builder.send(name, *args, &block)
112
+
113
+ @adds += adds
114
+ @adds.freeze
115
115
 
116
- attr_reader :default_start_event
116
+ recompile_process!
117
+
118
+ add_introspection!(adds, *returned_options)
119
+
120
+ return adds, returned_options
121
+ end
122
+
123
+ def add_introspection!(adds, task, local_options, *)
124
+ @debug[task] = { id: local_options[:id] }.freeze
125
+ end
126
+ end
117
127
 
118
- def to_circuit(graph)
119
- Circuit.new(graph.to_h( include_leafs: false ), end_events, {})
128
+ # delegate as much as possible to Builder
129
+ # let us process options and e.g. do :id
130
+ class << self
131
+ extend Forwardable # TODO: test those helpers
132
+ def_delegators :@builder, :Path#, :task
120
133
  end
121
134
 
122
- class Introspection
123
- # @param activity Activity
124
- def initialize(activity)
125
- @activity = activity
126
- @graph = activity.graph
127
- @circuit = activity.circuit
135
+ extend DSL # _task, :add_introspection
136
+ extend DSL.def_dsl!(:task) # define Activity::task.
137
+
138
+ extend Heritage::Accessor
139
+
140
+
141
+ # MOVE ME TO ADDS
142
+ module Recompile
143
+ # Recompile the process and outputs from the {ADDS} instance that collects circuit tasks and connections.
144
+ def self.call(adds)
145
+ process, end_events = Magnetic::Builder::Finalizer.(adds)
146
+ outputs = recompile_outputs(end_events)
147
+
148
+ return process, outputs
128
149
  end
129
150
 
130
- # Find the node that wraps `task` or return nil.
131
- def [](task)
132
- @graph.find_all { |node| node[:_wrapped] == task }.first
151
+ private
152
+
153
+ def self.recompile_outputs(end_events)
154
+ ary = end_events.collect do |evt|
155
+ [
156
+ semantic = evt.instance_variable_get(:@options)[:semantic], # DISCUSS: better API here?
157
+ Activity::Output(evt, semantic)
158
+ ]
159
+ end
160
+
161
+ ::Hash[ ary ]
133
162
  end
134
163
  end
164
+
165
+ # TODO: hm
166
+ class Railway < Activity
167
+ def self.config # FIXME: the normalizer is the same we have in Builder::plan.
168
+ return Magnetic::Builder::Railway, Magnetic::Builder::DefaultNormalizer.new(plus_poles: Magnetic::Builder::Railway.default_plus_poles)
169
+ end
170
+
171
+ extend DSL
172
+ extend DSL.def_dsl!(:step)
173
+ extend DSL.def_dsl!(:fail)
174
+ extend DSL.def_dsl!(:pass)
175
+ end
135
176
  end
136
177
  end
@@ -0,0 +1,30 @@
1
+ module Trailblazer
2
+ # This is copied from the Declarative gem. This might get removed in favor of a real heritage gem.
3
+ class Activity
4
+ class Heritage < Array
5
+ # Record inheritable assignments for replay in an inheriting class.
6
+ def record(method, *args, &block)
7
+ self << { method: method, args: args, block: block }
8
+ end
9
+
10
+ # Replay the recorded assignments on inheritor.
11
+ # Accepts a block that will allow processing the arguments for every recorded statement.
12
+ def call(inheritor, &block)
13
+ each { |cfg| call!(inheritor, cfg, &block) }
14
+ end
15
+
16
+ private
17
+ def call!(inheritor, cfg)
18
+ yield cfg if block_given? # allow messing around with recorded arguments.
19
+
20
+ inheritor.send(cfg[:method], *cfg[:args], &cfg[:block])
21
+ end
22
+
23
+ module Accessor
24
+ def heritage
25
+ @heritage ||= Heritage.new
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,105 @@
1
+ module Trailblazer
2
+ class Activity
3
+ # Introspection is not used at run-time except for rendering diagrams, tracing, and the like.
4
+ module Introspect
5
+
6
+ def self.collect(activity, options={}, &block)
7
+ circuit_hash, _ = activity.decompose
8
+
9
+ locals = circuit_hash.collect do |task, connections|
10
+ [
11
+ yield(task, connections),
12
+ *options[:recursive] && task.is_a?(Activity::Interface) ? collect(task, options, &block) : []
13
+ ]
14
+ end.flatten(1)
15
+ end
16
+
17
+
18
+ # render
19
+ def self.Cct(process, **options)
20
+ circuit_hash( process.instance_variable_get(:@circuit).to_fields[0], **options )
21
+ end
22
+
23
+ def self.circuit_hash(circuit_hash, show_ids:false)
24
+ content =
25
+ circuit_hash.collect do |task, connections|
26
+ conns = connections.collect do |signal, target|
27
+ " {#{signal}} => #{Task(target)}"
28
+ end
29
+
30
+ [ Task(task), conns.join("\n") ]
31
+ end
32
+
33
+ content = content.join("\n")
34
+
35
+ return "\n#{content}" if show_ids
36
+ return "\n#{content}".gsub(/\d\d+/, "")
37
+ end
38
+
39
+ def self.Ends(process)
40
+ end_events = process.instance_variable_get(:@circuit).to_fields[1]
41
+ ends = end_events.collect { |evt| Task(evt) }.join(",")
42
+ "[#{ends}]".gsub(/\d\d+/, "")
43
+ end
44
+
45
+
46
+ def self.Outputs(outputs)
47
+ outputs.collect { |semantic, output| "#{semantic}=> (#{output.signal}, #{output.semantic})" }.
48
+ join("\n").gsub(/0x\w+/, "").gsub(/\d\d+/, "")
49
+ end
50
+
51
+ def self.Task(task)
52
+ return task.inspect unless task.kind_of?(Trailblazer::Activity::End)
53
+
54
+ class_name = strip(task.class)
55
+ name = task.instance_variable_get(:@name)
56
+ semantic = task.instance_variable_get(:@options)[:semantic]
57
+ "#<#{class_name}:#{name}/#{semantic.inspect}>"
58
+ end
59
+
60
+ def self.strip(string)
61
+ string.to_s.sub("Trailblazer::Activity::", "")
62
+ end
63
+ end #Introspect
64
+ end
65
+
66
+ module Activity::Magnetic
67
+ module Introspect
68
+ def self.seq(activity)
69
+ adds = activity.instance_variable_get(:@adds)
70
+ tripletts = Builder::Finalizer.adds_to_tripletts(adds)
71
+
72
+ Seq(tripletts)
73
+ end
74
+
75
+ def self.cct(builder)
76
+ adds = builder.instance_variable_get(:@adds)
77
+ process, _ = Builder::Finalizer.(adds)
78
+
79
+ Cct(process)
80
+ end
81
+
82
+ private
83
+
84
+ def self.Seq(sequence)
85
+ content =
86
+ sequence.collect do |(magnetic_to, task, plus_poles)|
87
+ pluses = plus_poles.collect { |plus_pole| PlusPole(plus_pole) }
88
+
89
+ %{#{magnetic_to.inspect} ==> #{Activity::Introspect.Task(task)}
90
+ #{pluses.empty? ? " []" : pluses.join("\n")}}
91
+ end.join("\n")
92
+
93
+ "\n#{content}\n".gsub(/\d\d+/, "")
94
+ end
95
+
96
+ def self.PlusPole(plus_pole)
97
+ signal = plus_pole.signal.to_s.sub("Trailblazer::Activity::", "")
98
+ semantic = plus_pole.send(:output).semantic
99
+ " (#{semantic})/#{signal} ==> #{plus_pole.color.inspect}"
100
+ end
101
+
102
+
103
+ end
104
+ end
105
+ end