core-operation 0.1.1 → 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
  SHA256:
3
- metadata.gz: 5ddabc5be1c80df0049c0d02c04a9c96c30a3ec91da7c91393398902fd41f3d4
4
- data.tar.gz: d0c92a5a7f27b5b23265a44551cd913395f40908b97045bf0cf823598ec16402
3
+ metadata.gz: 31a0abdb122d6ca1ad5fcad68fedfa7f2286d11840d080db2daf54f45b6b153b
4
+ data.tar.gz: 2c472a84ff410b278d9f13344dcd386cb71c92d31987f6a2639bc9ad16379a16
5
5
  SHA512:
6
- metadata.gz: adc0ef08a62a4c989676186ec37bfa50ccc8fd04ed7ee55ccc84279144ea44b5a26cfe6fa99cc45e83ade8319b768ca3b0ed1c705ad07f9998ffa131c3f49a1c
7
- data.tar.gz: ae9eb2fe7239ed8c46d5db781c69d844145da7563c81d45bfc55da9e85cf95dc6b16ed41e9c8357879c27d992414d7899d78f4a35d5ec4d19eecaf5224056f18
6
+ metadata.gz: 272370d98179fc6e361e8ffb1ff226ae457922b7a74e32f68026e5db30000b8d95cafd3168c78df0a51424401d019f850c471894d0cdf8870dd74be0af91cf20
7
+ data.tar.gz: '0188b166e710ade916bf0ac9b7b74fafb686c665943d45ec17730be0fcccccadfcd141335c02d82dd5ddfe493e1688963ecb23f7add7f5f48b33e0b554c14828'
data/CHANGELOG.md CHANGED
@@ -1,19 +1,29 @@
1
- ## [v0.1.1](https://github.com/metabahn/corerb/releases/tag/2022-01-13)
1
+ ## [v0.2.0](https://github.com/bryanp/corerb/releases/tag/2023-12-24)
2
+
3
+ *released on 2023-12-24*
4
+
5
+ * `fix` [#149](https://github.com/bryanp/corerb/pull/149) Fix operations that don't define a default action ([bryanp](https://github.com/bryanp))
6
+ * `fix` [#148](https://github.com/bryanp/corerb/pull/148) Fix an issue with subclassed operations ([bryanp](https://github.com/bryanp))
7
+ * `dep` [#143](https://github.com/bryanp/corerb/pull/143) Deprecate `Is::*` and `Refine::*` namespaces ([bryanp](https://github.com/bryanp))
8
+ * `chg` [#140](https://github.com/bryanp/corerb/pull/140) Refactor operations to be single-action returning a result object ([bryanp](https://github.com/bryanp))
9
+ * `dep` [#135](https://github.com/bryanp/corerb/pull/135) Remove Ruby 2 support ([bryanp](https://github.com/bryanp))
10
+
11
+ ## [v0.1.1](https://github.com/bryanp/corerb/releases/tag/2022-01-13)
2
12
 
3
13
  *released on 2022-01-13*
4
14
 
5
- * `fix` [#120](https://github.com/metabahn/corerb/pull/120) Explicitly require a dependency ([bryanp](https://github.com/bryanp))
15
+ * `fix` [#120](https://github.com/bryanp/corerb/pull/120) Explicitly require a dependency ([bryanp](https://github.com/bryanp))
6
16
 
7
- ## [v0.1.0](https://github.com/metabahn/corerb/releases/tag/2021-11-03)
17
+ ## [v0.1.0](https://github.com/bryanp/corerb/releases/tag/2021-11-03)
8
18
 
9
19
  *released on 2021-11-03*
10
20
 
11
- * `add` [#104](https://github.com/metabahn/corerb/pull/104) Let operation result be turned into a hash ([bryanp](https://github.com/bryanp))
21
+ * `add` [#104](https://github.com/bryanp/corerb/pull/104) Let operation result be turned into a hash ([bryanp](https://github.com/bryanp))
12
22
 
13
- ## [v0.0.0](https://github.com/metabahn/corerb/releases/tag/2021-11-02)
23
+ ## [v0.0.0](https://github.com/bryanp/corerb/releases/tag/2021-11-02)
14
24
 
15
25
  *released on 2021-11-02*
16
26
 
17
- * `add` [#90](https://github.com/metabahn/corerb/pull/90) Initial implementation of the core-operation gem ([bryanp](https://github.com/bryanp))
27
+ * `add` [#90](https://github.com/bryanp/corerb/pull/90) Initial implementation of the core-operation gem ([bryanp](https://github.com/bryanp))
18
28
 
19
29
 
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "core/inspect"
4
+ require "core/state"
5
+ require "securerandom"
6
+
7
+ module Core
8
+ module Operation
9
+ # [public] An operation action.
10
+ #
11
+ class Action
12
+ include Core::State
13
+ state :__used_random_suffixes__, default: []
14
+
15
+ class << self
16
+ # [public] Builds an action for a given target.
17
+ #
18
+ def build(target = nil, *args, &block)
19
+ raise ArgumentError, "actions must be defined with a block" unless block
20
+
21
+ Actions::Block.new(target, &block)
22
+ end
23
+
24
+ def build_reference
25
+ suffix = generate_random_suffix
26
+ if __used_random_suffixes__.include?(suffix)
27
+ build_reference
28
+ else
29
+ __used_random_suffixes__ << suffix
30
+ "action_#{suffix}"
31
+ end
32
+ end
33
+
34
+ def generate_random_suffix
35
+ SecureRandom.hex(8)
36
+ end
37
+ end
38
+
39
+ include Core::Inspect
40
+
41
+ def initialize(name)
42
+ @name = (name || :call).to_sym
43
+ @reference = self.class.build_reference.to_sym
44
+ end
45
+
46
+ # [public] The action name.
47
+ #
48
+ attr_reader :name
49
+
50
+ # [public] The action reference.
51
+ #
52
+ attr_reader :reference
53
+
54
+ # [public] Finalizes the action for the given context.
55
+ #
56
+ def finalize(context)
57
+ raise "not implemented"
58
+ end
59
+
60
+ require_relative "actions/block"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "core/inspect"
4
+
5
+ module Core
6
+ module Operation
7
+ module Actions
8
+ # [public] An operation action defined as a block.
9
+ #
10
+ class Block < Action
11
+ include Core::Inspect
12
+
13
+ def initialize(name = nil, &block)
14
+ @block = block
15
+
16
+ super(name)
17
+ end
18
+
19
+ # [public]
20
+ #
21
+ def finalize(context)
22
+ context.define_method(@reference, &@block)
23
+
24
+ @reference
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "action"
4
+ require_relative "compiler"
5
+
6
+ module Core
7
+ module Operation
8
+ class Callable
9
+ def initialize(object)
10
+ @object = object
11
+ @actions = []
12
+ @compiled = false
13
+ @compiler = Compiler
14
+ end
15
+
16
+ def initialize_copy(...)
17
+ @actions = @actions.clone
18
+ super
19
+ end
20
+
21
+ attr_reader :actions
22
+
23
+ # [public] Relocates to another object context.
24
+ #
25
+ def relocate(object)
26
+ @object = object
27
+ end
28
+
29
+ # [public] Defines an action.
30
+ #
31
+ def action(...)
32
+ @actions << Action.build(...)
33
+ recompile if compiled?
34
+ end
35
+
36
+ # [public]
37
+ #
38
+ def action?(name)
39
+ @actions.any? { |action| action.name == name.to_sym }
40
+ end
41
+
42
+ # [public] Calls the default operation action with the given arguments.
43
+ #
44
+ def call(object, ...)
45
+ finalize.call(object, ...)
46
+ end
47
+
48
+ # [public]
49
+ #
50
+ def invoke(object, name, ...)
51
+ finalize.invoke(object, name, ...)
52
+ end
53
+
54
+ # [public] Finalizes the operation.
55
+ #
56
+ def finalize
57
+ compile
58
+
59
+ self
60
+ end
61
+
62
+ private def compile
63
+ return if compiled?
64
+
65
+ instance_eval(@compiler.compile(self, @object), __FILE__, __LINE__ - 1)
66
+ @compiled = true
67
+ end
68
+
69
+ private def recompile
70
+ remove_compiled_methods
71
+ compile
72
+ end
73
+
74
+ private def compiled?
75
+ @compiled == true
76
+ end
77
+
78
+ private def remove_compiled_methods
79
+ return unless compiled?
80
+
81
+ singleton_class.remove_method(:call)
82
+ singleton_class.remove_method(:invoke)
83
+ @compiled = false
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,24 +1,72 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "core/pipeline/compiler"
4
-
5
- require_relative "result"
3
+ require_relative "errors/undefined_action_error"
6
4
 
7
5
  module Core
8
6
  module Operation
9
- class Compiler < Pipeline::Compiler
7
+ # [public] Compiles an operation into evalable code.
8
+ #
9
+ class Compiler
10
10
  class << self
11
- private def compile_call(callable, object)
12
- code = +"result = Core::Operation::Result.new\n"
13
- code += "object.localize(:result, result) do\n"
14
- code += super
15
- code += "end\n"
16
- code += "result.finalize\n"
17
- code
11
+ # [public] Compiles a callable operation in context of an object.
12
+ #
13
+ def compile(callable, object)
14
+ compiled = <<~CODE
15
+ #{compile_actions(callable, object)}
16
+ def invoke(object, name, ...)
17
+ __send__(name, object, ...)
18
+ end
19
+ CODE
20
+
21
+ unless callable.actions.any? { |action| action.name == :call }
22
+ compiled << <<~CODE
23
+ def call(...)
24
+ raise UndefinedActionError, "undefined action `call`"
25
+ end
26
+ CODE
27
+ end
28
+
29
+ compiled
30
+ end
31
+
32
+ # [public]
33
+ #
34
+ private def compile_actions(callable, object)
35
+ compiled = +""
36
+ finalized_actions(callable, object).each_value do |action|
37
+ compiled << compile_action(action, object)
38
+ end
39
+
40
+ compiled
18
41
  end
19
42
 
43
+ # [public]
44
+ #
20
45
  private def compile_action(action, object)
21
- "result.set(#{action[:object].name.inspect}, #{super})\n"
46
+ action_body = case action[:finalized]
47
+ when Symbol
48
+ "object.#{action[:finalized]}(...)"
49
+ end
50
+
51
+ <<~CODE
52
+ def #{action[:object].name}(object, ...)
53
+ #{action_body}
54
+ end
55
+ CODE
56
+ end
57
+
58
+ private def finalized_actions(callable, object)
59
+ finalized_actions = build_finalized_actions(callable, object)
60
+ callable.instance_variable_set(:@__finalized_actions__, finalized_actions)
61
+ end
62
+
63
+ private def build_finalized_actions(callable, object)
64
+ callable.actions.map { |action|
65
+ finalized = action.finalize(object)
66
+ {object: action, finalized: finalized, object_id: finalized.object_id}
67
+ }.each_with_object({}) { |action, lookup|
68
+ lookup[action[:object_id]] = action
69
+ }
22
70
  end
23
71
  end
24
72
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Operation
5
+ class UndefinedActionError < NameError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Operation
5
+ # [public]
6
+ #
7
+ class Failed < RuntimeError
8
+ def initialize(value)
9
+ @value = value
10
+ end
11
+
12
+ # [public]
13
+ #
14
+ attr_reader :value
15
+ end
16
+ end
17
+ end
@@ -1,34 +1,66 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "failed"
4
+
3
5
  module Core
4
6
  module Operation
7
+ # [public]
8
+ #
5
9
  class Result
10
+ include Core::Inspect
11
+ inspects :value, :failure
12
+
6
13
  def initialize
7
- @values = {}
8
- @latest = nil
14
+ @value = nil
15
+ @failure = nil
16
+ @succeeded = false
17
+ @failed = false
9
18
  end
10
19
 
11
- def finalize
12
- @values.freeze
13
- freeze
14
- self
15
- end
20
+ # [public]
21
+ #
22
+ attr_reader :failure
16
23
 
24
+ # [public]
25
+ #
17
26
  def value
18
- @latest
27
+ raise Failed.new(@failure) if failure?
28
+ @value
19
29
  end
20
30
 
21
- def set(name, value)
22
- @values[name.to_sym] = value
23
- @latest = value
31
+ # [public]
32
+ #
33
+ def succeeded(value)
34
+ @value = value
35
+ @succeeded = true
36
+ finalize
24
37
  end
25
38
 
26
- def get(name)
27
- @values[name.to_sym]
39
+ # [public]
40
+ #
41
+ def failed(value)
42
+ @failure = value
43
+ @failed = true
44
+ finalize
28
45
  end
29
46
 
30
- def to_hash
31
- @values.dup
47
+ # [public]
48
+ #
49
+ def success?
50
+ @succeeded == true
51
+ end
52
+
53
+ # [public]
54
+ #
55
+ def failure?
56
+ @failed == true
57
+ end
58
+
59
+ # [public]
60
+ #
61
+ def finalize
62
+ freeze
63
+ self
32
64
  end
33
65
  end
34
66
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Core
4
4
  module Operation
5
- VERSION = "0.1.1"
5
+ VERSION = "0.2.0"
6
6
 
7
7
  # [public]
8
8
  #
@@ -1,7 +1,99 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "core/extension"
4
+ require "core/local"
5
+ require "core/state"
6
+
3
7
  module Core
8
+ # [public]
9
+ #
4
10
  module Operation
11
+ require_relative "operation/callable"
12
+ require_relative "operation/result"
5
13
  require_relative "operation/version"
14
+
15
+ extend Core::Extension
16
+
17
+ applies do |extended:|
18
+ # [public] The callable operation.
19
+ #
20
+ state :operation, default: Core::Operation::Callable.new(extended ? singleton_class : self)
21
+ end
22
+
23
+ extends dependencies: [
24
+ Core::Local,
25
+ Core::State
26
+ ]
27
+
28
+ extends :definition do
29
+ # [public] Defines an action.
30
+ #
31
+ def action(...)
32
+ operation.action(...)
33
+ end
34
+
35
+ def inherited(subclass)
36
+ super
37
+
38
+ subclass.operation.relocate(subclass)
39
+ end
40
+ end
41
+
42
+ extends :implementation do
43
+ # [public] Calls the default action with arguments.
44
+ #
45
+ def call(...)
46
+ call_operation do
47
+ @operation.call(self, ...)
48
+ end
49
+ end
50
+
51
+ # [public] Defines an action, isolated to the instance.
52
+ #
53
+ def action(...)
54
+ operation.relocate(singleton_class)
55
+ operation.action(...)
56
+ end
57
+
58
+ # [public]
59
+ #
60
+ def failed(...)
61
+ localized(:__core_operation_result__).failed(...)
62
+ end
63
+
64
+ def method_missing(name, ...)
65
+ if @operation.action?(name)
66
+ call_operation do
67
+ @operation.invoke(self, name, ...)
68
+ end
69
+ else
70
+ super
71
+ end
72
+ end
73
+
74
+ def respond_to_missing?(name, ...)
75
+ @operation.action?(name) || super
76
+ end
77
+
78
+ private def call_operation
79
+ localize(:__core_operation_result__, Core::Operation::Result.new) { |result|
80
+ value = yield
81
+ unless result.failure?
82
+ result.succeeded(value)
83
+ end
84
+ result
85
+ }
86
+ end
87
+ end
88
+
89
+ extends :implementation, prepend: true do
90
+ # [public]
91
+ #
92
+ def finalize(...)
93
+ super if defined?(super)
94
+ operation.finalize
95
+ self
96
+ end
97
+ end
6
98
  end
7
99
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: core-operation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.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: 2022-01-14 00:00:00.000000000 Z
11
+ date: 2023-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: core-extension
@@ -16,30 +16,44 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.4'
19
+ version: '0.5'
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.4'
26
+ version: '0.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: core-inspect
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: core-local
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '0.1'
47
+ version: '0.2'
34
48
  type: :runtime
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '0.1'
54
+ version: '0.2'
41
55
  - !ruby/object:Gem::Dependency
42
- name: core-pipeline
56
+ name: core-state
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
@@ -53,7 +67,7 @@ dependencies:
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0.2'
55
69
  description: Turns Ruby objects into operations.
56
- email: bryan@metabahn.com
70
+ email: bryan@bryanp.org
57
71
  executables: []
58
72
  extensions: []
59
73
  extra_rdoc_files: []
@@ -61,11 +75,15 @@ files:
61
75
  - CHANGELOG.md
62
76
  - LICENSE
63
77
  - lib/core/operation.rb
78
+ - lib/core/operation/action.rb
79
+ - lib/core/operation/actions/block.rb
80
+ - lib/core/operation/callable.rb
64
81
  - lib/core/operation/compiler.rb
82
+ - lib/core/operation/errors/undefined_action_error.rb
83
+ - lib/core/operation/failed.rb
65
84
  - lib/core/operation/result.rb
66
85
  - lib/core/operation/version.rb
67
- - lib/is/operation.rb
68
- homepage: https://github.com/metabahn/corerb/
86
+ homepage: https://github.com/bryanp/corerb/
69
87
  licenses:
70
88
  - MPL-2.0
71
89
  metadata: {}
@@ -77,14 +95,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
95
  requirements:
78
96
  - - ">="
79
97
  - !ruby/object:Gem::Version
80
- version: '2.7'
98
+ version: '3.0'
81
99
  required_rubygems_version: !ruby/object:Gem::Requirement
82
100
  requirements:
83
101
  - - ">="
84
102
  - !ruby/object:Gem::Version
85
103
  version: '0'
86
104
  requirements: []
87
- rubygems_version: 3.3.3
105
+ rubygems_version: 3.5.1
88
106
  signing_key:
89
107
  specification_version: 4
90
108
  summary: Turns Ruby objects into operations.
data/lib/is/operation.rb DELETED
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "is/extension"
4
- require "is/localized"
5
- require "is/pipeline"
6
-
7
- require_relative "../core/operation/compiler"
8
- require_relative "../core/operation/result"
9
-
10
- module Is
11
- # [public]
12
- #
13
- module Operation
14
- extend Is::Extension
15
-
16
- applies do
17
- pipeline.compiler = Core::Operation::Compiler
18
- end
19
-
20
- extends dependencies: [Is::Localized, Is::Pipeline]
21
-
22
- extends :definition, :implementation do
23
- # [public]
24
- #
25
- def action(*args, curry: true, **kwargs, &block)
26
- super(*args, curry: curry, **kwargs)
27
- end
28
- end
29
-
30
- extends :implementation do
31
- # [public]
32
- #
33
- def get(name)
34
- localized(:result).get(name)
35
- end
36
-
37
- # [public]
38
- #
39
- def set(name, value)
40
- localized(:result).set(name, value)
41
- end
42
- end
43
- end
44
- end