deeply_enumerable 0.9.3 → 2.0.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 +74 -0
- data/README.md +85 -26
- data/Rakefile +4 -31
- data/lib/base_extensions.rb +10 -3
- data/lib/deeply_enumerable/array.rb +52 -20
- data/lib/deeply_enumerable/enumerable.rb +66 -4
- data/lib/deeply_enumerable/hash.rb +53 -19
- data/lib/deeply_enumerable/version.rb +1 -1
- data/lib/deeply_enumerable.rb +2 -2
- metadata +23 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6dc0e555ef198f9c8a08f8d3567a6ee2b3d24b06dd777617d4b41a2a1d32b4fd
|
|
4
|
+
data.tar.gz: 7a22638bdff5ce80f5f0bcaaa3cec78e8541c5402fa3f615415528b130f3628e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1efe5ef4b790f4e936ef616dda532c2a50d22c0e244521abd22c7df3429b45aa81537d714bed552b398b5f21d1c3e8750246feed24bb2b1b3b044c6db065625d
|
|
7
|
+
data.tar.gz: cdaa3281558c05f8e58de0edb33912b7a939004881d7555dd70400b114422d3a35782e9f7914d31887356b03b3856ab34c28d4a7eeb23dbf1a1a267fba083046
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [2.0.0] - 2026-06-17
|
|
9
|
+
|
|
10
|
+
This is a **major, breaking release**. The argument-based `#deep_compact` API has
|
|
11
|
+
been replaced with four clearly-named methods, and "blank" now matches Rails'
|
|
12
|
+
`#compact_blank` semantics.
|
|
13
|
+
|
|
14
|
+
### Breaking
|
|
15
|
+
|
|
16
|
+
- **`#deep_compact` changed meaning.** In v1, bare `deep_compact` defaulted to the
|
|
17
|
+
*most aggressive* behavior. In v2 it removes **only `nil`** (matching Ruby's
|
|
18
|
+
`#compact`). If you relied on the old default, switch to `#deep_compact_blank`.
|
|
19
|
+
- **The boolean arguments are gone.** `deep_compact(remove_emptied_elements,
|
|
20
|
+
remove_empty_elements)` is replaced by four dedicated methods (see Added).
|
|
21
|
+
- **"Blank" now means `blank?`, not just empty.** The `_blank` family now also
|
|
22
|
+
removes `false` and blank strings (`""`, `" "`), in addition to `nil` and empty
|
|
23
|
+
collections — matching Rails' `#compact_blank`.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Four named compaction methods, each with an in-place `!` variant, available as
|
|
28
|
+
instance methods, class methods, and (via `require: "base_extensions"`) directly
|
|
29
|
+
on core `Hash`/`Array`:
|
|
30
|
+
- `#deep_compact` — removes `nil` only.
|
|
31
|
+
- `#deep_compact_existing_blank` — removes `nil` and values that are already
|
|
32
|
+
blank; keeps collections that only become blank through compaction.
|
|
33
|
+
- `#deep_compact_blanked` — removes `nil` and collections emptied by compaction;
|
|
34
|
+
keeps values that were already blank.
|
|
35
|
+
- `#deep_compact_blank` — removes every blank value.
|
|
36
|
+
- GitHub Actions CI running RSpec on Ruby 3.3, 3.4, and 4.0.
|
|
37
|
+
- A real, comprehensive test suite (43 examples) replacing the previous
|
|
38
|
+
placeholder specs.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- Rewrote the internals around a single recursive engine, with doc comments
|
|
43
|
+
linking each method to the Ruby/Rails source it mirrors. Blankness is determined
|
|
44
|
+
by an internal equivalent of ActiveSupport's `Object#blank?`, so the gem still
|
|
45
|
+
has **no ActiveSupport dependency**.
|
|
46
|
+
- `base_extensions` now mixes into `Hash`/`Array` the same way ActiveSupport does
|
|
47
|
+
(reopening the class).
|
|
48
|
+
- `rake` now runs the RSpec suite (`rake` / `rake spec`); dropped the unused
|
|
49
|
+
Minitest and RDoc tasks.
|
|
50
|
+
- Expanded the README with a semantics table, worked examples, and an
|
|
51
|
+
"Upgrading from v1" guide.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- Fixed a latent bug in `#reverse_deep_merge!` (`ebuild` → `rebuild`) that was
|
|
56
|
+
hidden by placeholder specs.
|
|
57
|
+
|
|
58
|
+
### Upgrading from v1
|
|
59
|
+
|
|
60
|
+
| v1 call | v2 equivalent |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| `deep_compact` / `deep_compact(true, true)` | `deep_compact_blank` |
|
|
63
|
+
| `deep_compact(true, false)` | `deep_compact_blanked` |
|
|
64
|
+
| `deep_compact(false, true)` | `deep_compact_existing_blank` |
|
|
65
|
+
| `deep_compact(false, false)` | `deep_compact` |
|
|
66
|
+
|
|
67
|
+
## [1.0.0] - 2023-01-18
|
|
68
|
+
|
|
69
|
+
- Initial release: recursive `#deep_compact` (with `remove_emptied_elements` /
|
|
70
|
+
`remove_empty_elements` flags) and `#reverse_deep_merge` for nested `Hash` and
|
|
71
|
+
`Array` objects.
|
|
72
|
+
|
|
73
|
+
[2.0.0]: https://github.com/chaunce/deeply_enumerable/compare/1.0.0...2.0.0
|
|
74
|
+
[1.0.0]: https://github.com/chaunce/deeply_enumerable/releases/tag/1.0.0
|
data/README.md
CHANGED
|
@@ -1,53 +1,112 @@
|
|
|
1
1
|
# DeeplyEnumerable
|
|
2
|
-
Extend Enumerable objects with recursive
|
|
2
|
+
Extend `Enumerable` objects with recursive versions of common operations.
|
|
3
|
+
|
|
4
|
+
`DeeplyEnumerable` walks a nested structure of `Hash` and `Array` objects and
|
|
5
|
+
applies an operation at every level. Nested `Enumerable` objects are converted
|
|
6
|
+
into `DeeplyEnumerable` types during the operation so the recursion can continue
|
|
7
|
+
all the way down.
|
|
8
|
+
|
|
9
|
+
## Compaction
|
|
10
|
+
Ruby's `#compact` removes `nil`. Rails' `#compact_blank` removes anything that is
|
|
11
|
+
`blank?` (`nil`, `false`, `""`, `" "`, `[]`, `{}`). `DeeplyEnumerable` provides
|
|
12
|
+
recursive versions of both, plus two methods in between for finer control over
|
|
13
|
+
*which* blanks are removed:
|
|
14
|
+
|
|
15
|
+
| Method | Removes | Keeps |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| `#deep_compact` | `nil` only | every other value, including `""`, `false`, `[]`, `{}` |
|
|
18
|
+
| `#deep_compact_existing_blank` | `nil` and values that are **already** blank | collections that only become blank through compaction |
|
|
19
|
+
| `#deep_compact_blanked` | `nil` and collections **emptied** by compaction | values that were already blank |
|
|
20
|
+
| `#deep_compact_blank` | every blank value | nothing blank |
|
|
21
|
+
|
|
22
|
+
The distinction between "existing" and "emptied" blanks matters once nesting is
|
|
23
|
+
involved. Consider `{ a: { b: nil }, c: [] }`:
|
|
24
|
+
|
|
25
|
+
* `a` holds `{ b: nil }`, which is *not* blank until its `nil` is removed — an
|
|
26
|
+
**emptied** blank.
|
|
27
|
+
* `c` holds `[]`, which is blank in the input — an **existing** blank.
|
|
3
28
|
|
|
4
|
-
## Usage
|
|
5
|
-
Provides the following methods
|
|
6
29
|
```ruby
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
30
|
+
source = { a: :b, c: nil, d: { e: nil, f: [] }, g: [1, nil], h: {}, i: "", j: [nil] }
|
|
31
|
+
|
|
32
|
+
DeeplyEnumerable::Hash.deep_compact(source)
|
|
33
|
+
# => { a: :b, d: { f: [] }, g: [1], h: {}, i: "", j: [] } # only nils removed
|
|
34
|
+
|
|
35
|
+
DeeplyEnumerable::Hash.deep_compact_existing_blank(source)
|
|
36
|
+
# => { a: :b, d: {}, g: [1], j: [] } # pre-existing [], {} and "" removed; emptied kept
|
|
37
|
+
|
|
38
|
+
DeeplyEnumerable::Hash.deep_compact_blanked(source)
|
|
39
|
+
# => { a: :b, d: { f: [] }, g: [1], h: {}, i: "" } # emptied collections removed; pre-existing blanks kept
|
|
11
40
|
|
|
12
|
-
|
|
13
|
-
|
|
41
|
+
DeeplyEnumerable::Hash.deep_compact_blank(source)
|
|
42
|
+
# => { a: :b, g: [1] } # every blank removed
|
|
14
43
|
```
|
|
15
44
|
|
|
16
|
-
|
|
45
|
+
Each method has an in-place `!` variant (`#deep_compact!`, `#deep_compact_blank!`,
|
|
46
|
+
etc.) that mutates the receiver and returns it, mirroring `#compact!`.
|
|
17
47
|
|
|
18
|
-
|
|
48
|
+
## Merging
|
|
19
49
|
```ruby
|
|
20
|
-
|
|
21
|
-
|
|
50
|
+
DeeplyEnumerable::Hash#reverse_deep_merge(other_hash) # recursively fill in only the missing keys
|
|
51
|
+
DeeplyEnumerable::Hash#reverse_deep_merge!(other_hash) # in place
|
|
22
52
|
```
|
|
53
|
+
`#deep_reverse_merge` / `#deep_reverse_merge!` are aliases.
|
|
23
54
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
55
|
+
## Usage
|
|
56
|
+
Call a class method to operate on a plain `Hash` or `Array`. The result is the
|
|
57
|
+
matching `DeeplyEnumerable` type (`Hash` returns a `DeeplyEnumerable::Hash`,
|
|
58
|
+
`Array` returns a `DeeplyEnumerable::Array`):
|
|
27
59
|
```ruby
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
$ Hash#deep_compact
|
|
31
|
-
$ Hash#deep_compact!
|
|
60
|
+
DeeplyEnumerable::Hash.deep_compact_blank({ a: 1, b: nil, c: { d: nil } })
|
|
61
|
+
# => { a: 1 }
|
|
32
62
|
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
DeeplyEnumerable::Array.deep_compact([1, nil, [2, nil]])
|
|
64
|
+
# => [1, [2]]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or extend the base `Hash` and `Array` classes (see *Installation*) and call the
|
|
68
|
+
methods directly:
|
|
69
|
+
```ruby
|
|
70
|
+
{ a: 1, b: nil }.deep_compact_blank # => { a: 1 }
|
|
71
|
+
[1, nil, [nil]].deep_compact # => [1, []]
|
|
35
72
|
```
|
|
36
73
|
|
|
74
|
+
## Upgrading from v1
|
|
75
|
+
v2 replaces the boolean arguments of `#deep_compact` with four named methods.
|
|
76
|
+
Map your old calls as follows:
|
|
77
|
+
|
|
78
|
+
| v1 call | v2 equivalent |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `deep_compact` / `deep_compact(true, true)` | `deep_compact_blank` |
|
|
81
|
+
| `deep_compact(true, false)` | `deep_compact_blanked` |
|
|
82
|
+
| `deep_compact(false, true)` | `deep_compact_existing_blank` |
|
|
83
|
+
| `deep_compact(false, false)` | `deep_compact` |
|
|
84
|
+
|
|
85
|
+
⚠️ **`#deep_compact` changed meaning.** In v1, bare `deep_compact` defaulted to
|
|
86
|
+
the *most aggressive* behavior. In v2, `#deep_compact` removes **only nils**
|
|
87
|
+
(matching Ruby's `#compact`). If you relied on the old default, switch to
|
|
88
|
+
`#deep_compact_blank`.
|
|
89
|
+
|
|
90
|
+
⚠️ **Blank now means `blank?`, not just empty.** v1 only ever removed `nil` and
|
|
91
|
+
empty collections (`[]`, `{}`). The v2 `_blank` family also removes `false` and
|
|
92
|
+
blank strings (`""`, `" "`), matching Rails' `#compact_blank`. Blankness is
|
|
93
|
+
determined by an internal equivalent of ActiveSupport's `Object#blank?`, so the
|
|
94
|
+
gem still has **no ActiveSupport dependency**.
|
|
95
|
+
|
|
37
96
|
## Installation
|
|
38
97
|
Add this line to your application's Gemfile:
|
|
39
98
|
```ruby
|
|
40
|
-
gem
|
|
99
|
+
gem "deeply_enumerable"
|
|
41
100
|
```
|
|
42
101
|
|
|
43
|
-
or this line to
|
|
102
|
+
or this line to mix the methods into the base `Hash` and `Array` classes:
|
|
44
103
|
```ruby
|
|
45
|
-
gem
|
|
104
|
+
gem "deeply_enumerable", require: "base_extensions"
|
|
46
105
|
```
|
|
47
106
|
|
|
48
107
|
And then execute:
|
|
49
108
|
```bash
|
|
50
|
-
$ bundle
|
|
109
|
+
$ bundle install
|
|
51
110
|
```
|
|
52
111
|
|
|
53
112
|
Or install it yourself as:
|
data/Rakefile
CHANGED
|
@@ -1,33 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
rescue LoadError
|
|
4
|
-
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
5
|
-
end
|
|
1
|
+
require "bundler/gem_tasks"
|
|
2
|
+
require "rspec/core/rake_task"
|
|
6
3
|
|
|
7
|
-
|
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
rdoc.rdoc_dir = 'rdoc'
|
|
11
|
-
rdoc.title = 'DeeplyEnumerable'
|
|
12
|
-
rdoc.options << '--line-numbers'
|
|
13
|
-
rdoc.rdoc_files.include('README.md')
|
|
14
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
require 'bundler/gem_tasks'
|
|
23
|
-
|
|
24
|
-
require 'rake/testtask'
|
|
25
|
-
|
|
26
|
-
Rake::TestTask.new(:test) do |t|
|
|
27
|
-
t.libs << 'test'
|
|
28
|
-
t.pattern = 'test/**/*_test.rb'
|
|
29
|
-
t.verbose = false
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
task default: :test
|
|
6
|
+
task default: :spec
|
data/lib/base_extensions.rb
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "deeply_enumerable"
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
# Mirror the way Rails (ActiveSupport) mixes its enumerable extensions into the
|
|
4
|
+
# core classes: reopen each class and include the corresponding module.
|
|
5
|
+
class Hash
|
|
6
|
+
include DeeplyEnumerable::HashExtension
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Array
|
|
10
|
+
include DeeplyEnumerable::ArrayExtension
|
|
11
|
+
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require_relative
|
|
1
|
+
require_relative "enumerable"
|
|
2
2
|
|
|
3
3
|
module DeeplyEnumerable
|
|
4
4
|
module ArrayExtension
|
|
@@ -8,31 +8,63 @@ module DeeplyEnumerable
|
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
# Recursively removes nil elements, leaving every other element (including
|
|
12
|
+
# already-blank ones such as "", [] and {}) untouched. This is the
|
|
13
|
+
# recursive counterpart of Ruby's Array#compact.
|
|
14
|
+
# https://docs.ruby-lang.org/en/master/Array.html#method-i-compact
|
|
15
|
+
def deep_compact
|
|
16
|
+
deep_compact_each(false, false)
|
|
17
|
+
end
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
# In-place variant of #deep_compact, mirroring Array#compact!.
|
|
20
|
+
def deep_compact!
|
|
21
|
+
replace(deep_compact)
|
|
22
|
+
end
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
# Recursively removes nils and elements that are already blank, but keeps
|
|
25
|
+
# collections that only become blank as a result of compaction.
|
|
26
|
+
def deep_compact_existing_blank
|
|
27
|
+
deep_compact_each(false, true)
|
|
28
|
+
end
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
# In-place variant of #deep_compact_existing_blank.
|
|
31
|
+
def deep_compact_existing_blank!
|
|
32
|
+
replace(deep_compact_existing_blank)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Recursively removes nils and collections that became blank through
|
|
36
|
+
# compaction, but keeps elements that were already blank.
|
|
37
|
+
def deep_compact_blanked
|
|
38
|
+
deep_compact_each(true, false)
|
|
39
|
+
end
|
|
30
40
|
|
|
31
|
-
|
|
41
|
+
# In-place variant of #deep_compact_blanked.
|
|
42
|
+
def deep_compact_blanked!
|
|
43
|
+
replace(deep_compact_blanked)
|
|
32
44
|
end
|
|
33
45
|
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
# Recursively removes every blank element (nil, false, "", " ", [], {}).
|
|
47
|
+
# This is the recursive counterpart of Rails' Array#compact_blank.
|
|
48
|
+
# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/enumerable.rb
|
|
49
|
+
def deep_compact_blank
|
|
50
|
+
deep_compact_each(true, true)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# In-place variant of #deep_compact_blank, mirroring Array#compact_blank!.
|
|
54
|
+
def deep_compact_blank!
|
|
55
|
+
replace(deep_compact_blank)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Shared engine for the deep_compact family: rebuilds an array of the same
|
|
61
|
+
# class, recursively compacting each element and discarding the ones that
|
|
62
|
+
# should be removed for the given combination of flags.
|
|
63
|
+
def deep_compact_each(remove_emptied_elements, remove_empty_elements)
|
|
64
|
+
each_with_object(self.class.new) do |element, result|
|
|
65
|
+
keep, element = deep_compact_element(element, remove_emptied_elements, remove_empty_elements)
|
|
66
|
+
result.push(element) if keep
|
|
67
|
+
end
|
|
36
68
|
end
|
|
37
69
|
end
|
|
38
70
|
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
module DeeplyEnumerable
|
|
2
2
|
module Enumerable
|
|
3
|
-
|
|
3
|
+
# Collection-like objects that should be treated as opaque elements rather
|
|
4
|
+
# than recursed into during deep operations.
|
|
5
|
+
UNENUMERABLE = ["ActiveRecord::Relation", "Range"]
|
|
6
|
+
|
|
7
|
+
# Whitespace-only strings are considered blank, mirroring ActiveSupport.
|
|
8
|
+
# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/object/blank.rb
|
|
9
|
+
BLANK_RE = /\A[[:space:]]*\z/
|
|
4
10
|
|
|
5
11
|
def self.included(klass)
|
|
6
12
|
klass.extend(ClassMethods)
|
|
@@ -24,9 +30,28 @@ module DeeplyEnumerable
|
|
|
24
30
|
new.tap { |deeply_enumerable_object| object.each { |value| deeply_enumerable_object.push(rebuild(value)) } }
|
|
25
31
|
end
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
# Recursively removes nil values.
|
|
34
|
+
# Mirrors Ruby's Array#compact / Hash#compact.
|
|
35
|
+
def deep_compact(object)
|
|
36
|
+
deep_rebuild(object).deep_compact
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Recursively removes nils and values that are already blank, while
|
|
40
|
+
# keeping collections that only become blank as a result of compaction.
|
|
41
|
+
def deep_compact_existing_blank(object)
|
|
42
|
+
deep_rebuild(object).deep_compact_existing_blank
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Recursively removes nils and collections that became blank through
|
|
46
|
+
# compaction, while keeping values that were already blank.
|
|
47
|
+
def deep_compact_blanked(object)
|
|
48
|
+
deep_rebuild(object).deep_compact_blanked
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Recursively removes every blank value.
|
|
52
|
+
# Mirrors Rails' Enumerable#compact_blank.
|
|
53
|
+
def deep_compact_blank(object)
|
|
54
|
+
deep_rebuild(object).deep_compact_blank
|
|
30
55
|
end
|
|
31
56
|
|
|
32
57
|
def unenumerable
|
|
@@ -47,5 +72,42 @@ module DeeplyEnumerable
|
|
|
47
72
|
def unenumerable_object?(object)
|
|
48
73
|
self.class.unenumerable.any? { |unenumerable_klass| object.is_a?(unenumerable_klass) }
|
|
49
74
|
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# Decides what to do with a single element during deep compaction and
|
|
79
|
+
# returns [keep?, element], where +element+ may be a rebuilt/compacted
|
|
80
|
+
# copy.
|
|
81
|
+
#
|
|
82
|
+
# nil is always dropped. Nested enumerables are compacted recursively, then
|
|
83
|
+
# dropped if they were already blank (+remove_empty_elements+) or only
|
|
84
|
+
# became blank through compaction (+remove_emptied_elements+).
|
|
85
|
+
# Non-enumerable elements are dropped only when already blank and
|
|
86
|
+
# +remove_empty_elements+ is set.
|
|
87
|
+
def deep_compact_element(element, remove_emptied_elements, remove_empty_elements)
|
|
88
|
+
return [false, element] if element.nil?
|
|
89
|
+
|
|
90
|
+
element = rebuild(element) unless unenumerable_object?(element) || element.respond_to?(:deep_compact_each, true)
|
|
91
|
+
|
|
92
|
+
if !unenumerable_object?(element) && element.respond_to?(:deep_compact_each, true)
|
|
93
|
+
blank_before = deeply_blank?(element)
|
|
94
|
+
element = element.send(:deep_compact_each, remove_emptied_elements, remove_empty_elements)
|
|
95
|
+
keep = !((blank_before && remove_empty_elements) ||
|
|
96
|
+
(!blank_before && remove_emptied_elements && deeply_blank?(element)))
|
|
97
|
+
[keep, element]
|
|
98
|
+
else
|
|
99
|
+
[!(remove_empty_elements && deeply_blank?(element)), element]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Internal equivalent of ActiveSupport's Object#blank? (with
|
|
104
|
+
# String#blank?), implemented here so the gem needs no ActiveSupport
|
|
105
|
+
# dependency.
|
|
106
|
+
# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/object/blank.rb
|
|
107
|
+
def deeply_blank?(value)
|
|
108
|
+
return value.match?(BLANK_RE) if value.is_a?(::String)
|
|
109
|
+
|
|
110
|
+
value.respond_to?(:empty?) ? !!value.empty? : !value
|
|
111
|
+
end
|
|
50
112
|
end
|
|
51
113
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require_relative
|
|
1
|
+
require_relative "enumerable"
|
|
2
2
|
|
|
3
3
|
module DeeplyEnumerable
|
|
4
4
|
module HashExtension
|
|
@@ -13,7 +13,7 @@ module DeeplyEnumerable
|
|
|
13
13
|
this_value = self[current_key]
|
|
14
14
|
|
|
15
15
|
self[current_key] = if this_value.is_a?(::Hash) && other_value.is_a?(::Hash)
|
|
16
|
-
this_value =
|
|
16
|
+
this_value = rebuild(this_value) unless this_value.respond_to?(:reverse_deep_merge)
|
|
17
17
|
this_value.reverse_deep_merge(other_value)
|
|
18
18
|
else
|
|
19
19
|
key?(current_key) ? this_value : other_value
|
|
@@ -29,27 +29,63 @@ module DeeplyEnumerable
|
|
|
29
29
|
end
|
|
30
30
|
alias_method :deep_reverse_merge, :reverse_deep_merge
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
# Recursively removes nil values, leaving every other value (including
|
|
33
|
+
# already-blank ones such as "", [] and {}) untouched. This is the
|
|
34
|
+
# recursive counterpart of Ruby's Hash#compact.
|
|
35
|
+
# https://docs.ruby-lang.org/en/master/Hash.html#method-i-compact
|
|
36
|
+
def deep_compact
|
|
37
|
+
deep_compact_each(false, false)
|
|
38
|
+
end
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
# In-place variant of #deep_compact, mirroring Hash#compact!.
|
|
41
|
+
def deep_compact!
|
|
42
|
+
replace(deep_compact)
|
|
43
|
+
end
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
# Recursively removes nils and values that are already blank, but keeps
|
|
46
|
+
# collections that only become blank as a result of compaction.
|
|
47
|
+
def deep_compact_existing_blank
|
|
48
|
+
deep_compact_each(false, true)
|
|
49
|
+
end
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
# In-place variant of #deep_compact_existing_blank.
|
|
52
|
+
def deep_compact_existing_blank!
|
|
53
|
+
replace(deep_compact_existing_blank)
|
|
54
|
+
end
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
# Recursively removes nils and collections that became blank through
|
|
57
|
+
# compaction, but keeps values that were already blank.
|
|
58
|
+
def deep_compact_blanked
|
|
59
|
+
deep_compact_each(true, false)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# In-place variant of #deep_compact_blanked.
|
|
63
|
+
def deep_compact_blanked!
|
|
64
|
+
replace(deep_compact_blanked)
|
|
49
65
|
end
|
|
50
66
|
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
# Recursively removes every blank value (nil, false, "", " ", [], {}). This
|
|
68
|
+
# is the recursive counterpart of Rails' Hash#compact_blank.
|
|
69
|
+
# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/hash/keys.rb
|
|
70
|
+
def deep_compact_blank
|
|
71
|
+
deep_compact_each(true, true)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# In-place variant of #deep_compact_blank, mirroring Hash#compact_blank!.
|
|
75
|
+
def deep_compact_blank!
|
|
76
|
+
replace(deep_compact_blank)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
# Shared engine for the deep_compact family: rebuilds a hash of the same
|
|
82
|
+
# class, recursively compacting each value and discarding the keys whose
|
|
83
|
+
# value should be removed for the given combination of flags.
|
|
84
|
+
def deep_compact_each(remove_emptied_elements, remove_empty_elements)
|
|
85
|
+
each_with_object(self.class.new) do |(key, value), result|
|
|
86
|
+
keep, value = deep_compact_element(value, remove_emptied_elements, remove_empty_elements)
|
|
87
|
+
result[key] = value if keep
|
|
88
|
+
end
|
|
53
89
|
end
|
|
54
90
|
end
|
|
55
91
|
|
|
@@ -70,5 +106,3 @@ module DeeplyEnumerable
|
|
|
70
106
|
end
|
|
71
107
|
end
|
|
72
108
|
end
|
|
73
|
-
|
|
74
|
-
|
data/lib/deeply_enumerable.rb
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
1
|
+
require "deeply_enumerable/array"
|
|
2
|
+
require "deeply_enumerable/hash"
|
metadata
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: deeply_enumerable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- chaunce
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rake
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
13
26
|
- !ruby/object:Gem::Dependency
|
|
14
27
|
name: rspec
|
|
15
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -24,13 +37,14 @@ dependencies:
|
|
|
24
37
|
- - ">="
|
|
25
38
|
- !ruby/object:Gem::Version
|
|
26
39
|
version: '0'
|
|
27
|
-
description:
|
|
40
|
+
description: recursive support for enumerable operations
|
|
28
41
|
email:
|
|
29
42
|
- chaunce.slc@gmail.com
|
|
30
43
|
executables: []
|
|
31
44
|
extensions: []
|
|
32
45
|
extra_rdoc_files: []
|
|
33
46
|
files:
|
|
47
|
+
- CHANGELOG.md
|
|
34
48
|
- MIT-LICENSE
|
|
35
49
|
- README.md
|
|
36
50
|
- Rakefile
|
|
@@ -43,8 +57,9 @@ files:
|
|
|
43
57
|
homepage: https://github.com/chaunce/deeply_enumerable
|
|
44
58
|
licenses:
|
|
45
59
|
- MIT
|
|
46
|
-
metadata:
|
|
47
|
-
|
|
60
|
+
metadata:
|
|
61
|
+
source_code_uri: https://github.com/chaunce/deeply_enumerable
|
|
62
|
+
changelog_uri: https://github.com/chaunce/deeply_enumerable/blob/master/CHANGELOG.md
|
|
48
63
|
rdoc_options: []
|
|
49
64
|
require_paths:
|
|
50
65
|
- lib
|
|
@@ -59,8 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
59
74
|
- !ruby/object:Gem::Version
|
|
60
75
|
version: '0'
|
|
61
76
|
requirements: []
|
|
62
|
-
rubygems_version:
|
|
63
|
-
signing_key:
|
|
77
|
+
rubygems_version: 4.0.6
|
|
64
78
|
specification_version: 4
|
|
65
|
-
summary:
|
|
79
|
+
summary: recursive support for enumerable operations
|
|
66
80
|
test_files: []
|