leto 2.0.0 → 2.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 +4 -4
- data/.rubocop.yml +6 -0
- data/CHANGELOG.md +7 -1
- data/Gemfile +2 -3
- data/README.md +4 -2
- data/lib/leto/call.rb +63 -52
- data/lib/leto/utils.rb +17 -5
- data/lib/leto/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ed0d190b234b4976a219ba5b50a3e9cb4f394be7b1a27aa2f1d7bdfe93887b0
|
4
|
+
data.tar.gz: eafafd24510514cf7ba4e59cdf1a967c7464d175b8ceed1f3cafa2e78fffcc1e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79b0be7884c86be4a14977e5aaae6147da85b857f632013bd82f9fe95addf28a4014ea7adeba1c75dfc63b9988ab7426c622cb79bc2c2886dca04be246ee2db5
|
7
|
+
data.tar.gz: 961a19be85b69aac0763828648c09100af4a9f84eff07a45e2b9c0c247c84e5b7ae9647a9bd4a7de791eb7cd0f0342b91f82d2cd50a85286425e4a8f915bd2fe
|
data/.rubocop.yml
CHANGED
@@ -21,8 +21,14 @@ Lint/AmbiguousBlockAssociation:
|
|
21
21
|
Lint/AmbiguousOperatorPrecedence:
|
22
22
|
Enabled: false
|
23
23
|
|
24
|
+
Style/DocumentDynamicEvalDefinition:
|
25
|
+
Enabled: false
|
26
|
+
|
24
27
|
Style/FetchEnvVar:
|
25
28
|
Enabled: false
|
26
29
|
|
27
30
|
Style/FrozenStringLiteralComment:
|
28
31
|
Enabled: false
|
32
|
+
|
33
|
+
Style/SymbolProc:
|
34
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [2.1.0] - 2023-05-20
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- `::call`, `::trace` and `::deep_dup` support for Data feature of Ruby 3.2
|
8
|
+
|
3
9
|
## [2.0.0] - 2023-02-27
|
4
10
|
|
5
11
|
### Changed
|
6
12
|
|
7
|
-
- removed support for
|
13
|
+
- removed support for getting paths by calling `::call` with a two-arg block
|
8
14
|
|
9
15
|
### Added
|
10
16
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# Leto
|
2
2
|
|
3
3
|
[](http://badge.fury.io/rb/leto)
|
4
|
-
[](https://github.com/jaynetics/leto/actions)
|
4
|
+
[](https://github.com/jaynetics/leto/actions)
|
5
|
+
[](https://codecov.io/gh/jaynetics/leto)
|
5
6
|
|
6
7
|
A generic object traverser for Ruby (named after the Greek [childbearing goddess Leto](https://www.theoi.com/Titan/TitanisLeto.html)).
|
7
8
|
|
@@ -12,6 +13,7 @@ Takes an object and recursively yields:
|
|
12
13
|
- Hash keys and values
|
13
14
|
- Enumerable members
|
14
15
|
- Struct members
|
16
|
+
- [Data](https://docs.ruby-lang.org/en/3.2/Data.html) members
|
15
17
|
- Range begins and ends
|
16
18
|
|
17
19
|
This makes stuff like deep-freezing fairly easy to implement:
|
@@ -79,7 +81,7 @@ Leto.dig(object, [[:[], 0], [:[], :a], [:[], 1], [:end]]) # => "d"
|
|
79
81
|
- [`Leto.deep_eql?(obj1, obj2)`](https://github.com/search?q=deep_eql+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
80
82
|
- stricter version of `#eql?` that takes all ivars into consideration
|
81
83
|
- [`Leto.deep_dup(obj)`](https://github.com/search?q=deep_dup+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
82
|
-
- more thorough than `active_support` or `deep_dup` gems, e.g. dups ivars
|
84
|
+
- more thorough than [`active_support`](https://www.rubydoc.info/search/gems/activesupport?q=deep_dup) or [`deep_dup`](https://github.com/ollie/deep_dup) gems, e.g. dups ivars
|
83
85
|
- [`Leto.shared_mutable_state?(obj1, obj2)`](https://github.com/search?q=shared_mutable_state+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
84
86
|
- useful for debugging or verifying that a `#dup` implementation is sane
|
85
87
|
- [`Leto.shared_mutables(obj1, obj2)`](https://github.com/search?q=shared_mutables+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
data/lib/leto/call.rb
CHANGED
@@ -12,70 +12,81 @@ module Leto
|
|
12
12
|
call(obj, max_depth: max_depth, path: path || Path.new(start: obj), &block)
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
return if seen[obj] || max_depth&.<(depth)
|
15
|
+
instance_eval <<-RUBY, __FILE__, __LINE__ + 1
|
17
16
|
|
18
|
-
|
19
|
-
depth += 1
|
17
|
+
private
|
20
18
|
|
21
|
-
|
19
|
+
def traverse(obj, path, depth, max_depth, seen, block)
|
20
|
+
return if seen[obj] || max_depth&.<(depth)
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
obj.instance_variable_get(ivar_name),
|
26
|
-
path&.+([[:instance_variable_get, ivar_name]]),
|
27
|
-
depth, max_depth, seen, block
|
28
|
-
)
|
29
|
-
end
|
22
|
+
seen[obj] = true
|
23
|
+
depth += 1
|
30
24
|
|
31
|
-
|
32
|
-
|
33
|
-
obj.
|
34
|
-
traverse(k, path&.+([[:keys], [:[], i]]), depth, max_depth, seen, block)
|
35
|
-
traverse(obj[k], path&.+([[:[], k]]), depth, max_depth, seen, block)
|
36
|
-
end
|
37
|
-
when Module
|
38
|
-
obj.class_variables.each do |cvar_name|
|
39
|
-
traverse(
|
40
|
-
obj.class_variable_get(cvar_name),
|
41
|
-
path&.+([[:class_variable_get, cvar_name]]),
|
42
|
-
depth, max_depth, seen, block
|
43
|
-
)
|
44
|
-
end
|
45
|
-
obj.constants.each do |const_name|
|
25
|
+
path ? block.call(obj, path) : block.call(obj)
|
26
|
+
|
27
|
+
obj.instance_variables.each do |ivar_name|
|
46
28
|
traverse(
|
47
|
-
obj.
|
48
|
-
path&.+([[:
|
29
|
+
obj.instance_variable_get(ivar_name),
|
30
|
+
path&.+([[:instance_variable_get, ivar_name]]),
|
49
31
|
depth, max_depth, seen, block
|
50
32
|
)
|
51
33
|
end
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
34
|
+
|
35
|
+
case obj
|
36
|
+
when Hash
|
37
|
+
obj.keys.each_with_index do |k, i|
|
38
|
+
traverse(k, path&.+([[:keys], [:[], i]]), depth, max_depth, seen, block)
|
39
|
+
traverse(obj[k], path&.+([[:[], k]]), depth, max_depth, seen, block)
|
40
|
+
end
|
41
|
+
when Module
|
42
|
+
obj.class_variables.each do |cvar_name|
|
43
|
+
traverse(
|
44
|
+
obj.class_variable_get(cvar_name),
|
45
|
+
path&.+([[:class_variable_get, cvar_name]]),
|
46
|
+
depth, max_depth, seen, block
|
47
|
+
)
|
48
|
+
end
|
49
|
+
obj.constants.each do |const_name|
|
50
|
+
traverse(
|
51
|
+
obj.const_get(const_name),
|
52
|
+
path&.+([[:const_get, const_name]]),
|
53
|
+
depth, max_depth, seen, block
|
54
|
+
)
|
55
|
+
end
|
56
|
+
when Range
|
57
|
+
traverse(obj.begin, path&.+([[:begin]]), depth, max_depth, seen, block)
|
58
|
+
traverse(obj.end, path&.+([[:end]]), depth, max_depth, seen, block)
|
59
|
+
when Struct
|
60
|
+
obj.members.each do |member|
|
61
|
+
traverse(obj[member], path&.+([[:[], member]]), depth, max_depth, seen, block)
|
62
|
+
end
|
63
|
+
when Enumerable
|
64
|
+
obj.each_with_index do |el, idx|
|
65
|
+
traverse(el, path&.+([[:[], idx]]), depth, max_depth, seen, block)
|
66
|
+
end
|
67
|
+
#{
|
68
|
+
defined?(Data) && Data.respond_to?(:define) && <<-DATA_FEATURE_RUBY
|
69
|
+
when Data
|
70
|
+
obj.members.each do |member|
|
71
|
+
traverse(
|
72
|
+
obj.send(member),
|
73
|
+
path&.+([[:send, member]]),
|
74
|
+
depth, max_depth, seen, block
|
75
|
+
)
|
76
|
+
end
|
77
|
+
DATA_FEATURE_RUBY
|
78
|
+
}
|
62
79
|
end
|
63
80
|
end
|
64
|
-
end
|
65
|
-
private_class_method :traverse
|
66
81
|
|
67
|
-
if RUBY_VERSION.to_f > 2.7
|
68
|
-
def self.build_seen_hash
|
69
|
-
{}.tap(&:compare_by_identity)
|
70
|
-
end
|
71
|
-
else
|
72
82
|
# ignore leaky constants in old rubies
|
73
|
-
def
|
74
|
-
hash = {}
|
75
|
-
hash
|
76
|
-
hash[::Etc::
|
77
|
-
hash[::
|
83
|
+
def build_seen_hash
|
84
|
+
hash = {}
|
85
|
+
hash.compare_by_identity
|
86
|
+
#{'hash[::Etc::Group] = true' if defined?(::Etc::Group)}
|
87
|
+
#{'hash[::Etc::Passwd] = true' if defined?(::Etc::Passwd)}
|
88
|
+
#{'hash[::Process::Tms] = true' if defined?(::Process::Tms)}
|
78
89
|
hash
|
79
90
|
end
|
80
|
-
|
91
|
+
RUBY
|
81
92
|
end
|
data/lib/leto/utils.rb
CHANGED
@@ -24,21 +24,27 @@ module Leto
|
|
24
24
|
return obj if IMMUTABLE_CLASSES.include?(obj.class) || !duplicable?(obj) ||
|
25
25
|
(!include_modules && obj.is_a?(Module))
|
26
26
|
|
27
|
-
|
27
|
+
copy = obj.dup
|
28
|
+
|
29
|
+
trace(obj, max_depth: 1).each do |el, path|
|
28
30
|
method, *args = path.steps[0]
|
29
31
|
case method
|
30
32
|
when :instance_variable_get
|
31
33
|
copy.instance_variable_set(*args, deep_dup(el, include_modules: include_modules))
|
32
34
|
when :[]
|
33
35
|
copy[*args] = deep_dup(el, include_modules: include_modules)
|
36
|
+
when :send # Data
|
37
|
+
copy = copy.with(args[0] => deep_dup(el, include_modules: include_modules))
|
34
38
|
when :begin
|
35
39
|
return Range.new(deep_dup(obj.begin), deep_dup(obj.end), obj.exclude_end?)
|
36
40
|
end
|
37
41
|
end
|
42
|
+
|
43
|
+
copy
|
38
44
|
end
|
39
45
|
|
40
46
|
def self.shared_mutable_state?(obj1, obj2)
|
41
|
-
|
47
|
+
each_shared_object(obj1, obj2, filter: method(:mutable?)).any?
|
42
48
|
end
|
43
49
|
|
44
50
|
# returns [[shared_object, path1, path2], ...], e.g.:
|
@@ -50,16 +56,22 @@ module Leto
|
|
50
56
|
# ["bar", [[:[], 1]], [[:[], 0]]]
|
51
57
|
# ]
|
52
58
|
def self.shared_mutables(obj1, obj2)
|
53
|
-
|
59
|
+
each_shared_object(obj1, obj2, filter: method(:mutable?)).to_a
|
54
60
|
end
|
55
61
|
|
56
62
|
def self.shared_objects(obj1, obj2, filter: nil)
|
63
|
+
each_shared_object(obj1, obj2, filter: filter).to_a
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.each_shared_object(obj1, obj2, filter: nil)
|
67
|
+
block_given? or return enum_for(__method__, obj1, obj2, filter: filter)
|
68
|
+
|
57
69
|
obj2_els_with_path = trace(obj2).to_a
|
58
|
-
trace(obj1).
|
70
|
+
trace(obj1).each do |el1, path1|
|
59
71
|
next if filter && !filter.call(el1)
|
60
72
|
|
61
73
|
obj2_els_with_path.reject do |el2, path2|
|
62
|
-
|
74
|
+
yield(el1, path1, path2) if el1.equal?(el2)
|
63
75
|
end
|
64
76
|
end
|
65
77
|
end
|
data/lib/leto/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: leto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janosch Müller
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -53,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
requirements: []
|
56
|
-
rubygems_version: 3.4.
|
56
|
+
rubygems_version: 3.4.13
|
57
57
|
signing_key:
|
58
58
|
specification_version: 4
|
59
59
|
summary: Generic object traverser
|