trailblazer-circuit 0.0.3 → 0.0.4

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
  SHA1:
3
- metadata.gz: 760ef971087c4983eb7d4f624bca5d78130be9b2
4
- data.tar.gz: f763cd9f2193d16440514a183cb0935955d2158a
3
+ metadata.gz: c9e50c60fbd29ee44bc11f122198acec25c02122
4
+ data.tar.gz: de1fb71290d6c4a817827bfad946cf739a1d4291
5
5
  SHA512:
6
- metadata.gz: 8d6ae7c2daa02a06ae5db0846cb78068cb09b8d489ff2ec5b8ad5d3e2308e055051cb4b0e9d25be64c2709650619f434d78b910852d8d046631a8fa4af94002f
7
- data.tar.gz: b306b440d01b1aa11c3faf587ef7970603dc58b95a3c4636eda3a76bad486c32c363db8a2ca449fd069396dbf12d8a2c052267a4cbadeaa0380ce4ac19075493
6
+ metadata.gz: c8b5ea44e5ca1a618da4c4b4c5130d27700ada1766c621e4753e4d58765995f6799490e3b842f89183ec1ea860704e0c3d55549270cc1a539fab93b5f503269c
7
+ data.tar.gz: 0cf6a6dd1cc39496a236eb2d816e360efca4f60d263a0d591a8f799890bc5e29bd3a8a0ccdcf9b89aa04b11071b40cf7aa0b196e6a7d41f2bf1273df2c646747
data/CHANGES.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 0.0.4
2
+
3
+ * Simpler tracing with `Stack`.
4
+ * Added `Context`.
5
+ * Simplified `Circuit#call`.
6
+
1
7
  # 0.0.3
2
8
 
3
9
  * Make the first argument to `#Activity` (`@name`) always a Hash where `:id` is a reserved key for the name of the circuit.
@@ -1,24 +1,35 @@
1
+ require "hirb"
2
+
1
3
  module Trailblazer
2
4
  class Circuit
3
- class Trace
5
+ module Trace
4
6
  # TODO:
5
7
  # * Struct for debug_item
6
8
  module Present
7
- FREE_SPACE = (' ' * 3).freeze
8
9
  module_function
9
10
 
10
- def tree(stack, level = 1)
11
+ def tree(stack, level=1, tree=[])
12
+ tree_for(stack, level, tree)
13
+
14
+ Hirb::Console.format_output(tree, class: :tree, type: :directory)
15
+ end
16
+
17
+ def tree_for(stack, level, tree)
11
18
  stack.each do |debug_item|
12
- puts FREE_SPACE * level + delimeter(stack, debug_item) + '--' + '> ' + to_name(debug_item) + to_options(debug_item)
19
+ if debug_item.size == 2 # flat
20
+ tree << [ level, debug_item[0].last[debug_item[0][0]] || debug_item[0][0] ]
21
+ else # nesting
22
+ tree << [ level, debug_item[0][0] ]
23
+
24
+ tree_for(debug_item[1..-2], level + 1, tree)
13
25
 
14
- if debug_item.last.is_a?(Array)
15
- tree(debug_item.last, level + 1)
26
+ tree << [ level+1, debug_item[-1][0] ]
16
27
  end
28
+
29
+ tree
17
30
  end
18
31
  end
19
32
 
20
- # private
21
-
22
33
  def to_name(debug_item)
23
34
  track = debug_item[2]
24
35
  klass = track.class == Class ? track : track.class
@@ -56,14 +67,6 @@ module Trailblazer
56
67
  pink: 35
57
68
  }
58
69
  end
59
-
60
- def delimeter(stack, debug_item)
61
- if stack.last == debug_item || debug_item.last.is_a?(Array)
62
- '`'
63
- else
64
- '|'
65
- end
66
- end
67
70
  end
68
71
  end
69
72
  end
@@ -40,7 +40,7 @@ class Trailblazer::Circuit
40
40
  # Make the context's instance method a "lambda" and reuse #call!.
41
41
  # TODO: should we make :context a kwarg?
42
42
  def meth!(proc, direction, options, flow_options, *args)
43
- call!(flow_options[:context].method(proc), direction, options, flow_options, *args)
43
+ call!(flow_options[:exec_context].method(proc), direction, options, flow_options, *args)
44
44
  end
45
45
  end
46
46
  end
@@ -1,22 +1,77 @@
1
1
  module Trailblazer
2
2
  class Circuit
3
- # direction, result = circuit.( circuit[:Start], options, runner: Circuit::Trace.new, stack: [] )
3
+ # Trace#call will call the activities and trace what steps are called, options passed,
4
+ # and the order and nesting.
5
+ #
6
+ # stack, _ = Trailblazer::Circuit::Trace.(activity, activity[:Start], { id: 1 })
7
+ # puts Trailblazer::Circuit::Present.tree(stack) # renders the trail.
8
+ module Trace
9
+ def self.call(activity, direction, options, flow_options={})
10
+ # activity_wrap is the circuit/pipeline that wraps each step and implements tracing (and more, like input/output contracts, etc!).
11
+ activity_wrap = Activity::Before( Activity::Wrapped::Activity, Activity::Wrapped::Call, Trace.method(:capture_args), direction: Right )
12
+ activity_wrap = Activity::Before( activity_wrap, Activity::Wrapped::Activity[:End], Trace.method(:capture_return), direction: Right )
4
13
 
5
- # Every `activity.call` is considered nested
6
- class Trace
7
- # Trace is passed in as the `:runner` into Circuit#call and is called per task.
8
- def call(activity, direction, args, debug:raise, stack:raise, **flow_options)
9
- activity_name, _ = debug[activity]
10
- activity_name ||= activity
14
+ step_runners = {
15
+ nil => activity_wrap, # call all steps with tracing.
16
+ }
11
17
 
12
- # Use Circuit::Run to actually call the task.
13
- direction, options, _flow_options = Run.(activity, direction, args, flow_options.merge(stack: []))
18
+ tracing_flow_options = {
19
+ runner: Activity::Wrapped::Runner,
20
+ stack: Circuit::Trace::Stack.new,
21
+ step_runners: step_runners,
22
+ debug: activity.circuit.instance_variable_get(:@name)
23
+ }
14
24
 
15
- # TODO: fix the inspect, we need a snapshot, deep-nested.
16
- stack << [activity_name, activity, direction, options, options.inspect, _flow_options[:stack].any? ? _flow_options[:stack] : nil ]
25
+ direction, options, flow_options = activity.( direction, options, tracing_flow_options.merge(flow_options) )
17
26
 
18
- return direction, options, _flow_options.merge(stack: stack, debug: debug)
27
+ return flow_options[:stack].to_a, direction, options, flow_options
19
28
  end
20
- end # Trace
29
+
30
+ def self.capture_args(direction, options, flow_options, wrap_config, original_flow_options)
31
+ original_flow_options[:stack].indent!
32
+
33
+ original_flow_options[:stack] << [ wrap_config[:step], :args, nil, options.dup, original_flow_options[:debug] ]
34
+
35
+ [ direction, options, flow_options, wrap_config, original_flow_options ]
36
+ end
37
+
38
+ def self.capture_return(direction, options, flow_options, wrap_config, original_flow_options)
39
+ original_flow_options[:stack] << [ wrap_config[:step], :return, flow_options[:result_direction], options.dup ]
40
+
41
+ original_flow_options[:stack].unindent!
42
+
43
+ [ direction, options, flow_options, wrap_config, original_flow_options ]
44
+ end
45
+
46
+ # Mutable/stateful per design. We want a (global) stack!
47
+ class Stack
48
+ def initialize
49
+ @nested = []
50
+ @stack = [ @nested ]
51
+ end
52
+
53
+ def indent!
54
+ current << indented = []
55
+ @stack << indented
56
+ end
57
+
58
+ def unindent!
59
+ @stack.pop
60
+ end
61
+
62
+ def <<(args)
63
+ current << args
64
+ end
65
+
66
+ def to_a
67
+ @nested
68
+ end
69
+
70
+ private
71
+ def current
72
+ @stack.last
73
+ end
74
+ end # Stack
75
+ end
21
76
  end
22
77
  end
@@ -1,5 +1,5 @@
1
1
  module Trailblazer
2
2
  class Circuit
3
- VERSION = "0.0.3"
3
+ VERSION = "0.0.4"
4
4
  end
5
5
  end
@@ -0,0 +1,58 @@
1
+ class Trailblazer::Circuit
2
+ module Activity::Wrapped
3
+ # Input = ->(direction, options, flow_options) { [direction, options, flow_options] }
4
+
5
+ def self.call_activity(direction, options, flow_options, wrap_config, original_flow_options)
6
+ task = wrap_config[:step]
7
+
8
+ # Call the actual task we're wrapping here.
9
+ wrap_config[:result_direction], options, flow_options = task.( direction, options, original_flow_options )
10
+
11
+ [ direction, options, flow_options, wrap_config, original_flow_options ]
12
+ end
13
+
14
+ Call = method(:call_activity)
15
+
16
+ # Output = ->(direction, options, flow_options) { [direction, options, flow_options] }
17
+
18
+ class End < Trailblazer::Circuit::End
19
+ def call(direction, options, flow_options, wrap_config, *args)
20
+ [ wrap_config[:result_direction], options, flow_options, wrap_config, *args ]
21
+ end
22
+ end
23
+
24
+ Activity = Trailblazer::Circuit::Activity({ id: "task.wrap" }, end: { default: End.new(:default) }) do |act|
25
+ {
26
+ act[:Start] => { Right => Call }, # options from outside
27
+ # Input => { Circuit::Right => Trace::CaptureArgs },
28
+ # MyInject => { Circuit::Right => Trace::CaptureArgs },
29
+ # Trace::CaptureArgs => { Circuit::Right => Call },
30
+ Call => { Right => act[:End] },
31
+ # Trace::CaptureReturn => { Circuit::Right => Output },
32
+ # Output => { Circuit::Right => act[:End] }
33
+ }
34
+ end
35
+
36
+ # Find the respective wrap per task, and run it.
37
+ class Runner
38
+ # private flow_options[ :step_runners ]
39
+ def self.call(task, direction, options, flow_options)
40
+ # TODO: test this decider!
41
+ task_wrap = flow_options[:step_runners][task] || flow_options[:step_runners][nil] # DISCUSS: default could be more explicit@
42
+
43
+ # we can't pass :runner in here since the Step::Pipeline would call itself again, then.
44
+ # However, we need the runner in nested activities.
45
+ wrap_config = { step: task }
46
+
47
+ # Call the task_wrap circuit:
48
+ # |-- Start
49
+ # |-- Trace.capture_args [optional]
50
+ # |-- Call (call actual task)
51
+ # |-- Trace.capture_return [optional]
52
+ # |-- End
53
+ # Pass empty flow_options to the task_wrap, so it doesn't infinite-loop.
54
+ task_wrap.( task_wrap[:Start], options, {}, wrap_config, flow_options ) # all tasks in Wrap have to implement this signature.
55
+ end
56
+ end # Runner
57
+ end
58
+ end
@@ -29,16 +29,15 @@ module Trailblazer
29
29
  #
30
30
  # @param activity A task from the circuit where to start
31
31
  # @param args An array of options passed to the first task.
32
- def call(activity, args, runner: Run, **flow_options)
33
- # TODO: args
34
- direction = nil
35
- flow_options = { runner: runner, debug: @name }.merge(flow_options) # DISCUSS: make this better?
32
+ def call(activity, options, flow_options={}, *args)
33
+ direction = nil
34
+ runner = flow_options[:runner] || Run
36
35
 
37
36
  loop do
38
- direction, args, flow_options = runner.( activity, direction, args, flow_options )
37
+ direction, options, flow_options = runner.( activity, direction, options, flow_options, *args )
39
38
 
40
39
  # Stop execution of the circuit when we hit a stop event (< End). This could be an activity's End or Suspend.
41
- return [ direction, args, flow_options ] if @stop_events.include?(activity)
40
+ return [ direction, options, flow_options ] if @stop_events.include?(activity)
42
41
 
43
42
  activity = next_for(activity, direction) do |next_activity, in_map|
44
43
  activity_name = @name[activity] || activity # TODO: this must be implemented only once, somewhere.
@@ -97,10 +96,20 @@ module Trailblazer
97
96
  end
98
97
 
99
98
  # Builder for running a nested process from a specific `start_at` position.
100
- def self.Nested(activity, start_with=activity[:Start])
101
- ->(start_at, options, *args) {
102
- activity.(start_with, options, *args)
103
- }
99
+ def self.Nested(*args)
100
+ Nested.new(*args)
101
+ end
102
+
103
+ class Nested
104
+ def initialize(activity, start_with=activity[:Start])
105
+ @activity, @start_with = activity, start_with
106
+ end
107
+
108
+ def call(start_at, *args)
109
+ @activity.(@start_with, *args)
110
+ end
111
+
112
+ attr_reader :activity
104
113
  end
105
114
 
106
115
  class Direction; end
@@ -113,3 +122,7 @@ require "trailblazer/circuit/activity"
113
122
  require "trailblazer/circuit/task"
114
123
  require "trailblazer/circuit/alter"
115
124
  require "trailblazer/circuit/trace"
125
+ require "trailblazer/circuit/present"
126
+ require "trailblazer/circuit/wrapped"
127
+
128
+ require "trailblazer/context"
@@ -0,0 +1,78 @@
1
+ # "di" step_di: order: 1. runtime, 2. { contract.class: A } (dynamic at runtime?)
2
+
3
+ =begin
4
+ * test "stripe scenario"
5
+ def pay!(options, stripe: Stripe.new) # can be overridden in e.g. test via runtime dependencies or container.
6
+ * problem: HOW TO payment.stripe.engine ? step M, underscore_dots: true
7
+ * Create["payment.stripe.engine"] = Stripe.new
8
+
9
+ =end
10
+
11
+ =begin
12
+ * In an operation, there's always a Context object that holds the Containers and the initial mutable data.
13
+ * SIMPLE/CURRENT WAY: we can simply pass this on without wrapping (no boundaries)
14
+ * wrap for Nested, unwrap after Nested
15
+ =end
16
+
17
+ # TODO: mark/make all but mutable_options as frozen.
18
+ # The idea of Skill is to have a generic, ordered read/write interface that
19
+ # collects mutable runtime-computed data while providing access to compile-time
20
+ # information.
21
+ # The runtime-data takes precedence over the class data.
22
+ module Trailblazer
23
+ # Holds local options (aka `mutable_options`) and "original" options from the "outer"
24
+ # activity (aka wrapped_options).
25
+
26
+ # only public creator: Build
27
+ class Context # :data object:
28
+ def initialize(wrapped_options, mutable_options)
29
+ @wrapped_options, @mutable_options = wrapped_options, mutable_options
30
+ end
31
+
32
+ def [](name)
33
+ @mutable_options[name] || @wrapped_options[name]
34
+ end
35
+
36
+ def []=(name, value)
37
+ @mutable_options[name] = value
38
+ end
39
+
40
+ def merge(hash)
41
+ original, mutable_options = decompose
42
+
43
+ ctx = Trailblazer::Context( original, mutable_options.merge(hash) )
44
+ end
45
+
46
+ # Return the Context's two components. Used when computing the new output for
47
+ # the next activity.
48
+ def decompose
49
+ # it would be cool if that could "destroy" the original object.
50
+ # also, if those hashes were immutable, that'd be amazing.
51
+ [ @wrapped_options, @mutable_options ]
52
+ end
53
+
54
+ # TODO: massive performance bottleneck. also, we could already "know" here what keys the
55
+ # transformation wants.
56
+ def to_hash
57
+ {}.tap do |hash|
58
+ # arr = to_runtime_data << to_mutable_data << tmp_options
59
+
60
+ # the "key" here is to call to_hash on all containers.
61
+ [ @wrapped_options.to_hash, @mutable_options.to_hash ].each do |options|
62
+ options.each { |k, v| hash[k.to_sym] = v }
63
+ end
64
+ end
65
+ end
66
+
67
+ def Build
68
+ wrapped_options, mutable_options = *decompose
69
+ wrapped_options = yield(wrapped_options, mutable_options) if block_given?
70
+
71
+ Trailblazer::Context(wrapped_options)
72
+ end
73
+ end
74
+
75
+ def self.Context(wrapped_options, mutable_options={})
76
+ Context.new(wrapped_options, mutable_options)
77
+ end
78
+ end
@@ -24,5 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency "minitest", "~> 5.0"
25
25
  spec.add_development_dependency "raise"
26
26
 
27
+ spec.add_dependency "hirb"
28
+
27
29
  spec.required_ruby_version = '>= 2.0.0'
28
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trailblazer-circuit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-05-11 00:00:00.000000000 Z
11
+ date: 2017-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: hirb
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: BPMN-compliant workflows or state machines. Used in Trailblazer's Operation
70
84
  to implement the Railway.
71
85
  email:
@@ -89,6 +103,8 @@ files:
89
103
  - lib/trailblazer/circuit/testing.rb
90
104
  - lib/trailblazer/circuit/trace.rb
91
105
  - lib/trailblazer/circuit/version.rb
106
+ - lib/trailblazer/circuit/wrapped.rb
107
+ - lib/trailblazer/context.rb
92
108
  - trailblazer-circuit.gemspec
93
109
  homepage: http://trailblazer.to/gems/workflow
94
110
  licenses: []