leto 2.3.0 → 2.4.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/CHANGELOG.md +9 -0
- data/Gemfile +2 -0
- data/README.md +65 -30
- data/lib/leto/call.rb +29 -0
- data/lib/leto/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f0297fbb39b4e2a4a72e314dde94542722e1f147cb7988181cc6a79529123474
|
|
4
|
+
data.tar.gz: afb6e6670df5f8e0f2ea10630c184d69f676457b0f78431a52944c3c6dff1d44
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: afe909e7a3e5a99e2fd580f74b709a746b1cdf66c8e8c3c0d61569c2c49faa06accd3c5a628ab6e1f57d42a70a676a851a9f7ba5edbc66132456de84cac9bf93
|
|
7
|
+
data.tar.gz: 1071b59853ec209240c7a298310c548d498af3f8086666623b4911684764975c8905291aae5d711d5be8b43b33382910cfdfb52eab8c10d0414caa5b0e592051
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [2.4.0] - 2026-10-26
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- support for `Complex` and `Rational` objects
|
|
8
|
+
- support for `Hash#default` and `Hash#default_proc`
|
|
9
|
+
- support for `Exception` fields
|
|
10
|
+
- support for `Time` zones
|
|
11
|
+
|
|
3
12
|
## [2.3.0] - 2026-09-29
|
|
4
13
|
|
|
5
14
|
### Added
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -4,18 +4,13 @@
|
|
|
4
4
|
[](https://github.com/jaynetics/leto/actions)
|
|
5
5
|
[](https://codecov.io/gh/jaynetics/leto)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Leto is a generic object traverser for Ruby (named after the Greek [childbearing goddess Leto](https://www.theoi.com/Titan/TitanisLeto.html)).
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
It takes an object and recursively yields:
|
|
10
10
|
|
|
11
11
|
- the given object
|
|
12
12
|
- instance variables, class variables, constants
|
|
13
|
-
-
|
|
14
|
-
- Enumerable members
|
|
15
|
-
- Range begins and ends
|
|
16
|
-
- Struct members
|
|
17
|
-
- [Data](https://docs.ruby-lang.org/en/3.2/Data.html) members
|
|
18
|
-
- Set members (for [native Sets on Ruby >= 4.0](https://bugs.ruby-lang.org/issues/21216))
|
|
13
|
+
- any object attributes, such as Array members, that are built into Ruby ([full list](#built-ins))
|
|
19
14
|
|
|
20
15
|
This makes stuff like deep-freezing fairly easy to implement:
|
|
21
16
|
|
|
@@ -29,7 +24,7 @@ my_object[0].frozen? # => true
|
|
|
29
24
|
my_object[0][:a][1].begin.frozen? # => true
|
|
30
25
|
```
|
|
31
26
|
|
|
32
|
-
Note: the slightly smarter `Leto.deep_freeze` is one of the [included utility methods](#included-
|
|
27
|
+
Note: the slightly smarter `Leto.deep_freeze` is one of the [included utility methods](#included-utils).
|
|
33
28
|
|
|
34
29
|
## Usage
|
|
35
30
|
|
|
@@ -39,16 +34,19 @@ Basic example:
|
|
|
39
34
|
object = [{ a: ['b', 'c'..'d'] }]
|
|
40
35
|
|
|
41
36
|
Leto.call(object) { |el| puts "#{el.class}: #{el}" }
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This prints:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Array: [{a: ["b", "c".."d"]}]
|
|
43
|
+
Hash: {a: ["b", "c".."d"]}
|
|
44
|
+
Symbol: a
|
|
45
|
+
Array: ["b", "c".."d"]
|
|
46
|
+
String: b
|
|
47
|
+
Range: c..d
|
|
48
|
+
String: c
|
|
49
|
+
String: d
|
|
52
50
|
```
|
|
53
51
|
|
|
54
52
|
Calls without a block return an `Enumerator`:
|
|
@@ -62,16 +60,19 @@ Leto.call(object).to_a
|
|
|
62
60
|
|
|
63
61
|
```ruby
|
|
64
62
|
Leto.trace(object) { |el, path| puts "#{el.inspect.ljust(23)} @#{path}" }
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This prints:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
[{:a=>["b", "c".."d"]}] @#<Leto::Path [{:a=>["b", "c".."d"]}]>
|
|
69
|
+
{:a=>["b", "c".."d"]} @#<Leto::Path [{:a=>["b", "c".."d"]}][0]>
|
|
70
|
+
:a @#<Leto::Path [{:a=>["b", "c".."d"]}][0].keys[0]>
|
|
71
|
+
["b", "c".."d"] @#<Leto::Path [{:a=>["b", "c".."d"]}][0][:a]>
|
|
72
|
+
"b" @#<Leto::Path [{:a=>["b", "c".."d"]}][0][:a][0]>
|
|
73
|
+
"c".."d" @#<Leto::Path [{:a=>["b", "c".."d"]}][0][:a][1]>
|
|
74
|
+
"c" @#<Leto::Path [{:a=>["b", "c".."d"]}][0][:a][1].begin>
|
|
75
|
+
"d" @#<Leto::Path [{:a=>["b", "c".."d"]}][0][:a][1].end>
|
|
75
76
|
```
|
|
76
77
|
|
|
77
78
|
Paths can be looked up with `Leto::Path#resolve` or `Leto.dig`:
|
|
@@ -82,22 +83,43 @@ path.resolve # => "d"
|
|
|
82
83
|
Leto.dig(object, path) # => "d"
|
|
83
84
|
```
|
|
84
85
|
|
|
85
|
-
Arrays of Arrays, where the
|
|
86
|
+
Arrays of Arrays, where the inner ones contain method names and arguments, are valid paths:
|
|
86
87
|
|
|
87
88
|
```ruby
|
|
88
89
|
Leto.dig(object, [[:[], 0], [:[], :a], [:[], 1], [:end]]) # => "d"
|
|
89
90
|
```
|
|
90
91
|
|
|
92
|
+
<a name='built-ins'></a>
|
|
93
|
+
|
|
94
|
+
### Supported built-in classes and attributes
|
|
95
|
+
|
|
96
|
+
The following classes are built into Ruby and some of their attributes are not exposed as regular instance variables. Leto handles these explicitly.
|
|
97
|
+
|
|
98
|
+
- `Complex` objects' real and imaginary parts
|
|
99
|
+
- [`Data`](https://docs.ruby-lang.org/en/3.2/Data.html) members
|
|
100
|
+
- `Enumerable` members
|
|
101
|
+
- `Exception` messages, backtraces, causes, and various subclass fields
|
|
102
|
+
- `Hash` keys, values, defaults, and default_procs
|
|
103
|
+
- `Range` begins and ends
|
|
104
|
+
- `Rational` numerators and denominators
|
|
105
|
+
- `Set` members (including for [native Sets on Ruby >= 4.0](https://bugs.ruby-lang.org/issues/21216))
|
|
106
|
+
- `Struct` and `OpenStruct` members
|
|
107
|
+
- `Time` zones, including custom timezone objects
|
|
108
|
+
|
|
109
|
+
<a name='included-utils'></a>
|
|
110
|
+
|
|
91
111
|
### Included utility methods
|
|
92
112
|
|
|
93
113
|
- [`Leto.deep_freeze(obj)`](https://github.com/search?q=deep_freeze+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
|
94
114
|
- similar to the version above, but avoids freezing Modules and unfreezables
|
|
115
|
+
- similar to [`Ractor.make_shareable`](https://docs.ruby-lang.org/en/4.0/Ractor.html#method-c-make_shareable)
|
|
95
116
|
- [`Leto.deep_print(obj)`](https://github.com/search?q=deep_print+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
|
96
117
|
- for debugging - prints more information than `pretty_print` does by default
|
|
97
118
|
- [`Leto.deep_eql?(obj1, obj2)`](https://github.com/search?q=deep_eql+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
|
98
119
|
- stricter version of `#eql?` that takes all ivars into consideration
|
|
99
120
|
- [`Leto.deep_dup(obj)`](https://github.com/search?q=deep_dup+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
|
100
121
|
- 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
|
|
122
|
+
- similar to [`Ractor.make_shareable(obj, copy: true)`](https://docs.ruby-lang.org/en/4.0/Ractor.html#method-c-make_shareable), but doesn't freeze
|
|
101
123
|
- [`Leto.shared_mutable_state?(obj1, obj2, ...)`](https://github.com/search?q=shared_mutable_state+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
|
102
124
|
- useful for debugging or verifying that a `#dup` implementation is sane
|
|
103
125
|
- [`Leto.shared_mutables(obj1, obj2)`](https://github.com/search?q=shared_mutables+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
|
@@ -105,6 +127,19 @@ Leto.dig(object, [[:[], 0], [:[], :a], [:[], 1], [:end]]) # => "d"
|
|
|
105
127
|
- [`Leto.shared_objects(obj1, obj2)`](https://github.com/search?q=shared_objects+repo%3Ajaynetics%2Fleto+path%3Alib%2Fleto%2Futils.rb&type=code)
|
|
106
128
|
- returns all objects shared by `obj1` and `obj2`, whether mutable or not
|
|
107
129
|
|
|
130
|
+
To make a shareable copy while leaving the original mutable:
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
copy = Leto.deep_freeze(object, shareable: true, copy: true)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Duplicating a Proc does not duplicate its captured environment. Shareable freezing
|
|
137
|
+
uses Ruby's rules for Procs, Methods, and other native objects, which vary between
|
|
138
|
+
Ruby versions.
|
|
139
|
+
|
|
140
|
+
Shareable freezing leaves Classes and Modules as they are, following Ractor's
|
|
141
|
+
rules. `include_modules: true` applies to regular deep freezing.
|
|
142
|
+
|
|
108
143
|
## Benchmarks
|
|
109
144
|
|
|
110
145
|
```
|
data/lib/leto/call.rb
CHANGED
|
@@ -38,6 +38,10 @@ module Leto
|
|
|
38
38
|
traverse(k, path&.+([[:keys], [:[], i]]), depth, max_depth, seen, block)
|
|
39
39
|
traverse(obj[k], path&.+([[:[], k]]), depth, max_depth, seen, block)
|
|
40
40
|
end
|
|
41
|
+
obj.default.nil? ||
|
|
42
|
+
traverse(obj.default, path&.+([[:default]]), depth, max_depth, seen, block)
|
|
43
|
+
obj.default_proc.nil? ||
|
|
44
|
+
traverse(obj.default_proc, path&.+([[:default_proc]]), depth, max_depth, seen, block)
|
|
41
45
|
when Module
|
|
42
46
|
obj.class_variables.each do |cvar_name|
|
|
43
47
|
traverse(
|
|
@@ -87,6 +91,31 @@ module Leto
|
|
|
87
91
|
obj.each_with_index do |el, idx|
|
|
88
92
|
traverse(el, path&.+([[:[], idx]]), depth, max_depth, seen, block)
|
|
89
93
|
end
|
|
94
|
+
when Complex
|
|
95
|
+
traverse(obj.real, path&.+([[:real]]), depth, max_depth, seen, block)
|
|
96
|
+
traverse(obj.imaginary, path&.+([[:imaginary]]), depth, max_depth, seen, block)
|
|
97
|
+
when Rational
|
|
98
|
+
traverse(obj.numerator, path&.+([[:numerator]]), depth, max_depth, seen, block)
|
|
99
|
+
traverse(obj.denominator, path&.+([[:denominator]]), depth, max_depth, seen, block)
|
|
100
|
+
when Exception
|
|
101
|
+
%i[
|
|
102
|
+
message backtrace backtrace_locations cause name receiver args key
|
|
103
|
+
matchee result exit_value reason tag value path status signo errno
|
|
104
|
+
].each do |attr|
|
|
105
|
+
attr = :original_message if attr == :message && obj.respond_to?(:original_message)
|
|
106
|
+
begin
|
|
107
|
+
value = obj.send(attr)
|
|
108
|
+
rescue ArgumentError
|
|
109
|
+
# Some exceptions have no receiver or key.
|
|
110
|
+
raise unless %i[receiver key].include?(attr)
|
|
111
|
+
rescue NoMethodError # ignore
|
|
112
|
+
end
|
|
113
|
+
value.nil? ||
|
|
114
|
+
traverse(value, path&.+([[attr]]), depth, max_depth, seen, block)
|
|
115
|
+
end
|
|
116
|
+
when Time
|
|
117
|
+
obj.zone.nil? ||
|
|
118
|
+
traverse(obj.zone, path&.+([[:zone]]), depth, max_depth, seen, block)
|
|
90
119
|
end
|
|
91
120
|
end
|
|
92
121
|
|
data/lib/leto/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leto
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Janosch Müller
|
|
@@ -51,7 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
51
51
|
- !ruby/object:Gem::Version
|
|
52
52
|
version: '0'
|
|
53
53
|
requirements: []
|
|
54
|
-
rubygems_version: 4.0.
|
|
54
|
+
rubygems_version: 4.0.23
|
|
55
55
|
specification_version: 4
|
|
56
56
|
summary: Generic object traverser
|
|
57
57
|
test_files: []
|