core-operation 0.1.1 → 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
  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