defi 3.0.1 → 4.0.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 +4 -4
- data/LICENSE.md +1 -1
- data/README.md +38 -45
- data/lib/defi/challenge.rb +71 -0
- data/lib/defi/value.rb +20 -68
- data/lib/defi.rb +10 -29
- metadata +13 -9
- data/lib/defi/method.rb +0 -105
- data/lib/kernel.rb +0 -45
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 147abcfe45309cf8e5e5df3cf02dbcaeaccd2c421841ab017844206dd1218ed1
|
|
4
|
+
data.tar.gz: 8d82cceb4e3e68f29ee6ae8632369cf9c4d2bdfdc3d3ba1a1cd3de5659cad8bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c0076455aa06379af57797aff1c160fce9bd1d930f45702a95eea4836b0c77270f9bcdac8cb4f00b4c5fe364766cb0a8a07b3f4c0800606366df9408debe882
|
|
7
|
+
data.tar.gz: '0379b004d430b65a97b2f56d2a968457ec37a41510a08f253899060e7645937b7c29e234e2d6a9bbca75b991edde71485ade15d64cb0f61a7b348fd750d2e092'
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
|
@@ -4,78 +4,71 @@
|
|
|
4
4
|
[](https://rubydoc.info/github/fixrb/defi/main)
|
|
5
5
|
[](https://github.com/fixrb/defi/raw/main/LICENSE.md)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
Rather than representing method signatures themselves, Defi focuses on providing a way to furnish methods with appropriate arguments, thereby preparing them for invocation.
|
|
9
|
-
|
|
10
|
-
This approach serves as an alternative to traditional methods like `Object#method` and `UnboundMethod`.
|
|
11
|
-
Where `Object#method` is bound to a specific object and `UnboundMethod` requires compatibility with the method’s originating class, Defi offers a more flexible and universal way to prepare method calls.
|
|
12
|
-
It allows method arguments to be specified in advance and then applied to any compatible object – those equipped with corresponding methods.
|
|
13
|
-
|
|
14
|
-
Defi is particularly useful in scenarios where you need to apply a set of method arguments across different objects to observe the varying outcomes, whether they be returned values or exceptions.
|
|
15
|
-
This makes it an ideal tool for testing, method comparison across different implementations, or any situation where method behavior needs to be assessed dynamically across various objects.
|
|
7
|
+
Defi prepares a method call, a name with its arguments, and sends it to any object, keeping what came back: the value returned, or the exception raised. The same call can be sent to as many objects as you like, to compare what each one does with it.
|
|
16
8
|
|
|
17
9
|
## Installation
|
|
18
10
|
|
|
19
|
-
Add this line to your application's Gemfile:
|
|
20
|
-
|
|
21
11
|
```ruby
|
|
12
|
+
# Gemfile
|
|
22
13
|
gem "defi"
|
|
23
14
|
```
|
|
24
15
|
|
|
25
|
-
|
|
16
|
+
Defi requires Ruby 3.3 or later, and depends on nothing.
|
|
26
17
|
|
|
27
|
-
|
|
28
|
-
bundle install
|
|
29
|
-
```
|
|
18
|
+
## Usage
|
|
30
19
|
|
|
31
|
-
|
|
20
|
+
A challenge is a method name, with positional arguments, keyword arguments and a block, as you would write them after a dot:
|
|
32
21
|
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
```
|
|
22
|
+
```ruby
|
|
23
|
+
require "defi"
|
|
36
24
|
|
|
37
|
-
|
|
25
|
+
challenge = Defi::Challenge.new(:fetch, :b, 0)
|
|
26
|
+
challenge.to_s # => ".fetch(:b, 0)"
|
|
27
|
+
```
|
|
38
28
|
|
|
39
|
-
|
|
40
|
-
Below are some detailed examples to demonstrate its versatility and ease of use:
|
|
29
|
+
Sent to an object with `to`, it brings back a value: what the method returned, or what it raised.
|
|
41
30
|
|
|
42
|
-
|
|
31
|
+
```ruby
|
|
32
|
+
value = challenge.to({ b: 2 })
|
|
33
|
+
value.raised? # => false
|
|
34
|
+
value.object # => 2
|
|
35
|
+
value.call # => 2
|
|
36
|
+
|
|
37
|
+
value = Defi::Challenge.new(:fetch, :b).to({})
|
|
38
|
+
value.raised? # => true
|
|
39
|
+
value.object # => #<KeyError: key not found: :b>
|
|
40
|
+
value.call # raises the KeyError again
|
|
41
|
+
```
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
With `Defi`, this can be elegantly done as follows:
|
|
43
|
+
Keyword arguments and the block go through as they are:
|
|
46
44
|
|
|
47
45
|
```ruby
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
Defi::Challenge.new(:encode, "US-ASCII", undef: :replace).to("café").object # => "caf?"
|
|
47
|
+
Defi::Challenge.new(:map) { |n| n * 2 }.to([1, 2]).object # => [2, 4]
|
|
50
48
|
```
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
The `.to(6)` method specifies that this challenge should be applied to the number `6`.
|
|
54
|
-
Finally, `.call` executes the challenge, yielding the result `42`.
|
|
50
|
+
## What comes back
|
|
55
51
|
|
|
56
|
-
|
|
52
|
+
The message is sent with `public_send`: a private method, or a `BasicObject`, which has no `public_send`, gives a `NoMethodError`. `public_send` protects the named method only: a challenge of `send` or `instance_eval`, which are public, reaches what they reach.
|
|
57
53
|
|
|
58
|
-
`
|
|
59
|
-
For instance:
|
|
54
|
+
A challenge keeps as its value the exceptions a method raises on its own: a `StandardError`, a `ScriptError` (such as `NotImplementedError`), a `SystemStackError`, or the `SystemExit` of an `exit` or an `abort`. What is not the method's doing goes through: an `Interrupt`, a `SignalException`, a `NoMemoryError`, the exception a `Timeout` uses to end its block (a `Timeout` given a class to raise, such as `RuntimeError`, is kept like any `StandardError`), and any other subclass of `Exception`. `Defi::Value::RAISED` lists the classes kept. `call` raises the exception again, the same object, with its own cause.
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
rescue NoMethodError => e
|
|
65
|
-
puts e.message # Output: undefined method `boom' for "foo":String
|
|
66
|
-
end
|
|
67
|
-
```
|
|
56
|
+
`to_s` writes the call as `.name(1, key: 2, &block)`: the arguments and the keys by their `inspect`, which runs their code and can change from one Ruby to the next (a Hash in 3.4, a Set in 4.0), the block as `&block`.
|
|
57
|
+
|
|
58
|
+
A challenge and a value are frozen. Their arguments are the objects given, not copies: a mutable argument stays shared with the caller.
|
|
68
59
|
|
|
69
|
-
|
|
70
|
-
`Defi` correctly raises a `NoMethodError`, showing that the method `boom` is undefined for a `String` object.
|
|
60
|
+
## Changes in 4.0
|
|
71
61
|
|
|
72
|
-
|
|
62
|
+
Compared with 3.0.1:
|
|
73
63
|
|
|
74
|
-
|
|
64
|
+
- `Defi::Method` becomes `Defi::Challenge`, a plain class, no longer a `BasicObject`, in `defi/challenge.rb`: `require "defi/method"` becomes `require "defi"`. `Kernel#Defi`, which every object received as a public method, is gone: write `Defi::Challenge.new`.
|
|
65
|
+
- `to` keeps the exceptions of the method only, where 3.0.1 caught every `Exception`, `Timeout` and `Interrupt` included. A name that is not a Symbol raises `ArgumentError`, as documented, with a new message; the `BasicObject` could not even `raise`. `call` keeps the cause of the exception it raises again.
|
|
66
|
+
- `Challenge#to_s` writes keyword arguments as `key: value` and the block as `&block`. `args` and `opts` are frozen. `Value.new` takes the object and whether it was raised; `Value::RAISE` and `Value::RETURN` are gone, as are `to_h` on both classes, `to_s` on a value and the custom `inspect` methods.
|
|
67
|
+
- Ruby 3.3 or later; tested on 3.3, 3.4, 4.0 and head.
|
|
75
68
|
|
|
76
69
|
## Versioning
|
|
77
70
|
|
|
78
|
-
|
|
71
|
+
Defi follows [Semantic Versioning 2.0](https://semver.org/).
|
|
79
72
|
|
|
80
73
|
## License
|
|
81
74
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "value"
|
|
4
|
+
|
|
5
|
+
module Defi
|
|
6
|
+
# A method call prepared in advance: a name, positional arguments, keyword
|
|
7
|
+
# arguments and a block, to send to any object with {#to}. A challenge is
|
|
8
|
+
# frozen, and sends its message as `public_send` does: a private method is
|
|
9
|
+
# a `NoMethodError`, while a public one that reaches further, such as
|
|
10
|
+
# `send` or `instance_eval`, does what it does.
|
|
11
|
+
class Challenge
|
|
12
|
+
# @return [Symbol] the name of the method
|
|
13
|
+
attr_reader :name
|
|
14
|
+
|
|
15
|
+
# @return [Array] the positional arguments, frozen
|
|
16
|
+
attr_reader :args
|
|
17
|
+
|
|
18
|
+
# @return [Hash] the keyword arguments, frozen
|
|
19
|
+
attr_reader :opts
|
|
20
|
+
|
|
21
|
+
# @return [Proc, nil] the block
|
|
22
|
+
attr_reader :block
|
|
23
|
+
|
|
24
|
+
# @param name [Symbol] the name of the method
|
|
25
|
+
# @param args [Array] the positional arguments
|
|
26
|
+
# @param opts [Hash] the keyword arguments
|
|
27
|
+
# @param block [Proc] the block
|
|
28
|
+
# @raise [ArgumentError] when the name is not a Symbol
|
|
29
|
+
def initialize(name, *args, **opts, &block)
|
|
30
|
+
raise ::ArgumentError, "the name of the method must be a Symbol" unless name.instance_of?(::Symbol)
|
|
31
|
+
|
|
32
|
+
@name = name
|
|
33
|
+
@args = args.freeze
|
|
34
|
+
@opts = opts.freeze
|
|
35
|
+
@block = block
|
|
36
|
+
freeze
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Sends the message to an object. What the method raises comes back as
|
|
40
|
+
# the value, as long as it is the method's own doing: a `StandardError`,
|
|
41
|
+
# a `ScriptError` (such as `NotImplementedError`), a `SystemStackError`
|
|
42
|
+
# or a `SystemExit`. An `Interrupt`, a signal, a `NoMemoryError` and the
|
|
43
|
+
# exception a `Timeout` uses to end the block go through.
|
|
44
|
+
#
|
|
45
|
+
# @param object [Object] the receiver; a `BasicObject` has no
|
|
46
|
+
# `public_send`, so the value is a `NoMethodError`
|
|
47
|
+
# @return [Defi::Value] what came back
|
|
48
|
+
def to(object)
|
|
49
|
+
Value.new(object.public_send(name, *args, **opts, &block), false)
|
|
50
|
+
rescue *Value::RAISED => e
|
|
51
|
+
Value.new(e, true)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [String] the call, as `.name(1, key: 2, &block)`: the arguments
|
|
55
|
+
# and the keys as their `inspect` writes them (user code, which runs
|
|
56
|
+
# here), the block as `&block`
|
|
57
|
+
def to_s
|
|
58
|
+
parts = args.map(&:inspect) + opts.map { |key, value| "#{keyword(key)} #{value.inspect}" }
|
|
59
|
+
parts << "&block" if block
|
|
60
|
+
parts.empty? ? ".#{name}" : ".#{name}(#{parts.join(", ")})"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# A keyword as Ruby writes it: `key:` for a Symbol, quoted when its
|
|
66
|
+
# inspect is, `"key" =>` otherwise.
|
|
67
|
+
def keyword(key)
|
|
68
|
+
key.instance_of?(::Symbol) ? "#{key.inspect[1..]}:" : "#{key.inspect} =>"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/defi/value.rb
CHANGED
|
@@ -1,85 +1,37 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Defi
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# instead of StandardError to maintain complete control over method execution
|
|
7
|
-
# outcomes, particularly in testing contexts. This ensures that system-level
|
|
8
|
-
# exceptions like SystemExit cannot prematurely terminate test execution with
|
|
9
|
-
# potentially false positive results.
|
|
4
|
+
# What came back from a challenge: the object returned, or the exception
|
|
5
|
+
# raised. A value is frozen.
|
|
10
6
|
class Value
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
# The exceptions a method raises on its own, which a challenge keeps as
|
|
8
|
+
# its value. Anything else, an interruption or a signal, a shortage of
|
|
9
|
+
# memory, the end of a `Timeout`, goes through.
|
|
10
|
+
RAISED = [::StandardError, ::ScriptError, ::SystemStackError, ::SystemExit].freeze
|
|
13
11
|
|
|
14
|
-
# @return [
|
|
12
|
+
# @return [Object] the object returned, or the exception raised
|
|
15
13
|
attr_reader :object
|
|
16
14
|
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# @example Exception handling
|
|
25
|
-
# value = Defi::Value.new { raise TypeError, "Invalid type" }
|
|
26
|
-
# value.raised? # => true
|
|
27
|
-
# value.object # => #<TypeError: Invalid type>
|
|
28
|
-
#
|
|
29
|
-
# @note This implementation catches Exception instead of StandardError
|
|
30
|
-
# to provide complete control over method execution outcomes,
|
|
31
|
-
# including system-level exceptions when needed.
|
|
32
|
-
#
|
|
33
|
-
# @yieldreturn [#object_id] Result or exception from block execution
|
|
34
|
-
def initialize
|
|
35
|
-
@object = yield
|
|
36
|
-
@raised = false
|
|
37
|
-
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
|
38
|
-
@object = e
|
|
39
|
-
@raised = true
|
|
15
|
+
# @param object [Object] the object returned, or the exception raised
|
|
16
|
+
# @param raised [Boolean] whether the method raised: `true` or `false`,
|
|
17
|
+
# as given
|
|
18
|
+
def initialize(object, raised)
|
|
19
|
+
@object = object
|
|
20
|
+
@raised = raised
|
|
21
|
+
freeze
|
|
40
22
|
end
|
|
41
23
|
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
# @example With successful result
|
|
45
|
-
# Value.new { "success" }.call # => "success"
|
|
46
|
-
#
|
|
47
|
-
# @example With exception
|
|
48
|
-
# Value.new { raise "error" }.call # raises RuntimeError: error
|
|
49
|
-
#
|
|
50
|
-
# @return [#object_id] The operation result
|
|
51
|
-
# @raise [Exception] The captured exception if raised? is true
|
|
52
|
-
def call
|
|
53
|
-
raise object if raised?
|
|
54
|
-
|
|
55
|
-
object
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# @return [Boolean] true if execution raised an exception
|
|
24
|
+
# @return [Boolean] whether the method raised
|
|
59
25
|
def raised?
|
|
60
26
|
@raised
|
|
61
27
|
end
|
|
62
28
|
|
|
63
|
-
# @return [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
# @return [Hash] Value properties
|
|
69
|
-
def to_h
|
|
70
|
-
{ raised: raised?, object: }
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# @return [String] String representation showing outcome type and value
|
|
74
|
-
def to_s
|
|
75
|
-
"#{raise_or_return} #{object}"
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
private
|
|
29
|
+
# @return [Object] the object returned
|
|
30
|
+
# @raise [Exception] the exception raised, again, with its own cause
|
|
31
|
+
def call
|
|
32
|
+
raise object, cause: object.cause if raised?
|
|
79
33
|
|
|
80
|
-
|
|
81
|
-
def raise_or_return
|
|
82
|
-
raised? ? RAISE : RETURN
|
|
34
|
+
object
|
|
83
35
|
end
|
|
84
36
|
end
|
|
85
37
|
end
|
data/lib/defi.rb
CHANGED
|
@@ -1,34 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# addition.to(1).call # => 3
|
|
12
|
-
#
|
|
13
|
-
# @example String manipulation
|
|
14
|
-
# upcase = Defi(:upcase)
|
|
15
|
-
# upcase.to("hello").call # => "HELLO"
|
|
16
|
-
#
|
|
17
|
-
# @example Method with keyword arguments
|
|
18
|
-
# format = Defi(:format, precision: 2)
|
|
19
|
-
# format.to(3.14159).call # => "3.14"
|
|
20
|
-
#
|
|
21
|
-
# @example Block usage
|
|
22
|
-
# map_double = Defi(:map) { |x| x * 2 }
|
|
23
|
-
# map_double.to([1, 2, 3]).call # => [2, 4, 6]
|
|
24
|
-
#
|
|
25
|
-
# @example Error handling
|
|
26
|
-
# result = Defi(:undefined_method).to(42)
|
|
27
|
-
# result.raised? # => true
|
|
28
|
-
# result.object # => #<NoMethodError: ...>
|
|
29
|
-
#
|
|
3
|
+
# Prepares a method call, a name with its arguments, and sends it to any
|
|
4
|
+
# object, keeping what came back: the value returned, or the exception
|
|
5
|
+
# raised.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# challenge = Defi::Challenge.new(:+, 2)
|
|
9
|
+
# challenge.to(40).object # => 42
|
|
10
|
+
# challenge.to("a").call # raises TypeError: no implicit conversion of Integer into String
|
|
30
11
|
module Defi
|
|
31
12
|
end
|
|
32
13
|
|
|
33
|
-
|
|
34
|
-
require_relative "
|
|
14
|
+
require_relative "defi/challenge"
|
|
15
|
+
require_relative "defi/value"
|
metadata
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: defi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cyril Kato
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-10-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
|
-
description:
|
|
13
|
+
description: 'Prepares a method call, a name with its arguments, and sends it to any
|
|
14
|
+
object, keeping what came back: the value returned, or the exception raised.'
|
|
14
15
|
email: contact@cyril.email
|
|
15
16
|
executables: []
|
|
16
17
|
extensions: []
|
|
@@ -19,14 +20,17 @@ files:
|
|
|
19
20
|
- LICENSE.md
|
|
20
21
|
- README.md
|
|
21
22
|
- lib/defi.rb
|
|
22
|
-
- lib/defi/
|
|
23
|
+
- lib/defi/challenge.rb
|
|
23
24
|
- lib/defi/value.rb
|
|
24
|
-
|
|
25
|
-
homepage: https://github.com/fixrb/defi
|
|
25
|
+
homepage: https://fixrb.dev/gems/defi/
|
|
26
26
|
licenses:
|
|
27
27
|
- MIT
|
|
28
28
|
metadata:
|
|
29
|
+
bug_tracker_uri: https://github.com/fixrb/defi/issues
|
|
30
|
+
changelog_uri: https://github.com/fixrb/defi#changes-in-40
|
|
31
|
+
documentation_uri: https://rubydoc.info/gems/defi
|
|
29
32
|
rubygems_mfa_required: 'true'
|
|
33
|
+
source_code_uri: https://github.com/fixrb/defi
|
|
30
34
|
post_install_message:
|
|
31
35
|
rdoc_options: []
|
|
32
36
|
require_paths:
|
|
@@ -35,15 +39,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
35
39
|
requirements:
|
|
36
40
|
- - ">="
|
|
37
41
|
- !ruby/object:Gem::Version
|
|
38
|
-
version: 3.
|
|
42
|
+
version: 3.3.0
|
|
39
43
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
40
44
|
requirements:
|
|
41
45
|
- - ">="
|
|
42
46
|
- !ruby/object:Gem::Version
|
|
43
47
|
version: '0'
|
|
44
48
|
requirements: []
|
|
45
|
-
rubygems_version: 3.
|
|
49
|
+
rubygems_version: 3.5.22
|
|
46
50
|
signing_key:
|
|
47
51
|
specification_version: 4
|
|
48
|
-
summary:
|
|
52
|
+
summary: A method call prepared in advance, to send to any object
|
|
49
53
|
test_files: []
|
data/lib/defi/method.rb
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Defi
|
|
4
|
-
# Represents a method to be applied against an object.
|
|
5
|
-
# Encapsulates method name, arguments, keyword arguments and blocks
|
|
6
|
-
# for dynamic method invocation with proper error handling.
|
|
7
|
-
class Method < ::BasicObject
|
|
8
|
-
# Initialize a new Method object.
|
|
9
|
-
#
|
|
10
|
-
# @param name [Symbol] The method name
|
|
11
|
-
# @param args [Array] Positional arguments
|
|
12
|
-
# @param opts [Hash] Keyword arguments
|
|
13
|
-
# @param block [Proc] Optional block
|
|
14
|
-
#
|
|
15
|
-
# @raise [ArgumentError] If name is not a Symbol, raises with the actual class received
|
|
16
|
-
def initialize(name, *args, **opts, &block)
|
|
17
|
-
raise ::ArgumentError, "Method name must be a Symbol, got: #{name.class}" unless name.is_a?(::Symbol)
|
|
18
|
-
|
|
19
|
-
@name = name
|
|
20
|
-
@args = args
|
|
21
|
-
@opts = opts
|
|
22
|
-
@block = block
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Applies the method to the given object.
|
|
26
|
-
#
|
|
27
|
-
# @param object [#object_id] Target object for method invocation
|
|
28
|
-
# @return [Defi::Value] Result wrapper containing return value or exception
|
|
29
|
-
#
|
|
30
|
-
# @example Basic arithmetic
|
|
31
|
-
# Defi::Method.new(:+, 1).to(2).call # => 3
|
|
32
|
-
#
|
|
33
|
-
# @example Block usage
|
|
34
|
-
# Defi::Method.new(:map) { |x| x * 2 }.to([1, 2, 3]).call # => [2, 4, 6]
|
|
35
|
-
#
|
|
36
|
-
# @example Error handling
|
|
37
|
-
# result = Defi::Method.new(:undefined).to(42)
|
|
38
|
-
# result.raised? # => true
|
|
39
|
-
def to(object)
|
|
40
|
-
Value.new { object.public_send(@name, *@args, **@opts, &@block) }
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# @return [Hash] Method properties
|
|
44
|
-
def to_h
|
|
45
|
-
{
|
|
46
|
-
name: @name,
|
|
47
|
-
args: @args,
|
|
48
|
-
opts: @opts,
|
|
49
|
-
block: @block
|
|
50
|
-
}
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# @return [String] Human-readable representation
|
|
54
|
-
#
|
|
55
|
-
# @example
|
|
56
|
-
# Defi::Method.new(:+, 1).inspect
|
|
57
|
-
# # => "Defi(name: :+, args: [1], opts: {}, block: nil)"
|
|
58
|
-
def inspect
|
|
59
|
-
"Defi(" \
|
|
60
|
-
"name: #{@name.inspect}, " \
|
|
61
|
-
"args: #{@args.inspect}, " \
|
|
62
|
-
"opts: #{@opts.inspect}, " \
|
|
63
|
-
"block: #{@block.nil? ? "nil" : "<Proc>"})"
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# @return [String] String representation of the method call
|
|
67
|
-
#
|
|
68
|
-
# @example
|
|
69
|
-
# Defi::Method.new(:+, 1).to_s # => ".+(1)"
|
|
70
|
-
# Defi::Method.new(:map) { |x| x }.to_s # => ".map(<Proc>)"
|
|
71
|
-
def to_s
|
|
72
|
-
return ".#{@name}" if no_arguments?
|
|
73
|
-
|
|
74
|
-
".#{@name}(#{stringified_arguments})"
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
private
|
|
78
|
-
|
|
79
|
-
def no_arguments?
|
|
80
|
-
@args.empty? && @opts.empty? && @block.nil?
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def stringified_arguments
|
|
84
|
-
[
|
|
85
|
-
args_string,
|
|
86
|
-
opts_string,
|
|
87
|
-
block_string
|
|
88
|
-
].compact.join(", ")
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def args_string
|
|
92
|
-
@args.inspect[1..-2] unless @args.empty?
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def opts_string
|
|
96
|
-
@opts.inspect[1..-2] unless @opts.empty?
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def block_string
|
|
100
|
-
"<Proc>" if @block
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
require_relative "value"
|
data/lib/kernel.rb
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative File.join("defi", "method")
|
|
4
|
-
|
|
5
|
-
# Core Ruby module extended to provide the Defi method globally.
|
|
6
|
-
# This extension allows for convenient method encapsulation and
|
|
7
|
-
# delayed execution through the Defi method, accessible anywhere
|
|
8
|
-
# in the Ruby environment.
|
|
9
|
-
module Kernel
|
|
10
|
-
# rubocop:disable Naming/MethodName
|
|
11
|
-
|
|
12
|
-
# Creates a new Defi::Method instance.
|
|
13
|
-
# This method provides a flexible way to encapsulate a method name along with its
|
|
14
|
-
# arguments, keyword arguments, and an optional block for later invocation.
|
|
15
|
-
#
|
|
16
|
-
# @param method_name [Symbol] The method name to be sent to an object
|
|
17
|
-
# @param args [Array] Positional arguments to be passed to the method
|
|
18
|
-
# @param opts [Hash] Keyword arguments to be passed to the method
|
|
19
|
-
# @param block [Proc] Optional block to be passed to the method
|
|
20
|
-
#
|
|
21
|
-
# @example Basic method without arguments
|
|
22
|
-
# Defi(:to_s)
|
|
23
|
-
#
|
|
24
|
-
# @example Method with positional arguments
|
|
25
|
-
# Defi(:+, 2)
|
|
26
|
-
# Defi(:[], 0, 2) # For array/string slicing
|
|
27
|
-
#
|
|
28
|
-
# @example Method with keyword arguments
|
|
29
|
-
# Defi(:transform, x: 1, y: 2)
|
|
30
|
-
#
|
|
31
|
-
# @example Method with a block
|
|
32
|
-
# Defi(:map) { |x| x * 2 }
|
|
33
|
-
#
|
|
34
|
-
# @example Complex method call
|
|
35
|
-
# Defi(:reduce, 0, &:+) # Sum an array
|
|
36
|
-
#
|
|
37
|
-
# @raise [ArgumentError] If method_name is not a Symbol
|
|
38
|
-
#
|
|
39
|
-
# @return [Defi::Method] An instance of Defi::Method encapsulating the method call
|
|
40
|
-
def Defi(method_name, *args, **opts, &block)
|
|
41
|
-
::Defi::Method.new(method_name, *args, **opts, &block)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# rubocop:enable Naming/MethodName
|
|
45
|
-
end
|