core-pipeline 0.0.0 → 0.1.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
  SHA256:
3
- metadata.gz: a40a0de02118c468488220555a57d531b738170f51bd61bbe165f71f5851ddb6
4
- data.tar.gz: bd1d8c14350961d2011fad408748095c315d04b8e7d067332e259f0b9f644656
3
+ metadata.gz: 12857259602df10fd3cb27322098ba04c9f3b7706fbd982939ba162e97f4dbcb
4
+ data.tar.gz: fbb77341b33a2cb8195dd6431fd23efeb99499fd6bf3b175a1cadc831feae135
5
5
  SHA512:
6
- metadata.gz: 4b87d1a60bad4f85bbaedddcd956222c03380d957dcac7663c63ce56bda9c876f4a230907285982438ea33fc396b3c8de4497168c9eaa6eb451351a02444dbc9
7
- data.tar.gz: f9c54d75eb9f2803326d422ab7efed3f27bec648bdccaf9222633c16e1078bf4b01bb2a9086b7eb9954d3c76e17cef4c6ff4bdb4f65a521b6d5d0dd25235ee15
6
+ metadata.gz: 8383cd41dc3db75a6b8329baff6f7af529b4fcb2f82464fa3be3c5afbb3fdfe44a8666aa2815ef115a191316066cb1a49c11a48747dd1b8755d9b89233329762
7
+ data.tar.gz: 254a3bc8e99465394dd6f7b2bbe3dc8e90a73d655d037e3c1b44580c0de172f19360fe696683940aaf7d22c72950b91ef59ab7bd209bd30e09afdbc3929aaeb6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## [v0.1.0](https://github.com/metabahn/corerb/releases/tag/2021-07-15)
2
+
3
+ *released on 2021-07-15*
4
+
5
+ * `fix` [#69](https://github.com/metabahn/corerb/pull/69) Allow pipelines to be called at the class level ([bryanp](https://github.com/bryanp))
6
+ * `add` [#64](https://github.com/metabahn/corerb/pull/64) Add a method for checking if a callable pipeline will call any actions ([bryanp](https://github.com/bryanp))
7
+ * `chg` [#63](https://github.com/metabahn/corerb/pull/63) Improve inspect output for callable pipelines ([bryanp](https://github.com/bryanp))
8
+ * `chg` [#61](https://github.com/metabahn/corerb/pull/61) Refactor pipelines to be more performant for new instances ([bryanp](https://github.com/bryanp))
9
+ * `fix` [#59](https://github.com/metabahn/corerb/pull/59) Prevent collisions in auto-defined action names ([bryanp](https://github.com/bryanp))
10
+ * `chg` [#58](https://github.com/metabahn/corerb/pull/58) Define pipeline actions with context ([bryanp](https://github.com/bryanp))
11
+ * `chg` [#57](https://github.com/metabahn/corerb/pull/57) Avoid cache lookups for action calls when the action is just a method ([bryanp](https://github.com/bryanp))
12
+ * `add` [#54](https://github.com/metabahn/corerb/pull/54) Make all pipeline objects inspectable ([bryanp](https://github.com/bryanp))
13
+
1
14
  ## [v0.0.0](https://github.com/metabahn/corerb/releases/tag/2021-07-07)
2
15
 
3
16
  *released on 2021-07-07*
@@ -1,28 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "is/inspectable"
4
+ require "is/stateful"
5
+ require "securerandom"
6
+
3
7
  module Core
4
8
  module Pipeline
5
9
  # [public] A pipeline action.
6
10
  #
7
11
  class Action
12
+ include Is::Stateful
13
+ state :__used_random_suffixes, default: []
14
+
8
15
  class << self
9
16
  # [public] Builds an action for a given target.
10
17
  #
11
- def build(target = nil, *args, context:, before:, after:, &block)
18
+ def build(target = nil, *args, before: nil, after: nil, context: nil, &block)
12
19
  if block
13
- Actions::Block.new(target, before: before, after: after, &block)
20
+ Actions::Block.new(target, before: before, after: after, context: context, &block)
14
21
  else
15
- build_target(target, *args, before: before, after: after)
22
+ build_target(target, *args, before: before, after: after, context: context)
16
23
  end
17
24
  end
18
25
 
19
- private def build_target(first, *args, before:, after:)
26
+ private def build_target(first, *args, before:, after:, context:)
20
27
  case first
21
28
  when String, Symbol
22
- if (target = build_target(args[0], *args[1..], before: before, after: after))
29
+ if (target = build_target(args[0], *args[1..], before: before, after: after, context: context))
23
30
  target
24
31
  else
25
- Actions::Method.new(first, before: before, after: after)
32
+ Actions::Method.new(first, before: before, after: after, context: context)
26
33
  end
27
34
  when NilClass
28
35
  nil
@@ -30,11 +37,29 @@ module Core
30
37
  raise ArgumentError, "unsupported action type"
31
38
  end
32
39
  end
40
+
41
+ def build_name
42
+ suffix = generate_random_suffix
43
+ if __used_random_suffixes.include?(suffix)
44
+ build_name
45
+ else
46
+ __used_random_suffixes << suffix
47
+ "action_#{suffix}"
48
+ end
49
+ end
50
+
51
+ def generate_random_suffix
52
+ SecureRandom.hex(8)
53
+ end
33
54
  end
34
55
 
35
- def initialize(before:, after:)
56
+ include Is::Inspectable
57
+
58
+ def initialize(name, before: nil, after: nil, context: nil)
59
+ @name = (name || self.class.build_name).to_sym
36
60
  @before = before
37
61
  @after = after
62
+ @context = context
38
63
  end
39
64
 
40
65
  # [public] The action name.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "securerandom"
3
+ require "is/inspectable"
4
4
 
5
5
  module Core
6
6
  module Pipeline
@@ -8,24 +8,29 @@ module Core
8
8
  # [public] A pipeline action defined as a block.
9
9
  #
10
10
  class Block < Action
11
- class << self
12
- def build_name
13
- "action_#{SecureRandom.hex(8)}"
14
- end
15
- end
11
+ include Is::Inspectable
16
12
 
17
- def initialize(name = nil, before:, after:, &block)
18
- @name = name || self.class.build_name
13
+ def initialize(name = nil, before: nil, after: nil, context: nil, &block)
19
14
  @block = block
20
15
 
21
- super(before: before, after: after)
16
+ super(name, before: before, after: after, context: context)
22
17
  end
23
18
 
24
19
  # [public]
25
20
  #
26
21
  def finalize(context)
27
- context.define_singleton_method(@name, &@block)
28
- context.method(@name).to_proc
22
+ case @context
23
+ when NilClass
24
+ context.define_method(@name, &@block)
25
+ @name
26
+ else
27
+ if @block.binding.receiver.equal?(@context)
28
+ @block
29
+ else
30
+ @context.define_singleton_method(@name, &@block)
31
+ @context.method(@name).to_proc
32
+ end
33
+ end
29
34
  end
30
35
  end
31
36
  end
@@ -1,22 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "is/inspectable"
4
+
3
5
  module Core
4
6
  module Pipeline
5
7
  module Actions
6
8
  # [public] A pipeline action defined as a method.
7
9
  #
8
10
  class Method < Action
9
- def initialize(name = nil, before:, after:)
10
- @name = name
11
+ include Is::Inspectable
12
+
13
+ def initialize(name = nil, before: nil, after: nil, context: nil)
11
14
  @method = nil
12
15
 
13
- super(before: before, after: after)
16
+ super(name, before: before, after: after, context: context)
14
17
  end
15
18
 
16
19
  # [public]
17
20
  #
18
21
  def finalize(context)
19
- context.method(@name).to_proc
22
+ case @context
23
+ when NilClass
24
+ context.instance_method(@name)
25
+ @name
26
+ else
27
+ @context.method(@name).to_proc
28
+ end
20
29
  end
21
30
  end
22
31
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "is/inspectable"
4
+
5
+ require_relative "action"
6
+ require_relative "compiler"
7
+
8
+ module Core
9
+ module Pipeline
10
+ include Is::Inspectable
11
+ inspects without: [:@object, :@mutex]
12
+
13
+ # [public] Calls a pipeline on a pipelined object.
14
+ #
15
+ class Callable
16
+ def initialize(object)
17
+ @object = object
18
+ @mutex = Mutex.new
19
+ @actions = []
20
+ @skipped = []
21
+ end
22
+
23
+ attr_reader :actions
24
+ attr_reader :skipped
25
+
26
+ # [public] Returns true if the pipeline will call any actions.
27
+ #
28
+ def any?
29
+ (@actions.map(&:name) - @skipped).any?
30
+ end
31
+
32
+ # [public] Defines a pipeline action.
33
+ #
34
+ def action(*args, before: nil, after: nil, context: nil, &block)
35
+ @actions << Action.build(*args, before: before, after: after, context: context, &block)
36
+ end
37
+
38
+ # [public] Skips the given action.
39
+ #
40
+ def skip(name)
41
+ @skipped << name.to_sym
42
+ end
43
+
44
+ # [public] Replaces existing actions with actions from the given pipeline.
45
+ #
46
+ def use(pipeline)
47
+ if (pipeline.is_a?(Class) || pipeline.is_a?(Module)) && pipeline.ancestors.include?(Is::Pipeline)
48
+ @mutex.synchronize do
49
+ @actions.clear
50
+ @actions.concat(pipeline.pipeline.actions)
51
+ end
52
+ else
53
+ raise ArgumentError, "expected a pipeline"
54
+ end
55
+ end
56
+
57
+ # [public] Includes actions from the given pipeline.
58
+ #
59
+ def include(pipeline)
60
+ if (pipeline.is_a?(Class) || pipeline.is_a?(Module)) && pipeline.ancestors.include?(Is::Pipeline)
61
+ @actions.concat(pipeline.pipeline.actions)
62
+ else
63
+ raise ArgumentError, "expected a pipeline"
64
+ end
65
+ end
66
+
67
+ # [public] Excludes actions from the given pipeline.
68
+ #
69
+ def exclude(pipeline)
70
+ if (pipeline.is_a?(Class) || pipeline.is_a?(Module)) && pipeline.ancestors.include?(Is::Pipeline)
71
+ pipeline.pipeline.actions.each do |action|
72
+ @actions.delete(action)
73
+ end
74
+ else
75
+ raise ArgumentError, "expected a pipeline"
76
+ end
77
+ end
78
+
79
+ # [public] Calls the pipeline in context of an object with the given arguments.
80
+ #
81
+ def call(object, ...)
82
+ compile.call(object, ...)
83
+ end
84
+
85
+ private def compile
86
+ instance_eval(Compiler.compile(self, @object), __FILE__, __LINE__ - 1)
87
+
88
+ self
89
+ end
90
+ end
91
+ end
92
+ end
@@ -5,69 +5,72 @@ require_relative "signals/rejected"
5
5
 
6
6
  module Core
7
7
  module Pipeline
8
- # [public] Compiles a pipeline instance into a callable object.
8
+ # [public] Compiles a pipeline into evalable code.
9
9
  #
10
10
  class Compiler
11
11
  class << self
12
- # [public] Compiles a pipeline instance.
12
+ # [public] Compiles a callable pipeline in context of an object.
13
13
  #
14
- def compile(pipeline)
15
- compiled = <<~CODE
16
- def call(...)
17
- #{compile_pipeline(pipeline)}
14
+ def compile(callable, object)
15
+ <<~CODE
16
+ def call(object, ...)
17
+ #{compile_call(callable, object)}
18
18
  rescue Core::Pipeline::Signals::Halted => error
19
19
  error.value
20
20
  end
21
21
  CODE
22
-
23
- pipeline.instance_eval(compiled, __FILE__, __LINE__ - 1)
24
22
  end
25
23
 
26
- private def compile_pipeline(pipeline)
24
+ private def compile_call(callable, object)
27
25
  compiled = +""
26
+ finalized_actions(callable, object).each_pair do |object_id, action|
27
+ compiled << "begin; "
28
+
29
+ compiled << case action
30
+ when Symbol
31
+ "object.#{action}(...); "
32
+ else
33
+ "@__finalized_actions[#{object_id}].call(...); "
34
+ end
28
35
 
29
- finalized_actions(pipeline).each_key do |object_id|
30
- compiled << "begin\n"
31
- compiled << " @__finalized_actions[#{object_id}].call(...)\n"
32
- compiled << "rescue Core::Pipeline::Signals::Rejected\n"
33
- compiled << "end\n"
36
+ compiled << "rescue Core::Pipeline::Signals::Rejected; end\n"
34
37
  end
35
38
 
36
39
  compiled
37
40
  end
38
41
 
39
- private def finalized_actions(pipeline)
40
- validate_skipped_actions(pipeline)
41
- finalized_actions = build_finalized_actions(pipeline)
42
- pipeline.instance_variable_set(:@__finalized_actions, finalized_actions)
42
+ private def finalized_actions(callable, object)
43
+ validate_skipped_actions(callable)
44
+ finalized_actions = build_finalized_actions(callable, object)
45
+ callable.instance_variable_set(:@__finalized_actions, finalized_actions)
43
46
  end
44
47
 
45
- private def validate_skipped_actions(pipeline)
46
- pipeline.__skipped_actions.each do |skipped_action|
47
- unless pipeline.__actions.any? { |action| action.name == skipped_action }
48
- raise "cannot skip unknown action `#{skipped_action}`"
48
+ private def validate_skipped_actions(callable)
49
+ callable.skipped.each do |skipped|
50
+ unless callable.actions.any? { |action| action.name == skipped }
51
+ raise "cannot skip unknown action `#{skipped}`"
49
52
  end
50
53
  end
51
54
  end
52
55
 
53
- private def build_finalized_actions(pipeline)
54
- ordered_actions(pipeline).reject { |action|
55
- pipeline.__skipped_actions.include?(action.name)
56
+ private def build_finalized_actions(callable, object)
57
+ ordered_actions(callable).reject { |action|
58
+ callable.skipped.include?(action.name)
56
59
  }.map { |action|
57
- action.finalize(pipeline)
60
+ action.finalize(object)
58
61
  }.each_with_object({}) { |action, lookup|
59
62
  lookup[action.object_id] = action
60
63
  }
61
64
  end
62
65
 
63
- private def ordered_actions(pipeline)
64
- return pipeline.__actions if pipeline.__actions.empty?
66
+ private def ordered_actions(callable)
67
+ return callable.actions if callable.actions.empty?
65
68
 
66
69
  marked = []
67
70
  ordered = []
68
- working = pipeline.__actions.dup
71
+ working = callable.actions.dup
69
72
 
70
- pipeline.__actions.each do |action|
73
+ callable.actions.each do |action|
71
74
  unless action.before || action.after
72
75
  ordered << action
73
76
  working.delete(action)
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "is/inspectable"
4
+
3
5
  module Core
4
6
  module Pipeline
5
7
  module Signals
6
8
  class Halted < Exception
9
+ include Is::Inspectable
10
+
7
11
  def initialize(value)
8
12
  @value = value
9
13
  end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "is/inspectable"
4
+
3
5
  module Core
4
6
  module Pipeline
5
7
  module Signals
6
8
  class Rejected < Exception
9
+ include Is::Inspectable
7
10
  end
8
11
  end
9
12
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Core
4
4
  module Pipeline
5
- VERSION = "0.0.0"
5
+ VERSION = "0.1.0"
6
6
 
7
7
  def self.version
8
8
  VERSION
data/lib/is/pipeline.rb CHANGED
@@ -4,7 +4,7 @@ require "is/async"
4
4
  require "is/extension"
5
5
  require "is/stateful"
6
6
 
7
- require_relative "../core/pipeline/action"
7
+ require_relative "../core/pipeline/callable"
8
8
  require_relative "../core/pipeline/compiler"
9
9
  require_relative "../core/pipeline/signals/halted"
10
10
  require_relative "../core/pipeline/signals/rejected"
@@ -15,70 +15,57 @@ module Is
15
15
  module Pipeline
16
16
  extend Is::Extension
17
17
 
18
+ applies do |extended:|
19
+ # [public] The current callable pipeline.
20
+ #
21
+ state :pipeline, default: Core::Pipeline::Callable.new(extended ? singleton_class : self)
22
+ end
23
+
18
24
  extends dependencies: [
19
25
  Is::Stateful
20
26
  ]
21
27
 
22
- extends :class do
28
+ extends :definition do
23
29
  # [public] Defines a pipeline action.
24
30
  #
25
- def action(*args, before: nil, after: nil, &block)
26
- @__actions << Core::Pipeline::Action.build(*args, context: self, before: before, after: after, &block)
31
+ def action(*args, before: nil, after: nil, context: nil, &block)
32
+ pipeline.action(*args, before: before, after: after, context: context, &block)
27
33
  end
28
34
 
29
35
  # [public] Skips the given action.
30
36
  #
31
37
  def skip_action(name)
32
- @__skipped_actions << name.to_sym
38
+ pipeline.skip(name)
33
39
  end
34
40
 
35
41
  # [public] Replaces existing actions with actions from the given pipeline.
36
42
  #
37
- def use_pipeline(pipeline)
38
- if (pipeline.is_a?(Class) || pipeline.is_a?(Module)) && pipeline.ancestors.include?(Is::Pipeline)
39
- @__actions.clear
40
- @__actions.concat(pipeline.__actions)
41
- else
42
- raise ArgumentError, "expected a pipeline"
43
- end
43
+ def use_pipeline(other_pipeline)
44
+ pipeline.use(other_pipeline)
44
45
  end
45
46
 
46
47
  # [public] Includes actions from the given pipeline.
47
48
  #
48
- def include_pipeline(pipeline)
49
- if (pipeline.is_a?(Class) || pipeline.is_a?(Module)) && pipeline.ancestors.include?(Is::Pipeline)
50
- @__actions.concat(pipeline.__actions)
51
- else
52
- raise ArgumentError, "expected a pipeline"
53
- end
49
+ def include_pipeline(other_pipeline)
50
+ pipeline.include(other_pipeline)
54
51
  end
55
52
 
56
53
  # [public] Excludes actions from the given pipeline.
57
54
  #
58
- def exclude_pipeline(pipeline)
59
- if (pipeline.is_a?(Class) || pipeline.is_a?(Module)) && pipeline.ancestors.include?(Is::Pipeline)
60
- pipeline.__actions.each do |action|
61
- @__actions.delete(action)
62
- end
63
- else
64
- raise ArgumentError, "expected a pipeline"
65
- end
55
+ def exclude_pipeline(other_pipeline)
56
+ pipeline.exclude(other_pipeline)
66
57
  end
67
58
  end
68
59
 
69
- extends :instance do
60
+ extends :implementation do
70
61
  # [public] Calls a pipeline with arguments.
71
62
  #
72
63
  def call(...)
73
- # Compiles the pipeline then redefines `call` to call the compiled pipeline.
74
- #
75
- Core::Pipeline::Compiler.compile(self)
76
-
77
- call(...)
64
+ @pipeline.call(self, ...)
78
65
  end
79
66
  end
80
67
 
81
- extends :instance, prepend: true do
68
+ extends :implementation, prepend: true do
82
69
  # [public] Halts the execution of the pipeline, setting the pipeline's current value to the given object.
83
70
  #
84
71
  private def halt(value = nil)
@@ -91,10 +78,5 @@ module Is
91
78
  raise Core::Pipeline::Signals::Rejected
92
79
  end
93
80
  end
94
-
95
- applied do
96
- state :__actions, default: []
97
- state :__skipped_actions, default: []
98
- end
99
81
  end
100
82
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: core-pipeline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-07 00:00:00.000000000 Z
11
+ date: 2021-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: core-async
14
+ name: core-extension
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.6'
19
+ version: '0.3'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.6'
26
+ version: '0.3'
27
27
  - !ruby/object:Gem::Dependency
28
- name: core-extension
28
+ name: core-inspect
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.1'
33
+ version: '0.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.1'
40
+ version: '0.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: core-state
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -64,6 +64,7 @@ files:
64
64
  - lib/core/pipeline/action.rb
65
65
  - lib/core/pipeline/actions/block.rb
66
66
  - lib/core/pipeline/actions/method.rb
67
+ - lib/core/pipeline/callable.rb
67
68
  - lib/core/pipeline/compiler.rb
68
69
  - lib/core/pipeline/signals/halted.rb
69
70
  - lib/core/pipeline/signals/rejected.rb