core-pipeline 0.0.0 → 0.1.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
  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