trailblazer-circuit 0.0.3 → 0.0.4

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.
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: []