crack_pipe 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b90ccb391cbc78b10497f1978236cac94de4f715
4
- data.tar.gz: 12f9809281e638984c9d59ac340f5cef7e5c6592
3
+ metadata.gz: ee187d314abb9bfab155824f0d2c92825ef2f0e7
4
+ data.tar.gz: fc720da093abcfbf35c625488d3904012114b193
5
5
  SHA512:
6
- metadata.gz: 8e53e038a829b08417c47ab9a961dc76169be2d6ca6393f70ab0bb2e4f1c249a17b2f58851354f1500e55515ab07d0f07060ac14822bb0e440a1c60665dc4168
7
- data.tar.gz: 3d4f43c12cc6d2c9af68c2b28395d7fdf4da17bc81afbac25bdd9e07ee34b3051097f54044e330e277d2c03107f3a2e50fd3e5ce48892aed8ce2c18db522aa71
6
+ metadata.gz: 4704be40ad6adbf04cd9ed32a03915294e36479481aa1b2711171023d12d9ec17176b0286dd109e74c296fef46fc147e67211f1a27e79d55b257811137378052
7
+ data.tar.gz: fcfef7cacbaafd4ab99ccdc65ee8fe7daeb5b5c6cf0769ddbc3d841faee17a29e9b896217ed7b68feae10fde546c40920c342e57f172465d45066779eed5f8f6
@@ -0,0 +1,80 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'crack_pipe/action/signal'
4
+
5
+ module CrackPipe
6
+ class Action
7
+ module Exec
8
+ class << self
9
+ def call(action, context, track = :default)
10
+ Result.new(action(action, context, [], track))
11
+ end
12
+
13
+ def action(action, context, results = [], track = :default)
14
+ action.steps.each_with_object(results) do |s, rslts|
15
+ next unless track == s.track
16
+
17
+ if s.exec.is_a?(Action)
18
+ self.action(s.exec, context, rslts)
19
+ else
20
+ rslts << step(action, s, context)
21
+ end
22
+
23
+ rslts.last.tap do |r|
24
+ context = r[:context]
25
+ track = r[:next]
26
+ return rslts if r[:signal] == :halt
27
+ end
28
+ end
29
+ end
30
+
31
+ def flow_control_hash(action, step, context, output)
32
+ success = success_with_step?(action, step, output)
33
+
34
+ {
35
+ exec: step.exec,
36
+ track: step.track,
37
+ next: success ? step.track : :fail,
38
+ context: context.dup
39
+ }.merge(flow_control_with_output(output, success))
40
+ end
41
+
42
+ def flow_control_with_output(output, success)
43
+ case output
44
+ when Signal
45
+ {
46
+ signal: output.type,
47
+ output: output.value,
48
+ success: output.success.nil? ? success : output.success
49
+ }
50
+ else
51
+ { output: output, success: success }
52
+ end
53
+ end
54
+
55
+ def halt(output, success = nil)
56
+ throw(:signal, Signal.new(:halt, output, success))
57
+ end
58
+
59
+ def step(action, step, context)
60
+ output = catch(:signal) do
61
+ case (e = step.exec)
62
+ when Symbol
63
+ action.public_send(e, context, **context)
64
+ when Proc
65
+ action.instance_exec(context, **context, &e)
66
+ else
67
+ e.call(context, **context)
68
+ end
69
+ end
70
+
71
+ action.after_step(flow_control_hash(action, step, context, output))
72
+ end
73
+
74
+ def success_with_step?(action, step, output)
75
+ step.always_pass? || step.track != :fail && !action.failure?(output)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -3,12 +3,14 @@
3
3
  module CrackPipe
4
4
  class Action
5
5
  class Result
6
- attr_reader :context, :output
6
+ attr_reader :context, :history, :output
7
7
 
8
- def initialize(context: {}, output: nil, success:, **)
9
- @context = context
10
- @output = output
11
- @success = success
8
+ def initialize(history)
9
+ last_result = history.last
10
+ @context = last_result[:context]
11
+ @history = history
12
+ @output = last_result[:output]
13
+ @success = last_result[:success]
12
14
  end
13
15
 
14
16
  def [](key)
@@ -2,10 +2,11 @@
2
2
 
3
3
  module CrackPipe
4
4
  class Action
5
- class ShortCircuit
6
- attr_reader :success, :value
5
+ class Signal
6
+ attr_reader :success, :type, :value
7
7
 
8
- def initialize(value, success = nil)
8
+ def initialize(type, value, success = nil)
9
+ @type = type
9
10
  @value = value
10
11
  @success = success
11
12
  end
@@ -3,19 +3,33 @@
3
3
  module CrackPipe
4
4
  class Action
5
5
  class Step
6
- attr_reader :exec, :context_override, :desc, :output_override, :track
6
+ attr_reader :exec, :track
7
7
 
8
- def initialize(
9
- track,
10
- exec,
11
- output_override = nil,
12
- **context_override
13
- )
14
- @context_override = context_override.dup
15
- @exec = exec
16
- @output_override = output_override
8
+ def initialize(exec = nil, always_pass: false, track: :default, **, &blk)
9
+ if block_given?
10
+ raise ArgumentError, '`exec` must be `nil` with a block' unless
11
+ exec.nil?
12
+ exec = blk
13
+ end
14
+
15
+ @always_pass = always_pass
16
+ @exec = instantiate_action(exec)
17
17
  @track = track
18
18
  end
19
+
20
+ def always_pass?
21
+ @always_pass
22
+ end
23
+
24
+ private
25
+
26
+ # NOTE: This allows actions to be passed in as a class rather than as an
27
+ # instance. It's the difference betweem `step SomeAction` vs
28
+ # `step SomeAction.new` when nesting actions.
29
+ def instantiate_action(obj)
30
+ return obj.new if obj.is_a?(Class) && obj < Action
31
+ obj
32
+ end
19
33
  end
20
34
  end
21
35
  end
@@ -1,10 +1,15 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require 'crack_pipe/action/exec'
3
4
  require 'crack_pipe/action/result'
4
- require 'crack_pipe/action/short_circuit'
5
+ require 'crack_pipe/action/signal'
5
6
  require 'crack_pipe/action/step'
6
7
 
7
8
  module CrackPipe
9
+ # NOTE: The reason for using an instantiated class rather than doing all of
10
+ # this functionally, since there is no real state data, is so that symbols
11
+ # operate as instance methods and that procs and the like are executed with
12
+ # `instance_exec`, giving them access to the local scope.
8
13
  class Action
9
14
  @steps = []
10
15
 
@@ -12,9 +17,13 @@ module CrackPipe
12
17
  attr_reader :steps
13
18
 
14
19
  def call(context, &blk)
15
- new(context, steps, &blk).result
20
+ new(&blk).call(context)
16
21
  end
17
22
 
23
+ # NOTE: In general, the only time you should use inheritence is when
24
+ # created a more specilized generic action with some of its own methods
25
+ # similar such as `pass!` or overiding behavior in `after_step` or
26
+ # `failure?`.
18
27
  def inherited(subclass)
19
28
  subclass.instance_variable_set(:@steps, @steps.dup)
20
29
  super
@@ -27,30 +36,26 @@ module CrackPipe
27
36
  end
28
37
 
29
38
  def pass(exec = nil, **kwargs, &blk)
30
- step(exec, kwargs.merge(output_override: true), &blk)
39
+ step(exec, kwargs.merge(always_pass: true), &blk)
31
40
  end
32
41
 
33
- def step(
34
- exec = nil,
35
- output_override: nil,
36
- track: :default,
37
- **opts,
38
- &blk
39
- )
40
- if block_given?
41
- raise ArgumentError, '`exec` must be `nil` with a block' unless
42
- exec.nil?
43
- exec = blk
44
- end
45
- @steps += [Step.new(track, exec, output_override, opts)]
42
+ def step(*args, &blk)
43
+ @steps += [Step.new(*args, &blk)]
46
44
  end
47
45
  end
48
46
 
49
- attr_reader :history, :result
47
+ attr_reader :steps
50
48
 
51
- def initialize(context, steps = self.class.steps, &blk)
52
- @history = []
53
- _result!(context.dup, steps.dup, &blk)
49
+ def initialize(steps = nil, **default_context, &blk)
50
+ @__default_context__ = default_context.dup
51
+ @__wrapper__ = block_given? ? blk : nil
52
+ @steps = steps ? steps.dup : self.class.steps
53
+ end
54
+
55
+ def call(context)
56
+ context = @__default_context__.merge(context)
57
+ return @__wrapper__.call(Exec.(self, context)) if @__wrapper__
58
+ Exec.(self, context)
54
59
  end
55
60
 
56
61
  # NOTE: While this hook does nothing by default, it is here with the
@@ -58,109 +63,20 @@ module CrackPipe
58
63
  # for output or adding values to the context. A common example would be
59
64
  # returning some kind of default failure object in place of a literal `nil`
60
65
  # or `false`.
61
- def after_step(output, _context, _step)
62
- output
63
- end
64
-
65
- def fail!(value)
66
- ShortCircuit.new(value, false)
67
- end
68
-
69
- def pass!(value)
70
- ShortCircuit.new(value, true)
71
- end
72
-
73
- def step_failure?(output)
74
- case output
75
- when Result
76
- output.failure?
77
- else
78
- !output
79
- end
80
- end
81
-
82
- def to_a
83
- history.dup
66
+ def after_step(flow_control_hash)
67
+ flow_control_hash
84
68
  end
85
69
 
86
- private
87
-
88
- def _action?(obj)
89
- obj.is_a?(Class) && obj < Action
90
- end
91
-
92
- def _exec_nested!(context, action)
93
- @history += action.new(context).history
94
- @history.last.tap { |h| context.merge!(h[:context]) }
70
+ def fail!(output)
71
+ Exec.halt(output, false)
95
72
  end
96
73
 
97
- def _exec_step!(context, step)
98
- e = step.exec
99
- return _exec_nested!(context, e) if _action?(e)
100
-
101
- output =
102
- case e
103
- when Symbol
104
- public_send(e, context, **context)
105
- when Proc
106
- instance_exec(context, **context, &e)
107
- else
108
- e.call(context, **context)
109
- end
110
-
111
- _wrap_output(after_step(output, context, step), context.dup, step)
112
- .tap { |wo| @history << wo }
113
- end
114
-
115
- def _exec_steps!(context, steps)
116
- next_track = :default
117
- steps.map do |s|
118
- next unless next_track == s.track
119
- _exec_step!(context, s).tap do |wrapped_output|
120
- next_track =
121
- wrapped_output[:short_circuit] ? nil : wrapped_output[:next_track]
122
- end
123
- end.compact.last
124
- end
125
-
126
- def _output_hash(output, step)
127
- output = step.output_override unless step.output_override.nil?
128
- success = step.track != :fail && !step_failure?(output)
129
- case output
130
- when ShortCircuit
131
- {
132
- output: output.value,
133
- short_circuit: true,
134
- success: output.success.nil? ? success : output.success
135
- }
136
- else
137
- {
138
- output: output,
139
- short_circuit: false,
140
- success: success
141
- }
142
- end
143
- end
144
-
145
- # NOTE: The optional block here allows you to wrap the the execution in
146
- # external functionality, such as a default `rescue` or something like a
147
- # database transaction if it's necessary to span multiple steps.
148
- def _result!(context, steps)
149
- if block_given?
150
- yield(@result = Result.new(_exec_steps!(context, steps)))
151
- else
152
- @result = Result.new(_exec_steps!(context, steps))
153
- end
154
- @result
74
+ def failure?(output)
75
+ !output
155
76
  end
156
77
 
157
- def _wrap_output(output, context, step)
158
- wrapped_output = _output_hash(after_step(output, context, step), step)
159
- {
160
- exec: step.exec,
161
- next_track: wrapped_output[:success] ? :default : :fail,
162
- context: context
163
- }.merge(wrapped_output)
78
+ def pass!(output)
79
+ Exec.halt(output, true)
164
80
  end
165
81
  end
166
82
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CrackPipe
4
4
  MAJOR = 0
5
- MINOR = 1
5
+ MINOR = 2
6
6
  TINY = 0
7
7
  VERSION = [MAJOR, MINOR, TINY].join('.').freeze
8
8
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crack_pipe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua
@@ -39,6 +39,20 @@ dependencies:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
41
  version: '5.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: pry
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 0.12.1
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 0.12.1
42
56
  - !ruby/object:Gem::Dependency
43
57
  name: rake
44
58
  requirement: !ruby/object:Gem::Requirement
@@ -64,8 +78,9 @@ files:
64
78
  - README.md
65
79
  - lib/crack_pipe.rb
66
80
  - lib/crack_pipe/action.rb
81
+ - lib/crack_pipe/action/exec.rb
67
82
  - lib/crack_pipe/action/result.rb
68
- - lib/crack_pipe/action/short_circuit.rb
83
+ - lib/crack_pipe/action/signal.rb
69
84
  - lib/crack_pipe/action/step.rb
70
85
  - lib/crack_pipe/version.rb
71
86
  homepage: https://github.com/binarypaladin/crack_pipe