trailblazer-activity 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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