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 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