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,8 +1,9 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
+ - 2.6.0
4
5
  - 2.5.1
5
6
  - 2.4.4
6
- - 2.3.7
7
- - 2.2.10
7
+ # - 2.3.7
8
+ # - 2.2.10
8
9
  before_install: gem install bundler
data/CHANGES.md CHANGED
@@ -1,3 +1,13 @@
1
+ # 0.8.0
2
+
3
+ * Separate the [DSL](https://github.com/trailblazer/trailblazer-activity-dsl-linear) from the runtime code. The latter sits in this gem.
4
+ * Separate the runtime {Activity} from its compilation, which happens through {Intermediate} (the structure) and {Implementation} (the runtime implementation) now.
5
+ * Introduce {Pipeline} which is a simpler and much fast type of activity, mostly for the taskWrap.
6
+
7
+ # 0.7.2
8
+
9
+ * When recording DSL calls, use the `object_id` as key, so cloned methods are considered as different recordings.
10
+
1
11
  # 0.7.1
2
12
 
3
13
  * Alias `Trace.call` to `Trace.invoke` for consistency.
data/Gemfile CHANGED
@@ -1,12 +1,13 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in workflow.gemspec
4
4
  gemspec
5
5
 
6
- gem "minitest-line"
7
6
  gem "benchmark-ips"
7
+ gem "minitest-line"
8
8
 
9
9
  gem "rubocop", require: false
10
10
 
11
- gem "trailblazer-test", git: "https://github.com/trailblazer/trailblazer-test"
12
-
11
+ # gem "trailblazer-context", path: "../trailblazer-context"
12
+ gem "trailblazer-developer", path: "../trailblazer-developer"
13
+ # gem "trailblazer-developer", github: "trailblazer/trailblazer-developer", branch: "exception-tracing"
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Activity
2
2
 
3
+ implements Intermediate, Implementation, compiler and `Activity::Implementation`, Activity::Interface
4
+
3
5
  The `activity` gem brings a light-weight DSL to define business processes, and the runtime logic to run those activities.
4
6
 
5
7
  A process is a set of arbitrary pieces of logic you define, chained together and put into a meaningful context by an activity. Activity lets you focus on the implementation of steps while Trailblazer takes care of the control flow.
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require "rubocop/rake_task"
5
5
  Rake::TestTask.new(:test) do |t|
6
6
  t.libs << "test"
7
7
  t.libs << "lib"
8
- t.test_files = FileList["**/*_test.rb"]
8
+ t.test_files = FileList["**/*_test.rb"] - FileList["test/docs/*"]
9
9
  end
10
10
 
11
11
  RuboCop::RakeTask.new
@@ -1,123 +1,60 @@
1
1
  module Trailblazer
2
- class Activity < Module
3
- attr_reader :initial_state
2
+ # This is DSL-independent code, focusing only on run-time.
3
+ class Activity
4
+ # include Activity::Interface # TODO
4
5
 
5
- def initialize(implementation, options)
6
- builder, adds, circuit, outputs, options = BuildState.build_state_for( implementation.config, options)
7
-
8
- @initial_state = State::Config.build(
9
- builder: builder,
10
- options: options,
11
- adds: adds,
12
- circuit: circuit,
13
- outputs: outputs,
14
- )
15
-
16
- include *options[:extend] # include the DSL methods.
17
- include PublicAPI
18
- end
19
-
20
- # Injects the initial configuration into the module defining a new activity.
21
- def extended(extended)
22
- super
23
- extended.instance_variable_set(:@state, initial_state)
24
- end
25
-
26
-
27
- module Inspect
28
- def inspect
29
- "#<Trailblazer::Activity: {#{name || self[:options][:name]}}>"
30
- end
31
-
32
- alias_method :to_s, :inspect
6
+ def initialize(schema)
7
+ @schema = schema
33
8
  end
34
9
 
35
-
36
- require "trailblazer/activity/dsl/helper"
37
- # Helpers such as Path, Output, End to be included into {Activity}.
38
- module DSLHelper
39
- extend Forwardable
40
- def_delegators :@builder, :Path
41
- def_delegators DSL, :Output, :End, :Subprocess, :Track
42
-
43
- def Path(*args, &block)
44
- self[:builder].Path(*args, &block)
45
- end
10
+ def call(args, circuit_options={})
11
+ @schema[:circuit].(
12
+ args,
13
+ circuit_options.merge(activity: self)
14
+ )
46
15
  end
47
16
 
48
- # Reader and writer method for DSL objects.
17
+ # Reader and writer method for an Activity.
49
18
  # The writer {dsl[:key] = "value"} exposes immutable behavior and will replace the old
50
19
  # @state with a new, modified copy.
51
20
  #
52
- # Always use the DSL::Accessor accessors to avoid leaking state to other components
21
+ # Always use the accessors to avoid leaking state to other components
53
22
  # due to mutable write operations.
54
- module Accessor
55
- def []=(*args)
56
- @state = State::Config.send(:[]=, @state, *args)
57
- end
58
-
59
- def [](*args)
60
- State::Config[@state, *args]
61
- end
23
+ def [](*key)
24
+ @schema[:config][*key]
62
25
  end
63
26
 
64
- # FIXME: still to be decided
65
- # By including those modules, we create instance methods.
66
- # Later, this module is `extended` in Path, Railway and FastTrack, and
67
- # imports the DSL methods as class methods.
68
- module PublicAPI
69
- include Accessor
70
-
71
- require "trailblazer/activity/dsl/add_task"
72
- include DSL::AddTask
73
-
74
- require "trailblazer/activity/interface"
75
- include Activity::Interface # DISCUSS
76
-
77
- include DSLHelper # DISCUSS
78
-
79
- include Activity::Inspect # DISCUSS
80
-
81
- require "trailblazer/activity/dsl/magnetic/merge"
82
- include Magnetic::Merge # Activity#merge!
83
-
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) )
87
- end
27
+ def to_h
28
+ @schema
88
29
  end
89
30
 
31
+ def inspect
32
+ %{#<Trailblazer::Activity:0x#{object_id}>}
33
+ end
90
34
  end # Activity
91
35
  end
92
36
 
93
- require "trailblazer/circuit"
37
+ # require "trailblazer/activity/interface"
94
38
  require "trailblazer/activity/structures"
39
+ require "trailblazer/activity/schema"
40
+ require "trailblazer/activity/schema/implementation"
41
+ require "trailblazer/activity/schema/intermediate"
42
+ require "trailblazer/activity/circuit"
95
43
  require "trailblazer/activity/config"
96
44
 
97
- require "trailblazer/activity/dsl/strategy/build_state"
98
- require "trailblazer/activity/dsl/strategy/path"
99
- require "trailblazer/activity/dsl/strategy/plan"
100
- require "trailblazer/activity/dsl/strategy/railway"
101
- require "trailblazer/activity/dsl/strategy/fast_track"
102
-
103
45
  require "trailblazer/activity/task_wrap"
46
+ require "trailblazer/activity/task_wrap/pipeline"
104
47
  require "trailblazer/activity/task_wrap/call_task"
105
- require "trailblazer/activity/task_wrap/trace"
106
48
  require "trailblazer/activity/task_wrap/runner"
107
- require "trailblazer/activity/task_wrap/merge"
108
49
  require "trailblazer/activity/task_wrap/variable_mapping"
50
+ require "trailblazer/activity/task_wrap/inject"
109
51
 
110
52
  require "trailblazer/activity/trace"
111
53
  require "trailblazer/activity/present"
112
54
 
113
55
  require "trailblazer/activity/introspect"
56
+ require "trailblazer/option"
57
+ require "trailblazer/context"
58
+ require "trailblazer/activity/task_builder"
114
59
 
115
- require "trailblazer/activity/dsl/magnetic/builder/state"
116
- require "trailblazer/activity/dsl/magnetic" # the "magnetic" DSL
117
-
118
- require "trailblazer/activity/dsl/schema/sequence"
119
- require "trailblazer/activity/dsl/schema/dependencies"
120
-
121
- require "trailblazer/activity/dsl/magnetic/builder/normalizer" # DISCUSS: name and location are odd. This one uses Activity ;)
122
60
 
123
- require "trailblazer/activity/dsl/record"
@@ -0,0 +1,74 @@
1
+ module Trailblazer
2
+ class Activity
3
+ # Running a Circuit instance will run all tasks sequentially depending on the former's result.
4
+ # Each task is called and retrieves the former task's return values.
5
+ #
6
+ # Note: Please use #Activity as a public circuit builder.
7
+ #
8
+ # @param map [Hash] Defines the wiring.
9
+ # @param stop_events [Array] Tasks that stop execution of the circuit.
10
+ #
11
+ # result = circuit.(start_at, *args)
12
+ #
13
+ # @see Activity
14
+ # @api semi-private
15
+ #
16
+ # This is the "pipeline operator"'s implementation.
17
+ class Circuit
18
+ def initialize(map, stop_events, start_task:, name: nil)
19
+ @map = map
20
+ @stop_events = stop_events
21
+ @name = name
22
+ @start_task = start_task
23
+ end
24
+
25
+ # @param args [Array] all arguments to be passed to the task's `call`
26
+ # @param task [callable] task to call
27
+ Run = ->(task, args, **circuit_options) { task.(args, **circuit_options) }
28
+
29
+ # Runs the circuit until we hit a stop event.
30
+ #
31
+ # This method throws exceptions when the returned value of a task doesn't match
32
+ # any wiring.
33
+ #
34
+ # @param task An event or task of this circuit from where to start
35
+ # @param options anything you want to pass to the first task
36
+ # @param flow_options Library-specific flow control data
37
+ # @return [last_signal, options, flow_options, *args]
38
+ #
39
+ # NOTE: returned circuit_options are discarded when calling the runner.
40
+ def call(args, start_task: @start_task, runner: Run, **circuit_options)
41
+ circuit_options = circuit_options.merge( runner: runner ).freeze # TODO: set the :runner option via arguments_for_call to save the merge?
42
+ task = start_task
43
+
44
+ loop do
45
+ last_signal, args, _discarded_circuit_options = runner.(
46
+ task,
47
+ args,
48
+ circuit_options
49
+ )
50
+
51
+ # Stop execution of the circuit when we hit a stop event (< End). This could be an task's End or Suspend.
52
+ return [ last_signal, args ] if @stop_events.include?(task) # DISCUSS: return circuit_options here?
53
+
54
+ task = next_for(task, last_signal) or raise IllegalSignalError.new("<#{@name}>[#{task}][ #{last_signal.inspect} ]")
55
+ end
56
+ end
57
+
58
+ # Returns the circuit's components.
59
+ def to_h
60
+ { map: @map, end_events: @stop_events, start_task: @start_task }
61
+ end
62
+
63
+ private
64
+
65
+ def next_for(last_task, signal)
66
+ outputs = @map[last_task]
67
+ outputs[signal]
68
+ end
69
+
70
+ class IllegalSignalError < RuntimeError
71
+ end
72
+ end
73
+ end
74
+ end
@@ -3,12 +3,10 @@ module Trailblazer
3
3
  # Compile-time
4
4
  #
5
5
  # DISCUSS: we could replace parts with Hamster::Hash.
6
- class Config
7
- def self.build(variables={})
8
- Hash[ variables.collect { |k,v| [k, v.freeze] } ].freeze
9
- end
6
+ module Config
7
+ module_function
10
8
 
11
- def self.[]=(state, *args)
9
+ def set(state, *args)
12
10
  if args.size == 2
13
11
  key, value = *args
14
12
 
@@ -25,7 +23,7 @@ module Trailblazer
25
23
  state
26
24
  end
27
25
 
28
- def self.[](state, *args)
26
+ def get(state, *args)
29
27
  directive, key = *args
30
28
 
31
29
  return state[directive] if args.size == 1
@@ -1,18 +1,22 @@
1
1
  module Trailblazer
2
- class Activity < Module
3
- # Compile-time.
4
- # Introspection is not used at run-time except for rendering diagrams, tracing, and the like.
2
+ class Activity
3
+ # The Introspect API abstracts internals about circuits and provides a convenient API
4
+ # to third-parties such as tracing, rendering activities, etc.
5
5
  module Introspect
6
6
  def self.Graph(*args)
7
7
  Graph.new(*args)
8
8
  end
9
9
 
10
+ # TODO: order of step/fail/pass in Node would be cool to have
11
+
10
12
  # @private This API is still under construction.
11
13
  class Graph
12
14
  def initialize(activity)
13
15
  @activity = activity
14
- @circuit = activity.to_h[:circuit]
15
- @adds = activity.to_h[:adds].compact # FIXME: why are there nils in Adds?
16
+ @schema = activity.to_h or raise
17
+ @circuit = @schema[:circuit]
18
+ @map = @circuit.to_h[:map]
19
+ @configs = @schema[:nodes]
16
20
  end
17
21
 
18
22
  def find(id=nil, &block)
@@ -20,148 +24,48 @@ module Trailblazer
20
24
  find_with_block(&block)
21
25
  end
22
26
 
27
+ def collect(strategy: :circuit, &block)
28
+ @map.keys.each_with_index.collect { |task, i| yield find_with_block { |node| node.task==task }, i }
29
+ end
30
+
31
+ def stop_events
32
+ @circuit.to_h[:end_events]
33
+ end
34
+
23
35
  private
24
36
 
25
37
  def find_by_id(id)
26
- (_, (id, triplett)) = @adds.find { |(op, (_id, triplett))| _id == id }
27
-
28
- Node(triplett[1], id, triplett[0])
38
+ node = @configs.find { |node| node.id == id } or return
39
+ node_for(node)
29
40
  end
30
41
 
31
42
  def find_with_block(&block)
32
- adds = @adds.find { |(op, (id, triplett))| yield( Node(triplett[1], id, triplett[0]) ) }
33
- return nil unless adds
43
+ existing = @configs.find { |node| yield Node(node.task, node.id, node.outputs, node.data) } or return
34
44
 
35
- (op, (id, triplett)) = adds
45
+ node_for(existing)
46
+ end
36
47
 
37
- Node(triplett[1], id, triplett[0])
48
+ def node_for(node_attributes)
49
+ Node(node_attributes.task, node_attributes.id, node_attributes.outputs, outgoings_for(node_attributes), node_attributes.data)
38
50
  end
39
51
 
40
52
  def Node(*args)
41
53
  Node.new(*args).freeze
42
54
  end
43
55
 
44
- Node = Struct.new(:task, :id, :magnetic_to)
45
- end
46
-
56
+ Node = Struct.new(:task, :id, :outputs, :outgoings, :data)
57
+ Outgoing = Struct.new(:output, :task)
47
58
 
48
- # FIXME: remove this
49
- # @private This will be removed shortly.
50
- def self.collect(activity, options={}, &block)
51
- circuit = activity.to_h[:circuit]
52
- circuit_hash = circuit.to_h[:map]
53
-
54
- locals = circuit_hash.collect do |task, connections|
55
- [
56
- yield(task, connections),
57
- *options[:recursive] && task.is_a?(Activity::Interface) ? collect(task, options, &block) : []
58
- ]
59
- end.flatten(1)
60
- end
59
+ def outgoings_for(node)
60
+ outputs = node.outputs
61
+ connections = @map[node.task]
61
62
 
62
-
63
- def self.inspect_task_builder(task)
64
- proc = task.instance_variable_get(:@user_proc)
65
- match = proc.inspect.match(/(\w+)>$/)
66
-
67
- %{#<TaskBuilder{.#{match[1]}}>}
68
- end
69
-
70
- # FIXME: clean up that shit below.
71
-
72
- # render
73
- def self.Cct(circuit, **options)
74
- circuit_hash( circuit.to_h[:map], **options )
75
- end
76
-
77
- def self.circuit_hash(circuit_hash, show_ids:false, **options)
78
- content =
79
- circuit_hash.collect do |task, connections|
80
- conns = connections.collect do |signal, target|
81
- " {#{signal}} => #{inspect_with_matcher(target, **options)}"
82
- end
83
-
84
- [ inspect_with_matcher(task, **options), conns.join("\n") ]
63
+ connections.collect do |signal, target|
64
+ output = outputs.find { |out| out.signal == signal }
65
+ Outgoing.new(output, target)
85
66
  end
86
-
87
- content = content.join("\n")
88
-
89
- return "\n#{content}" if show_ids
90
- return "\n#{content}".gsub(/0x\w+/, "0x").gsub(/0.\d+/, "0.")
91
- end
92
-
93
- def self.Ends(activity)
94
- end_events = activity.to_h[:end_events]
95
- ends = end_events.collect { |evt| inspect_end(evt) }.join(",")
96
- "[#{ends}]".gsub(/\d\d+/, "")
97
- end
98
-
99
-
100
- def self.Outputs(outputs)
101
- outputs.collect { |semantic, output| "#{semantic}=> (#{output.signal}, #{output.semantic})" }.
102
- join("\n").gsub(/0x\w+/, "").gsub(/\d\d+/, "")
103
- end
104
-
105
- # If Ruby had pattern matching, this function wasn't necessary.
106
- def self.inspect_with_matcher(task, inspect_task: method(:inspect_task), inspect_end: method(:inspect_end))
107
- return inspect_task.(task) unless task.kind_of?(Trailblazer::Activity::End)
108
- inspect_end.(task)
109
- end
110
-
111
- def self.inspect_task(task)
112
- task.inspect
113
- end
114
-
115
- def self.inspect_end(task)
116
- class_name = strip(task.class)
117
- options = task.to_h
118
-
119
- "#<#{class_name}/#{options[:semantic].inspect}>"
120
- end
121
-
122
- def self.strip(string)
123
- string.to_s.sub("Trailblazer::Activity::", "")
67
+ end
124
68
  end
125
69
  end #Introspect
126
70
  end
127
-
128
- module Activity::Magnetic
129
- module Introspect
130
- def self.seq(activity)
131
- adds = activity.instance_variable_get(:@adds)
132
- tripletts = Builder::Finalizer.adds_to_tripletts(adds)
133
-
134
- Seq(tripletts)
135
- end
136
-
137
- def self.cct(builder)
138
- adds = builder.instance_variable_get(:@adds)
139
- circuit, _ = Builder::Finalizer.(adds)
140
-
141
- Cct(circuit)
142
- end
143
-
144
- private
145
-
146
- def self.Seq(sequence)
147
- content =
148
- sequence.collect do |(magnetic_to, task, plus_poles)|
149
- pluses = plus_poles.collect { |plus_pole| PlusPole(plus_pole) }
150
-
151
- %{#{magnetic_to.inspect} ==> #{Activity::Introspect.inspect_with_matcher(task)}
152
- #{pluses.empty? ? " []" : pluses.join("\n")}}
153
- end.join("\n")
154
-
155
- "\n#{content}\n".gsub(/\d\d+/, "").gsub(/0x\w+/, "0x")
156
- end
157
-
158
- def self.PlusPole(plus_pole)
159
- signal = plus_pole.signal.to_s.sub("Trailblazer::Activity::", "")
160
- semantic = plus_pole.send(:output).semantic
161
- " (#{semantic})/#{signal} ==> #{plus_pole.color.inspect}"
162
- end
163
-
164
-
165
- end
166
- end
167
71
  end