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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2926017bd4fc2ec078b89e6d39157ed7e79e48693e82628665c3435c5c39b4f4
4
- data.tar.gz: f8736c1e1e0a5e7a7b6c93d8c48c7393b6d554906245c5fcc71d05aa3d200789
3
+ metadata.gz: 0ed0d190b234b4976a219ba5b50a3e9cb4f394be7b1a27aa2f1d7bdfe93887b0
4
+ data.tar.gz: eafafd24510514cf7ba4e59cdf1a967c7464d175b8ceed1f3cafa2e78fffcc1e
5
5
  SHA512:
6
- metadata.gz: 1b13efbac9e6e200a43067a57b219a5128d69eea06a4a41a8fe2bd88d49932d0a10a60b38807443133300b86aaa9d3d75045e0bc340057d6d16564647b68c8ba
7
- data.tar.gz: 476318c70ed5d9a1df6ab4abc67a9d3dad35a003585d34ec4bdc743f6990823db6dc5c3430a244ed12bc3c454cbb5a8e1937dc2ada58568c8a38ce1e018cbbb9
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 gettings paths by calling `::call` with a two-arg block
13
+ - removed support for getting paths by calling `::call` with a two-arg block
8
14
 
9
15
  ### Added
10
16
 
data/Gemfile CHANGED
@@ -6,11 +6,10 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
  gem "rake", "~> 13.0"
9
-
10
9
  gem "rspec", "~> 3.0"
11
10
 
12
11
  if RUBY_VERSION.to_f >= 3.0
13
- gem "rubocop", "~> 1.21"
14
-
15
12
  gem "relaxed-rubocop"
13
+ gem "rubocop", "~> 1.21"
14
+ gem "simplecov-cobertura"
16
15
  end
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
- def self.traverse(obj, path, depth, max_depth, seen, block)
16
- return if seen[obj] || max_depth&.<(depth)
15
+ instance_eval <<-RUBY, __FILE__, __LINE__ + 1
17
16
 
18
- seen[obj] = true
19
- depth += 1
17
+ private
20
18
 
21
- path ? block.call(obj, path) : block.call(obj)
19
+ def traverse(obj, path, depth, max_depth, seen, block)
20
+ return if seen[obj] || max_depth&.<(depth)
22
21
 
23
- obj.instance_variables.each do |ivar_name|
24
- traverse(
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
- case obj
32
- when Hash
33
- obj.keys.each_with_index do |k, i|
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.const_get(const_name),
48
- path&.+([[:const_get, const_name]]),
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
- when Range
53
- traverse(obj.begin, path&.+([[:begin]]), depth, max_depth, seen, block)
54
- traverse(obj.end, path&.+([[:end]]), depth, max_depth, seen, block)
55
- when Struct
56
- obj.members.each do |member|
57
- traverse(obj[member], path&.+([[:[], member]]), depth, max_depth, seen, block)
58
- end
59
- when Enumerable
60
- obj.each_with_index do |el, idx|
61
- traverse(el, path&.+([[:[], idx]]), depth, max_depth, seen, block)
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 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)
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
- end
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
- trace(obj, max_depth: 1).each_with_object(obj.dup) do |(el, path), copy|
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
- shared_mutables(obj1, obj2).any?
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
- shared_objects(obj1, obj2, filter: method(:mutable?))
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).each_with_object([]) do |(el1, path1), acc|
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
- acc << [el1, path1, path2] if el1.equal?(el2)
74
+ yield(el1, path1, path2) if el1.equal?(el2)
63
75
  end
64
76
  end
65
77
  end
data/lib/leto/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leto
4
- VERSION = "2.0.0"
4
+ VERSION = "2.1.0"
5
5
  end
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.0.0
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-02-27 00:00:00.000000000 Z
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.1
56
+ rubygems_version: 3.4.13
57
57
  signing_key:
58
58
  specification_version: 4
59
59
  summary: Generic object traverser