leto 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile +4 -2
- data/README.md +11 -9
- data/Rakefile +7 -5
- data/leto.gemspec +1 -1
- data/lib/leto/call.rb +36 -19
- data/lib/leto/path.rb +5 -2
- data/lib/leto/utils.rb +39 -6
- 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: 2926017bd4fc2ec078b89e6d39157ed7e79e48693e82628665c3435c5c39b4f4
|
4
|
+
data.tar.gz: f8736c1e1e0a5e7a7b6c93d8c48c7393b6d554906245c5fcc71d05aa3d200789
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b13efbac9e6e200a43067a57b219a5128d69eea06a4a41a8fe2bd88d49932d0a10a60b38807443133300b86aaa9d3d75045e0bc340057d6d16564647b68c8ba
|
7
|
+
data.tar.gz: 476318c70ed5d9a1df6ab4abc67a9d3dad35a003585d34ec4bdc743f6990823db6dc5c3430a244ed12bc3c454cbb5a8e1937dc2ada58568c8a38ce1e018cbbb9
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [2.0.0] - 2023-02-27
|
4
|
+
|
5
|
+
### Changed
|
6
|
+
|
7
|
+
- removed support for gettings paths by calling `::call` with a two-arg block
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- `::deep_dup`
|
12
|
+
- `::trace`
|
13
|
+
- support for older Rubies
|
14
|
+
|
3
15
|
## [1.0.0] - 2023-02-24
|
4
16
|
|
5
17
|
- Initial release
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -50,8 +50,8 @@ Leto.call(object) { |el| p el }
|
|
50
50
|
Leto.call(object).to_a
|
51
51
|
# => [[{:a=>["b", ["c".."d"]]}], {:a=>["b", ["c".."d"]]}, :a, ...]
|
52
52
|
|
53
|
-
#
|
54
|
-
Leto.
|
53
|
+
# Leto::trace behaves like ::call, but also yields each (sub-)object's path:
|
54
|
+
Leto.trace(object) { |el, path| puts "#{el.inspect.ljust(23)} @#{path}" }
|
55
55
|
# prints:
|
56
56
|
#
|
57
57
|
# [{:a=>["b", "c".."d"]}] @#<Leto::Path [{:a=>["b", "c".."d"]}]>
|
@@ -64,7 +64,7 @@ Leto.call(object) { |el, path| puts "#{el.inspect.ljust(23)} @#{path}" }
|
|
64
64
|
# "d" @#<Leto::Path [{:a=>["b", "c".."d"]}][0][:a][1].end>
|
65
65
|
|
66
66
|
# paths can be looked up with Leto::Path#resolve or Leto::dig
|
67
|
-
path = Leto.
|
67
|
+
path = Leto.trace(object).map { |_el, path| path }.last # => #<Leto::Path...>
|
68
68
|
path.resolve # => "d"
|
69
69
|
Leto.dig(object, path) # => "d"
|
70
70
|
Leto.dig(object, [[:[], 0], [:[], :a], [:[], 1], [:end]]) # => "d"
|
@@ -72,17 +72,19 @@ Leto.dig(object, [[:[], 0], [:[], :a], [:[], 1], [:end]]) # => "d"
|
|
72
72
|
|
73
73
|
### Included utility methods
|
74
74
|
|
75
|
-
- `Leto.deep_freeze(obj)`
|
75
|
+
- [`Leto.deep_freeze(obj)`](https://github.com/search?q=deep_freeze+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
76
76
|
- similar to the version above, but avoids freezing Modules and unfreezables
|
77
|
-
- `Leto.deep_print(obj)`
|
77
|
+
- [`Leto.deep_print(obj)`](https://github.com/search?q=deep_print+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
78
78
|
- for debugging - prints more information than `pretty_print` does by default
|
79
|
-
- `Leto.deep_eql?(obj1, obj2)`
|
79
|
+
- [`Leto.deep_eql?(obj1, obj2)`](https://github.com/search?q=deep_eql+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
80
80
|
- stricter version of `#eql?` that takes all ivars into consideration
|
81
|
-
- `Leto.
|
81
|
+
- [`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
|
83
|
+
- [`Leto.shared_mutable_state?(obj1, obj2)`](https://github.com/search?q=shared_mutable_state+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
82
84
|
- useful for debugging or verifying that a `#dup` implementation is sane
|
83
|
-
- `Leto.shared_mutables(obj1, obj2)`
|
85
|
+
- [`Leto.shared_mutables(obj1, obj2)`](https://github.com/search?q=shared_mutables+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
84
86
|
- useful for debugging or verifying that a `#dup` implementation is sane
|
85
|
-
- `Leto.shared_objects(obj1, obj2)`
|
87
|
+
- [`Leto.shared_objects(obj1, obj2)`](https://github.com/search?q=shared_objects+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
86
88
|
- returns all objects shared by `obj1` and `obj2`, whether mutable or not
|
87
89
|
|
88
90
|
## Benchmarks
|
data/Rakefile
CHANGED
@@ -5,8 +5,10 @@ require "rspec/core/rake_task"
|
|
5
5
|
|
6
6
|
RSpec::Core::RakeTask.new(:spec)
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
RuboCop::RakeTask.new
|
11
|
-
|
12
|
-
|
8
|
+
if RUBY_VERSION.to_f >= 3.0
|
9
|
+
require "rubocop/rake_task"
|
10
|
+
RuboCop::RakeTask.new
|
11
|
+
task default: %i[spec rubocop]
|
12
|
+
else
|
13
|
+
task default: %i[spec]
|
14
|
+
end
|
data/leto.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.summary = "Generic object traverser"
|
12
12
|
spec.homepage = "https://github.com/jaynetics/leto"
|
13
13
|
spec.license = "MIT"
|
14
|
-
spec.required_ruby_version = ">= 2.
|
14
|
+
spec.required_ruby_version = ">= 2.3.0"
|
15
15
|
|
16
16
|
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
data/lib/leto/call.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
module Leto
|
2
|
-
|
2
|
+
def self.call(obj, max_depth: nil, path: nil, &block)
|
3
|
+
block_given? or return enum_for(__method__, obj, max_depth: max_depth, path: path)
|
3
4
|
|
4
|
-
|
5
|
-
return enum_for(__method__, obj) unless block_given?
|
6
|
-
|
7
|
-
seen = {}.tap(&:compare_by_identity)
|
8
|
-
seen[Process::Tms] = true if LEAKY_PROCESS_TMS
|
9
|
-
path = block.arity == 2 ? Path.new(start: obj) : nil
|
10
|
-
traverse(obj, path, seen, block)
|
5
|
+
traverse(obj, path, 0, max_depth, build_seen_hash, block)
|
11
6
|
obj
|
12
7
|
end
|
13
8
|
|
14
|
-
def self.
|
15
|
-
return
|
9
|
+
def self.trace(obj, max_depth: nil, path: nil, &block)
|
10
|
+
block_given? or return enum_for(__method__, obj, max_depth: max_depth, path: path)
|
11
|
+
|
12
|
+
call(obj, max_depth: max_depth, path: path || Path.new(start: obj), &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.traverse(obj, path, depth, max_depth, seen, block)
|
16
|
+
return if seen[obj] || max_depth&.<(depth)
|
16
17
|
|
17
18
|
seen[obj] = true
|
19
|
+
depth += 1
|
18
20
|
|
19
21
|
path ? block.call(obj, path) : block.call(obj)
|
20
22
|
|
@@ -22,43 +24,58 @@ module Leto
|
|
22
24
|
traverse(
|
23
25
|
obj.instance_variable_get(ivar_name),
|
24
26
|
path&.+([[:instance_variable_get, ivar_name]]),
|
25
|
-
seen, block
|
27
|
+
depth, max_depth, seen, block
|
26
28
|
)
|
27
29
|
end
|
28
30
|
|
29
31
|
case obj
|
30
32
|
when Hash
|
31
33
|
obj.keys.each_with_index do |k, i|
|
32
|
-
traverse(k, path&.+([[:keys], [:[], i]]), seen, block)
|
33
|
-
traverse(obj[k], path&.+([[:[], k]]), seen, block)
|
34
|
+
traverse(k, path&.+([[:keys], [:[], i]]), depth, max_depth, seen, block)
|
35
|
+
traverse(obj[k], path&.+([[:[], k]]), depth, max_depth, seen, block)
|
34
36
|
end
|
35
37
|
when Module
|
36
38
|
obj.class_variables.each do |cvar_name|
|
37
39
|
traverse(
|
38
40
|
obj.class_variable_get(cvar_name),
|
39
41
|
path&.+([[:class_variable_get, cvar_name]]),
|
40
|
-
seen, block
|
42
|
+
depth, max_depth, seen, block
|
41
43
|
)
|
42
44
|
end
|
43
45
|
obj.constants.each do |const_name|
|
44
46
|
traverse(
|
45
47
|
obj.const_get(const_name),
|
46
48
|
path&.+([[:const_get, const_name]]),
|
47
|
-
seen, block
|
49
|
+
depth, max_depth, seen, block
|
48
50
|
)
|
49
51
|
end
|
50
52
|
when Range
|
51
|
-
traverse(obj.begin, path&.+([[:begin]]), seen, block)
|
52
|
-
traverse(obj.end, path&.+([[:end]]), seen, block)
|
53
|
+
traverse(obj.begin, path&.+([[:begin]]), depth, max_depth, seen, block)
|
54
|
+
traverse(obj.end, path&.+([[:end]]), depth, max_depth, seen, block)
|
53
55
|
when Struct
|
54
56
|
obj.members.each do |member|
|
55
|
-
traverse(obj[member], path&.+([[:[], member]]), seen, block)
|
57
|
+
traverse(obj[member], path&.+([[:[], member]]), depth, max_depth, seen, block)
|
56
58
|
end
|
57
59
|
when Enumerable
|
58
60
|
obj.each_with_index do |el, idx|
|
59
|
-
traverse(el, path&.+([[:[], idx]]), seen, block)
|
61
|
+
traverse(el, path&.+([[:[], idx]]), depth, max_depth, seen, block)
|
60
62
|
end
|
61
63
|
end
|
62
64
|
end
|
63
65
|
private_class_method :traverse
|
66
|
+
|
67
|
+
if RUBY_VERSION.to_f > 2.7
|
68
|
+
def self.build_seen_hash
|
69
|
+
{}.tap(&:compare_by_identity)
|
70
|
+
end
|
71
|
+
else
|
72
|
+
# ignore leaky constants in old rubies
|
73
|
+
def self.build_seen_hash
|
74
|
+
hash = {}.tap(&:compare_by_identity)
|
75
|
+
hash[::Etc::Group] = true if defined?(::Etc::Group)
|
76
|
+
hash[::Etc::Passwd] = true if defined?(::Etc::Passwd)
|
77
|
+
hash[::Process::Tms] = true if defined?(::Process::Tms)
|
78
|
+
hash
|
79
|
+
end
|
80
|
+
end
|
64
81
|
end
|
data/lib/leto/path.rb
CHANGED
@@ -14,6 +14,11 @@ module Leto
|
|
14
14
|
steps.each(&block)
|
15
15
|
end
|
16
16
|
|
17
|
+
def size
|
18
|
+
steps.size
|
19
|
+
end
|
20
|
+
alias count size
|
21
|
+
|
17
22
|
def +(other)
|
18
23
|
self.class.new(start: start, steps: steps + other.to_a)
|
19
24
|
end
|
@@ -21,8 +26,6 @@ module Leto
|
|
21
26
|
def resolve
|
22
27
|
steps.inject(start) do |obj, (method, *args)|
|
23
28
|
obj&.send(method, *args) or break obj
|
24
|
-
rescue StandardError => e
|
25
|
-
warn "#{__method__}: #{e.class} #{e.message}"
|
26
29
|
end
|
27
30
|
end
|
28
31
|
|
data/lib/leto/utils.rb
CHANGED
@@ -8,7 +8,7 @@ module Leto
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def self.deep_print(obj, print_method: :inspect, indent: 4, show_path: true)
|
11
|
-
|
11
|
+
trace(obj) do |el, path|
|
12
12
|
puts "#{' ' * path.count * indent}#{el.send(print_method)}" \
|
13
13
|
"#{" @ #{path.inspect}" if show_path}" \
|
14
14
|
[0..78]
|
@@ -20,6 +20,23 @@ module Leto
|
|
20
20
|
call(obj1).to_a == call(obj2).to_a
|
21
21
|
end
|
22
22
|
|
23
|
+
def self.deep_dup(obj, include_modules: false)
|
24
|
+
return obj if IMMUTABLE_CLASSES.include?(obj.class) || !duplicable?(obj) ||
|
25
|
+
(!include_modules && obj.is_a?(Module))
|
26
|
+
|
27
|
+
trace(obj, max_depth: 1).each_with_object(obj.dup) do |(el, path), copy|
|
28
|
+
method, *args = path.steps[0]
|
29
|
+
case method
|
30
|
+
when :instance_variable_get
|
31
|
+
copy.instance_variable_set(*args, deep_dup(el, include_modules: include_modules))
|
32
|
+
when :[]
|
33
|
+
copy[*args] = deep_dup(el, include_modules: include_modules)
|
34
|
+
when :begin
|
35
|
+
return Range.new(deep_dup(obj.begin), deep_dup(obj.end), obj.exclude_end?)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
23
40
|
def self.shared_mutable_state?(obj1, obj2)
|
24
41
|
shared_mutables(obj1, obj2).any?
|
25
42
|
end
|
@@ -37,12 +54,11 @@ module Leto
|
|
37
54
|
end
|
38
55
|
|
39
56
|
def self.shared_objects(obj1, obj2, filter: nil)
|
40
|
-
|
41
|
-
|
42
|
-
objects_with_path1.each.with_object([]) do |(el1, path1), acc|
|
57
|
+
obj2_els_with_path = trace(obj2).to_a
|
58
|
+
trace(obj1).each_with_object([]) do |(el1, path1), acc|
|
43
59
|
next if filter && !filter.call(el1)
|
44
60
|
|
45
|
-
|
61
|
+
obj2_els_with_path.reject do |el2, path2|
|
46
62
|
acc << [el1, path1, path2] if el1.equal?(el2)
|
47
63
|
end
|
48
64
|
end
|
@@ -56,9 +72,26 @@ module Leto
|
|
56
72
|
IMMUTABLE_CLASSES = [
|
57
73
|
FalseClass,
|
58
74
|
Float,
|
59
|
-
Integer
|
75
|
+
if defined?(Integer)
|
76
|
+
Integer
|
77
|
+
else
|
78
|
+
Fixnum # rubocop:disable Lint/UnifiedInteger for Ruby < 2.4
|
79
|
+
end,
|
60
80
|
NilClass,
|
61
81
|
Symbol,
|
62
82
|
TrueClass,
|
63
83
|
].freeze
|
84
|
+
|
85
|
+
def self.duplicable?(obj)
|
86
|
+
!NON_DUPLICABLE_CLASSES.include?(obj.class) && obj.respond_to?(:dup)
|
87
|
+
end
|
88
|
+
private_class_method :duplicable?
|
89
|
+
|
90
|
+
require 'singleton'
|
91
|
+
|
92
|
+
NON_DUPLICABLE_CLASSES = [
|
93
|
+
Method,
|
94
|
+
Singleton,
|
95
|
+
UnboundMethod
|
96
|
+
].freeze
|
64
97
|
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:
|
4
|
+
version: 2.0.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-02-
|
11
|
+
date: 2023-02-27 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -46,7 +46,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
46
46
|
requirements:
|
47
47
|
- - ">="
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: 2.
|
49
|
+
version: 2.3.0
|
50
50
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|