crack_pipe 0.1.0 → 0.2.0

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: 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