leto 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/leto.svg)](http://badge.fury.io/rb/leto)
|
4
|
-
[![Build Status](https://github.com/jaynetics/leto/workflows/tests/badge.svg)](https://github.com/jaynetics/leto/actions)
|
4
|
+
[![Build Status](https://github.com/jaynetics/leto/actions/workflows/tests.yml/badge.svg)](https://github.com/jaynetics/leto/actions)
|
5
|
+
[![Coverage](https://codecov.io/gh/jaynetics/leto/branch/main/graph/badge.svg?token=0993K9I8VC)](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
|