leto 2.0.0 → 2.2.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: b0362bb08490e1bd600bb8996a1be449584295433c047472864578b2d43a6cb5
4
+ data.tar.gz: de8244c6e7d6bd2a58453effbadf1e93f9956a0aace1fc0fcef1c97660e486f4
5
5
  SHA512:
6
- metadata.gz: 1b13efbac9e6e200a43067a57b219a5128d69eea06a4a41a8fe2bd88d49932d0a10a60b38807443133300b86aaa9d3d75045e0bc340057d6d16564647b68c8ba
7
- data.tar.gz: 476318c70ed5d9a1df6ab4abc67a9d3dad35a003585d34ec4bdc743f6990823db6dc5c3430a244ed12bc3c454cbb5a8e1937dc2ada58568c8a38ce1e018cbbb9
6
+ metadata.gz: a8a4a40c37e53158ce69775451a56a2b939cecb01df5c8441a0c7a18b9e4c88db68d350dd57abbc2d87fd03c770335f2e92344e2fba4370280796bb46fce1d6e
7
+ data.tar.gz: b73106dde5c5f19b65c6b95ac6f0fdf4039ab84eaf6dfb53abe705e54e4b7c2f8951fd3ebe205cf53ca46faaccf3812cc1fc2e534979a8adfd99df07bc0a66fb
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,22 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.2.0] - 2026-09-03
4
+
5
+ ### Added
6
+
7
+ - support for varargs in `::shared_mutable_state?`
8
+
9
+ ## [2.1.0] - 2023-05-20
10
+
11
+ ### Added
12
+
13
+ - `::call`, `::trace` and `::deep_dup` support for Data feature of Ruby 3.2
14
+
3
15
  ## [2.0.0] - 2023-02-27
4
16
 
5
17
  ### Changed
6
18
 
7
- - removed support for gettings paths by calling `::call` with a two-arg block
19
+ - removed support for getting paths by calling `::call` with a two-arg block
8
20
 
9
21
  ### Added
10
22
 
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,8 +81,8 @@ 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
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)
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
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)
86
88
  - useful for debugging or verifying that a `#dup` implementation is sane
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
@@ -10,8 +10,7 @@ module Leto
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
- "#{" @ #{path.inspect}" if show_path}" \
14
- [0..78]
13
+ "#{" @ #{path.inspect}" if show_path}"[0..78]
15
14
  end
16
15
  nil
17
16
  end
@@ -24,21 +23,29 @@ module Leto
24
23
  return obj if IMMUTABLE_CLASSES.include?(obj.class) || !duplicable?(obj) ||
25
24
  (!include_modules && obj.is_a?(Module))
26
25
 
27
- trace(obj, max_depth: 1).each_with_object(obj.dup) do |(el, path), copy|
26
+ copy = obj.dup
27
+
28
+ trace(obj, max_depth: 1).each do |el, path|
28
29
  method, *args = path.steps[0]
29
30
  case method
30
31
  when :instance_variable_get
31
32
  copy.instance_variable_set(*args, deep_dup(el, include_modules: include_modules))
32
33
  when :[]
33
34
  copy[*args] = deep_dup(el, include_modules: include_modules)
35
+ when :send # Data
36
+ copy = copy.with(args[0] => deep_dup(el, include_modules: include_modules))
34
37
  when :begin
35
38
  return Range.new(deep_dup(obj.begin), deep_dup(obj.end), obj.exclude_end?)
36
39
  end
37
40
  end
41
+
42
+ copy
38
43
  end
39
44
 
40
- def self.shared_mutable_state?(obj1, obj2)
41
- shared_mutables(obj1, obj2).any?
45
+ def self.shared_mutable_state?(*objects)
46
+ objects.combination(2).any? do |obj1, obj2|
47
+ each_shared_object(obj1, obj2, filter: method(:mutable?)).any?
48
+ end
42
49
  end
43
50
 
44
51
  # returns [[shared_object, path1, path2], ...], e.g.:
@@ -50,16 +57,22 @@ module Leto
50
57
  # ["bar", [[:[], 1]], [[:[], 0]]]
51
58
  # ]
52
59
  def self.shared_mutables(obj1, obj2)
53
- shared_objects(obj1, obj2, filter: method(:mutable?))
60
+ each_shared_object(obj1, obj2, filter: method(:mutable?)).to_a
54
61
  end
55
62
 
56
63
  def self.shared_objects(obj1, obj2, filter: nil)
64
+ each_shared_object(obj1, obj2, filter: filter).to_a
65
+ end
66
+
67
+ def self.each_shared_object(obj1, obj2, filter: nil)
68
+ block_given? or return enum_for(__method__, obj1, obj2, filter: filter)
69
+
57
70
  obj2_els_with_path = trace(obj2).to_a
58
- trace(obj1).each_with_object([]) do |(el1, path1), acc|
71
+ trace(obj1).each do |el1, path1|
59
72
  next if filter && !filter.call(el1)
60
73
 
61
74
  obj2_els_with_path.reject do |el2, path2|
62
- acc << [el1, path1, path2] if el1.equal?(el2)
75
+ yield(el1, path1, path2) if el1.equal?(el2)
63
76
  end
64
77
  end
65
78
  end
@@ -75,7 +88,7 @@ module Leto
75
88
  if defined?(Integer)
76
89
  Integer
77
90
  else
78
- Fixnum # rubocop:disable Lint/UnifiedInteger for Ruby < 2.4
91
+ Fixnum # rubocop:disable Lint/UnifiedInteger -- for Ruby < 2.4
79
92
  end,
80
93
  NilClass,
81
94
  Symbol,
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.2.0"
5
5
  end
metadata CHANGED
@@ -1,16 +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.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janosch Müller
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-02-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description:
14
12
  email:
15
13
  - janosch84@gmail.com
16
14
  executables: []
@@ -38,7 +36,6 @@ licenses:
38
36
  metadata:
39
37
  changelog_uri: https://github.com/jaynetics/leto/blob/main/CHANGELOG.md
40
38
  homepage_uri: https://github.com/jaynetics/leto
41
- post_install_message:
42
39
  rdoc_options: []
43
40
  require_paths:
44
41
  - lib
@@ -53,8 +50,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
50
  - !ruby/object:Gem::Version
54
51
  version: '0'
55
52
  requirements: []
56
- rubygems_version: 3.4.1
57
- signing_key:
53
+ rubygems_version: 4.0.3
58
54
  specification_version: 4
59
55
  summary: Generic object traverser
60
56
  test_files: []