key_tree 0.5.3 → 0.6.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/.rubocop.yml +3 -0
- data/Gemfile +2 -0
- data/RELEASE_NOTES.md +47 -0
- data/Rakefile +3 -1
- data/bin/console +1 -0
- data/key_tree.gemspec +13 -5
- data/lib/key_tree/forest.rb +42 -16
- data/lib/key_tree/loader/nil.rb +2 -0
- data/lib/key_tree/loader.rb +2 -0
- data/lib/key_tree/meta_data.rb +3 -1
- data/lib/key_tree/path.rb +33 -36
- data/lib/key_tree/refine/deep_hash.rb +128 -0
- data/lib/key_tree/refinements.rb +40 -0
- data/lib/key_tree/tree.rb +101 -74
- data/lib/key_tree.rb +8 -13
- metadata +28 -13
- data/lib/key_tree/version.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 535d58bfc5880880fe498d115be5d62a7cb4670661c17d46f4a44087f02c36a2
|
4
|
+
data.tar.gz: 42896ddb6f71accd5a23504ae63e38abf242bf6f839df4b599d7e567c48fcb4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0e0c52ca0cc4766b226d8f31efcf1e97aa154a8a67f132d5f4f2c0764bd2c45ff05e8a9686f25ed3bfcc87caa144960ffd9dfdcf083084e71d490d9418d2444
|
7
|
+
data.tar.gz: 8cff399bdf835fb25fb45d0c50cf562e7f080ef9d5a88b7fd53c2a9c029b747fa1065bbf83455ffe1ef7a91eba5a91ef28b0ee2688eb06c942a7a4110a0c6eb7
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/RELEASE_NOTES.md
CHANGED
@@ -1,5 +1,52 @@
|
|
1
1
|
# Release Notes
|
2
2
|
|
3
|
+
## v0.6.0 – 2018-05-30
|
4
|
+
|
5
|
+
### Major changes
|
6
|
+
|
7
|
+
* Updated to Ruby ~> 2.3
|
8
|
+
* Added refinements module for `to_key_*` conversions in core classes
|
9
|
+
* `KeyTree::Tree` rewritten from scratch, to use a refinements
|
10
|
+
to an internal `Hash` structure instead of subclassing `Hash`
|
11
|
+
|
12
|
+
### New methods
|
13
|
+
|
14
|
+
* `KeyTree::Forest#key_paths`
|
15
|
+
* `KeyTree::Forest#to_key_forest`
|
16
|
+
* `KeyTree::Forest#to_key_wood`
|
17
|
+
* `KeyTree::Path#===`
|
18
|
+
* `KeyTree::Path#to_key_path`
|
19
|
+
* `KeyTree::Tree#delete(key_path)`
|
20
|
+
* `KeyTree::Tree#delete!(key_path)`
|
21
|
+
* `KeyTree::Tree#store(key_path, new_value)`
|
22
|
+
* `KeyTree::Tree#store!(key_path, new_value)`
|
23
|
+
* `KeyTree::Tree#to_key_tree`
|
24
|
+
* `KeyTree::Tree#to_key_wood`
|
25
|
+
|
26
|
+
* Using `KeyTree::Refinements`
|
27
|
+
* `Array#to_key_forest`
|
28
|
+
* `Array#to_key_path`
|
29
|
+
* `Array#to_key_wood`
|
30
|
+
* `Hash#to_key_tree`
|
31
|
+
* `Hash#to_key_wood`
|
32
|
+
* `String#to_key_path`
|
33
|
+
* `Symbol#to_key_path`
|
34
|
+
|
35
|
+
* Using `KeyTree::Refine::DeepHash`
|
36
|
+
* `Hash#deep`
|
37
|
+
* `Hash#deep_delete(key_path)`
|
38
|
+
* `Hash#deep_fetch(key_path, default, &default_proc)`
|
39
|
+
* `Hash#deep_merge(other)`
|
40
|
+
* `Hash#deep_merge!(other)`
|
41
|
+
* `Hash#deep_store(key_path, new_value)`
|
42
|
+
* `Hash#deep_transform_keys(&block)`
|
43
|
+
* `Hash#deep_transform_keys!(&block)`
|
44
|
+
|
45
|
+
### Removed methods
|
46
|
+
|
47
|
+
* `KeyTree::Tree` no longer inherits `Hash`, but most of the
|
48
|
+
inherited methods didn't work properly anyway
|
49
|
+
|
3
50
|
## v0.5.3 – 2018-05-25
|
4
51
|
|
5
52
|
### Bug fixes
|
data/Rakefile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'bundler/setup'
|
2
4
|
require 'bundler/gem_tasks'
|
3
5
|
|
@@ -11,4 +13,4 @@ desc 'Run RSpec'
|
|
11
13
|
require 'rspec/core/rake_task'
|
12
14
|
RSpec::Core::RakeTask.new(:spec)
|
13
15
|
|
14
|
-
task default: %i[rubocop
|
16
|
+
task default: %i[spec rubocop]
|
data/bin/console
CHANGED
data/key_tree.gemspec
CHANGED
@@ -1,17 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'git-version-bump'
|
8
|
+
GIT_VERSION = GVB.version.freeze
|
9
|
+
rescue LoadError
|
10
|
+
GIT_VERSION = '0.0.0.UNDEF'
|
11
|
+
end
|
5
12
|
|
6
13
|
Gem::Specification.new do |spec|
|
7
14
|
spec.name = 'key_tree'
|
8
|
-
spec.version =
|
9
|
-
spec.date = KeyTree::DATE
|
15
|
+
spec.version = GIT_VERSION
|
10
16
|
spec.authors = ['Calle Englund']
|
11
17
|
spec.email = ['calle@discord.bofh.se']
|
12
18
|
|
13
19
|
spec.summary = 'Manage trees of keys'
|
14
|
-
spec.description = spec.summary
|
15
20
|
spec.homepage = 'https://github.com/notcalle/ruby-keytree'
|
16
21
|
spec.license = 'MIT'
|
17
22
|
|
@@ -22,10 +27,13 @@ Gem::Specification.new do |spec|
|
|
22
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
28
|
spec.require_paths = ['lib']
|
24
29
|
|
25
|
-
spec.
|
30
|
+
spec.platform = Gem::Platform::RUBY
|
31
|
+
spec.required_ruby_version = '~> 2.3'
|
26
32
|
|
27
33
|
spec.add_development_dependency 'bundler', '~> 1.16'
|
34
|
+
spec.add_development_dependency 'git-version-bump', '~> 0.15'
|
28
35
|
spec.add_development_dependency 'rake', '~> 10.0'
|
29
36
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
30
37
|
spec.add_development_dependency 'rubocop', '~> 0.52'
|
38
|
+
spec.add_development_dependency 'ruby-prof', '~> 0.17'
|
31
39
|
end
|
data/lib/key_tree/forest.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'meta_data'
|
4
|
+
require_relative 'refinements'
|
5
|
+
require_relative 'tree'
|
6
|
+
|
7
|
+
module KeyTree # rubocop:disable Style/Documentation
|
8
|
+
using Refinements
|
3
9
|
|
4
|
-
module KeyTree
|
5
10
|
#
|
6
11
|
# A forest is a (possibly nested) collection of trees
|
7
12
|
#
|
@@ -10,10 +15,13 @@ module KeyTree
|
|
10
15
|
|
11
16
|
def self.[](*contents)
|
12
17
|
contents.reduce(Forest.new) do |result, content|
|
13
|
-
result <<
|
18
|
+
result << content.to_key_wood
|
14
19
|
end
|
15
20
|
end
|
16
21
|
|
22
|
+
alias to_key_forest itself
|
23
|
+
alias to_key_wood itself
|
24
|
+
|
17
25
|
# For a numeric key, return the n:th tree in the forest
|
18
26
|
#
|
19
27
|
# For a key path convertable key, return the closest match in the forest
|
@@ -24,8 +32,12 @@ module KeyTree
|
|
24
32
|
#
|
25
33
|
def [](key)
|
26
34
|
return super(key) if key.is_a?(Numeric)
|
27
|
-
|
28
|
-
|
35
|
+
|
36
|
+
trees.lazy.each do |tree|
|
37
|
+
result = tree[key]
|
38
|
+
return result unless result.nil?
|
39
|
+
break if tree.prefix?(key)
|
40
|
+
end
|
29
41
|
nil
|
30
42
|
end
|
31
43
|
|
@@ -37,11 +49,27 @@ module KeyTree
|
|
37
49
|
end
|
38
50
|
|
39
51
|
def key?(key)
|
40
|
-
trees.any? { |tree| tree.key?(key) }
|
52
|
+
trees.lazy.any? { |tree| tree.key?(key) }
|
41
53
|
end
|
54
|
+
alias has_key? key?
|
42
55
|
|
43
56
|
def prefix?(key)
|
44
|
-
trees.any? { |
|
57
|
+
trees.lazy.any? { |tree| tree.prefix?(key) }
|
58
|
+
end
|
59
|
+
alias has_prefix? prefix?
|
60
|
+
|
61
|
+
def key_path?(key)
|
62
|
+
trees.lazy.any? { |tree| tree.key_path?(key) }
|
63
|
+
end
|
64
|
+
alias has_key_path? key_path?
|
65
|
+
|
66
|
+
def include?(needle)
|
67
|
+
case needle
|
68
|
+
when Tree, Forest
|
69
|
+
super(needle)
|
70
|
+
else
|
71
|
+
key_path?(needle)
|
72
|
+
end
|
45
73
|
end
|
46
74
|
|
47
75
|
# Flattening a forest produces a tree with the equivalent view of key paths
|
@@ -64,17 +92,15 @@ module KeyTree
|
|
64
92
|
end
|
65
93
|
end
|
66
94
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
result = trees.detect do |tree|
|
71
|
-
tree.prefix?(key) || tree.default_key?(key)
|
72
|
-
end
|
73
|
-
result || raise(KeyError, %(key not found: "#{key}"))
|
95
|
+
# Return all visible key paths in the forest
|
96
|
+
def key_paths
|
97
|
+
trees.reduce(Set.new) { |result, tree| result.merge(tree.key_paths) }
|
74
98
|
end
|
75
99
|
|
100
|
+
private
|
101
|
+
|
76
102
|
def tree_with_key(key)
|
77
|
-
result = trees.detect do |tree|
|
103
|
+
result = trees.lazy.detect do |tree|
|
78
104
|
tree.prefix?(key)
|
79
105
|
end
|
80
106
|
result || raise(KeyError, %(key not found: "#{key}"))
|
data/lib/key_tree/loader/nil.rb
CHANGED
data/lib/key_tree/loader.rb
CHANGED
data/lib/key_tree/meta_data.rb
CHANGED
data/lib/key_tree/path.rb
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'refinements'
|
4
|
+
|
5
|
+
module KeyTree # rubocop:disable Style/Documentation
|
6
|
+
using Refinements
|
7
|
+
|
2
8
|
#
|
3
9
|
# Representation of the key path to a value in a key tree
|
4
10
|
#
|
@@ -8,9 +14,9 @@ module KeyTree
|
|
8
14
|
#
|
9
15
|
# Make a new key path from one or more keys or paths
|
10
16
|
#
|
11
|
-
def self.[](*
|
12
|
-
|
13
|
-
result <<
|
17
|
+
def self.[](*key_paths)
|
18
|
+
key_paths.reduce(Path.new) do |result, key_path|
|
19
|
+
result << key_path.to_key_path
|
14
20
|
end
|
15
21
|
end
|
16
22
|
|
@@ -24,19 +30,21 @@ module KeyTree
|
|
24
30
|
# KeyTree::Path.new("a.b.c")
|
25
31
|
# => ["a", "b", "c"]
|
26
32
|
#
|
27
|
-
def initialize(
|
28
|
-
case
|
29
|
-
when
|
30
|
-
|
31
|
-
when Symbol
|
32
|
-
initialize(key_or_path.to_s)
|
33
|
+
def initialize(key_path = nil)
|
34
|
+
case key_path
|
35
|
+
when NilClass
|
36
|
+
nil
|
33
37
|
when Array
|
34
|
-
|
38
|
+
concat(key_path.map(&:to_sym))
|
35
39
|
else
|
36
|
-
|
40
|
+
initialize(key_path.to_key_path)
|
37
41
|
end
|
38
42
|
end
|
39
43
|
|
44
|
+
def to_key_path
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
40
48
|
def to_s
|
41
49
|
join('.')
|
42
50
|
end
|
@@ -46,45 +54,34 @@ module KeyTree
|
|
46
54
|
end
|
47
55
|
|
48
56
|
def <<(other)
|
49
|
-
|
50
|
-
when Path
|
51
|
-
other.reduce(self) do |result, key|
|
52
|
-
result.append(key)
|
53
|
-
end
|
54
|
-
else
|
55
|
-
self << Path[other]
|
56
|
-
end
|
57
|
+
concat(other.to_key_path)
|
57
58
|
end
|
58
59
|
|
59
60
|
def +(other)
|
60
|
-
dup
|
61
|
+
dup.concat(other.to_key_path)
|
61
62
|
end
|
62
63
|
|
63
|
-
#
|
64
|
-
#
|
65
|
-
# Returns a key path without the leading prefix
|
66
|
-
#
|
67
|
-
# drop(+n+)
|
64
|
+
# Returns a key path without the leading +prefix+
|
68
65
|
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
drop(other.length)
|
76
|
-
else
|
77
|
-
super(prefix)
|
78
|
-
end
|
66
|
+
# :call-seq:
|
67
|
+
# Path - other => Path
|
68
|
+
def -(other)
|
69
|
+
other = other.to_key_path
|
70
|
+
raise KeyError unless prefix?(other)
|
71
|
+
super(other.length)
|
79
72
|
end
|
80
73
|
|
81
74
|
# Is +other+ a prefix?
|
82
75
|
#
|
76
|
+
# :call-seq:
|
77
|
+
# prefix?(other) => boolean
|
83
78
|
def prefix?(other)
|
79
|
+
other = other.to_key_path
|
84
80
|
return false if other.length > length
|
85
81
|
key_enum = each
|
86
82
|
other.all? { |other_key| key_enum.next == other_key }
|
87
83
|
end
|
84
|
+
alias === prefix?
|
88
85
|
|
89
86
|
# Would +other+ conflict?
|
90
87
|
#
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeyTree
|
4
|
+
module Refine
|
5
|
+
# Refinements to Hash for deep_ methods, for traversing nested structures
|
6
|
+
module DeepHash
|
7
|
+
refine Hash do # rubocop:disable Metrics/BlockLength
|
8
|
+
# Return a deep enumerator for all (+key_path+, +value+) pairs in a
|
9
|
+
# nested hash structure.
|
10
|
+
#
|
11
|
+
# :call-seq:
|
12
|
+
# deep => Enumerator
|
13
|
+
def deep
|
14
|
+
Enumerator.new do |yielder|
|
15
|
+
deep_enumerator(yielder)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Fetch a leaf value from a nested hash structure
|
20
|
+
#
|
21
|
+
# :call-seq:
|
22
|
+
# deep_fetch(key_path) => value
|
23
|
+
# deep_fetch(key_path, default) => value || default
|
24
|
+
# deep_fetch(key_path) { |key_path| block } => value || block
|
25
|
+
def deep_fetch(key_path, *args, &key_missing)
|
26
|
+
key_error = [KeyError, %(key path invalid: "#{key_path}")]
|
27
|
+
result = key_path.reduce(self) do |hash, key|
|
28
|
+
raise(*key_error) unless hash.is_a?(Hash)
|
29
|
+
hash.fetch(key, *args, &key_missing)
|
30
|
+
end
|
31
|
+
return result unless result.is_a?(Hash)
|
32
|
+
raise(*key_error)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Store a new value in a nested hash structure, expanding it
|
36
|
+
# if necessary.
|
37
|
+
#
|
38
|
+
# :call-seq:
|
39
|
+
# deep_store(key_path, new_value) => new_value
|
40
|
+
#
|
41
|
+
# Raises KeyError if a prefix of the +key_path+ has a value.
|
42
|
+
def deep_store(key_path, new_value)
|
43
|
+
*prefix_path, last_key = key_path
|
44
|
+
result = prefix_path.reduce(self) do |hash, key|
|
45
|
+
result = hash.fetch(key) { hash[key] = {} }
|
46
|
+
next result if result.is_a?(Hash)
|
47
|
+
raise KeyError, %(prefix has value: "#{key_path}")
|
48
|
+
end
|
49
|
+
result[last_key] = new_value
|
50
|
+
end
|
51
|
+
|
52
|
+
# Delete a leaf value in a nested hash structure
|
53
|
+
#
|
54
|
+
# :call-seq:
|
55
|
+
# deep_delete(key_path)
|
56
|
+
#
|
57
|
+
# Raises KeyError if a prefix of the +key_path+ has a value.
|
58
|
+
def deep_delete(key_path)
|
59
|
+
*prefix_path, last_key = key_path
|
60
|
+
result = prefix_path.reduce(self) do |hash, key|
|
61
|
+
result = hash.fetch(key, nil)
|
62
|
+
next result if result.is_a?(Hash)
|
63
|
+
raise KeyError, %(prefix has value: "#{key_path}")
|
64
|
+
end
|
65
|
+
result.delete(last_key)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Deeply merge nested hash structures
|
69
|
+
#
|
70
|
+
# :call-seq:
|
71
|
+
# deep_merge!(other) => self
|
72
|
+
# deep_merge!(other) { |key, lhs, rhs| } => self
|
73
|
+
def deep_merge!(other)
|
74
|
+
merge!(other) do |key, lhs, rhs|
|
75
|
+
next lhs.merge!(rhs) if lhs.is_a?(Hash) && rhs.is_a?(Hash)
|
76
|
+
next yield(key, lhs, rhs) if block_given?
|
77
|
+
rhs
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Deeply merge nested hash structures
|
82
|
+
#
|
83
|
+
# :call-seq:
|
84
|
+
# deep_merge(other) => self
|
85
|
+
# deep_merge(other) { |key, lhs, rhs| } => self
|
86
|
+
def deep_merge(other)
|
87
|
+
merge(other) do |key, lhs, rhs|
|
88
|
+
next lhs.merge(rhs) if lhs.is_a?(Hash) && rhs.is_a?(Hash)
|
89
|
+
next yield(key, lhs, rhs) if block_given?
|
90
|
+
rhs
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Transform keys in a nested hash structure
|
95
|
+
#
|
96
|
+
# :call-seq:
|
97
|
+
# deep_transform_keys { |key| block }
|
98
|
+
def deep_transform_keys(&block)
|
99
|
+
result = transform_keys(&block)
|
100
|
+
result.transform_values! do |value|
|
101
|
+
next value unless value.is_a?(Hash)
|
102
|
+
value.deep_transform_keys(&block)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Transform keys in a nested hash structure
|
107
|
+
#
|
108
|
+
# :call-seq:
|
109
|
+
# deep_transform_keys! { |key| block }
|
110
|
+
def deep_transform_keys!(&block)
|
111
|
+
result = transform_keys!(&block)
|
112
|
+
result.transform_values! do |value|
|
113
|
+
next value unless value.is_a?(Hash)
|
114
|
+
value.deep_transform_keys!(&block)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def deep_enumerator(yielder, prefix = [])
|
119
|
+
each do |key, value|
|
120
|
+
key_path = prefix + [key]
|
121
|
+
yielder << [key_path, value]
|
122
|
+
value.deep_enumerator(yielder, key_path) if value.is_a?(Hash)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'forest'
|
4
|
+
require_relative 'path'
|
5
|
+
require_relative 'tree'
|
6
|
+
|
7
|
+
module KeyTree
|
8
|
+
# KeyTree refinements to core classes
|
9
|
+
module Refinements
|
10
|
+
refine Array do
|
11
|
+
def to_key_forest
|
12
|
+
Forest[*map(&:to_key_wood)]
|
13
|
+
end
|
14
|
+
alias_method :to_key_wood, :to_key_forest
|
15
|
+
|
16
|
+
def to_key_path
|
17
|
+
Path.new(self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
refine Hash do
|
22
|
+
def to_key_tree
|
23
|
+
Tree[self]
|
24
|
+
end
|
25
|
+
alias_method :to_key_wood, :to_key_tree
|
26
|
+
end
|
27
|
+
|
28
|
+
refine String do
|
29
|
+
def to_key_path
|
30
|
+
split('.').to_key_path
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
refine Symbol do
|
35
|
+
def to_key_path
|
36
|
+
to_s.to_key_path
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/key_tree/tree.rb
CHANGED
@@ -1,118 +1,145 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require_relative 'meta_data'
|
5
|
+
require_relative 'path'
|
6
|
+
require_relative 'refinements'
|
7
|
+
require_relative 'refine/deep_hash'
|
8
|
+
|
9
|
+
module KeyTree # rubocop:disable Style/Documentation
|
10
|
+
using Refinements
|
11
|
+
using Refine::DeepHash
|
3
12
|
|
4
|
-
module KeyTree
|
5
13
|
# A tree of key-value lookup tables (hashes)
|
6
|
-
class Tree
|
14
|
+
class Tree
|
7
15
|
include MetaData
|
16
|
+
extend Forwardable
|
8
17
|
#
|
9
18
|
# KeyTree::Tree.new(+hash+)
|
10
19
|
#
|
11
20
|
# Initialize a new KeyTree from nested Hash:es
|
12
21
|
#
|
13
22
|
def self.[](hash = {})
|
14
|
-
|
15
|
-
hash.each do |key, value|
|
16
|
-
keytree[key] = value
|
17
|
-
end
|
18
|
-
keytree
|
23
|
+
new(hash)
|
19
24
|
end
|
20
25
|
|
21
|
-
def
|
22
|
-
|
26
|
+
def initialize(hash = {}, default = nil, &default_proc)
|
27
|
+
@hash = hash.to_h.deep_transform_keys(&:to_sym)
|
28
|
+
@default = default
|
29
|
+
@default_proc = default_proc
|
23
30
|
end
|
24
31
|
|
25
|
-
|
26
|
-
super(Path[key_or_path], *args, &missing_key)
|
27
|
-
end
|
32
|
+
attr_reader :default, :default_proc
|
28
33
|
|
29
|
-
|
30
|
-
|
31
|
-
end
|
34
|
+
alias to_key_tree itself
|
35
|
+
alias to_key_wood itself
|
32
36
|
|
33
|
-
|
34
|
-
path = Path[key_or_path]
|
37
|
+
delegate %i[empty? to_h to_json] => :@hash
|
35
38
|
|
36
|
-
|
39
|
+
# Convert a Tree to YAML, with string keys
|
40
|
+
#
|
41
|
+
# :call-seq:
|
42
|
+
# to_yaml => String
|
43
|
+
def to_yaml
|
44
|
+
to_h.deep_transform_keys(&:to_s).to_yaml
|
45
|
+
end
|
37
46
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
super(path, new_value)
|
47
|
+
def [](key_path)
|
48
|
+
fetch(key_path) do
|
49
|
+
next default_proc.call(self, key_path) unless default_proc.nil?
|
50
|
+
default
|
43
51
|
end
|
52
|
+
rescue KeyError
|
53
|
+
default
|
44
54
|
end
|
45
55
|
|
46
|
-
def
|
47
|
-
|
56
|
+
def fetch(key_path, *args, &key_missing)
|
57
|
+
@hash.deep_fetch(key_path.to_key_path, *args, &key_missing)
|
48
58
|
end
|
49
59
|
|
50
|
-
def
|
51
|
-
|
52
|
-
default_proc.yield(self, Path[key_or_path])
|
53
|
-
true
|
54
|
-
rescue KeyError
|
55
|
-
false
|
60
|
+
def store(key_path, new_value)
|
61
|
+
@hash.deep_store(key_path.to_key_path, new_value)
|
56
62
|
end
|
57
63
|
|
58
|
-
def
|
59
|
-
|
64
|
+
def store!(key_path, new_value)
|
65
|
+
store(key_path, new_value)
|
66
|
+
rescue KeyError
|
67
|
+
delete!(key_path)
|
68
|
+
retry
|
60
69
|
end
|
70
|
+
alias []= store!
|
61
71
|
|
62
|
-
def
|
63
|
-
|
72
|
+
def delete(key_path)
|
73
|
+
@hash.deep_delete(key_path.to_key_path)
|
64
74
|
end
|
65
75
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
delete_if { |key, _| other.conflict?(key) }
|
72
|
-
super
|
76
|
+
def delete!(key_path)
|
77
|
+
delete(key_path)
|
78
|
+
rescue KeyError
|
79
|
+
key_path = key_path[0..-2]
|
80
|
+
retry
|
73
81
|
end
|
74
|
-
alias << merge!
|
75
82
|
|
76
|
-
def
|
77
|
-
|
83
|
+
def values_at(*key_paths)
|
84
|
+
key_paths.map { |key_path| self[key_path] }
|
78
85
|
end
|
79
|
-
alias + merge
|
80
86
|
|
81
|
-
#
|
82
|
-
|
83
|
-
|
87
|
+
# Return all maximal key paths in a tree
|
88
|
+
#
|
89
|
+
# :call-seq:
|
90
|
+
# keys => Array of KeyTree::Path
|
91
|
+
def keys
|
92
|
+
@hash.deep.each_with_object([]) do |(key_path, value), result|
|
93
|
+
result << key_path.to_key_path unless value.is_a?(Hash)
|
94
|
+
end
|
84
95
|
end
|
96
|
+
alias key_paths keys
|
85
97
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
to_hash_tree(**kwargs)
|
98
|
+
def include?(key_path)
|
99
|
+
fetch(key_path)
|
100
|
+
true
|
101
|
+
rescue KeyError
|
102
|
+
false
|
92
103
|
end
|
104
|
+
alias key? include?
|
105
|
+
alias has_key? include?
|
106
|
+
alias key_path? include?
|
107
|
+
alias has_key_path? include?
|
93
108
|
|
94
|
-
|
95
|
-
|
96
|
-
|
109
|
+
def prefix?(key_path)
|
110
|
+
key_path.to_key_path.reduce(@hash) do |subtree, key|
|
111
|
+
return false unless subtree.is_a?(Hash)
|
112
|
+
return false unless subtree.key?(key)
|
113
|
+
subtree[key]
|
114
|
+
end
|
115
|
+
true
|
97
116
|
end
|
117
|
+
alias has_prefix? prefix?
|
98
118
|
|
99
|
-
|
100
|
-
|
101
|
-
to_hash_tree(string_keys: true).to_yaml
|
119
|
+
def value?(needle)
|
120
|
+
@hash.deep.lazy.any? { |(_, straw)| straw == needle }
|
102
121
|
end
|
122
|
+
alias has_value? value?
|
103
123
|
|
104
|
-
|
124
|
+
# Merge values from +other+ tree into self
|
125
|
+
#
|
126
|
+
# :call-seq:
|
127
|
+
# merge!(other) => self
|
128
|
+
# merge!(other) { |key, lhs, rhs| } => self
|
129
|
+
def merge!(other, &block)
|
130
|
+
@hash.deep_merge!(other.to_h, &block)
|
131
|
+
self
|
132
|
+
end
|
133
|
+
alias << merge!
|
105
134
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
next first_value if first_key.nil? || first_key.empty?
|
114
|
-
to_hash_tree(next_level)
|
115
|
-
end
|
135
|
+
# Return a new tree by merging values from +other+ tree
|
136
|
+
#
|
137
|
+
# :call-seq:
|
138
|
+
# merge(other) => Tree
|
139
|
+
# merge(other) { |key, lhs, rhs| } => Tree
|
140
|
+
def merge(other, &block)
|
141
|
+
@hash.deep_merge(other.to_h, &block).to_key_tree
|
116
142
|
end
|
143
|
+
alias + merge
|
117
144
|
end
|
118
145
|
end
|
data/lib/key_tree.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
3
|
require 'key_tree/forest'
|
4
4
|
require 'key_tree/loader'
|
5
|
+
require 'key_tree/refinements'
|
6
|
+
require 'key_tree/tree'
|
5
7
|
|
6
8
|
# Manage a tree of keys
|
7
9
|
#
|
@@ -13,17 +15,10 @@ require 'key_tree/loader'
|
|
13
15
|
# -> 2
|
14
16
|
#
|
15
17
|
module KeyTree
|
18
|
+
using Refinements
|
19
|
+
|
16
20
|
def self.[](contents = {})
|
17
|
-
|
18
|
-
when Tree, Forest
|
19
|
-
contents
|
20
|
-
when Hash
|
21
|
-
Tree[contents]
|
22
|
-
when Array
|
23
|
-
Forest[*contents]
|
24
|
-
else
|
25
|
-
raise ArgumentError, "can't load #{contents.class} into a KeyTree"
|
26
|
-
end
|
21
|
+
contents.to_key_wood
|
27
22
|
end
|
28
23
|
|
29
24
|
# Load a KeyTree from some external serialization
|
@@ -49,7 +44,7 @@ module KeyTree
|
|
49
44
|
contents = loader.load(serialization)
|
50
45
|
contents = { prefix => contents } unless prefix.nil?
|
51
46
|
|
52
|
-
|
47
|
+
contents.to_key_wood.with_meta_data do |meta_data|
|
53
48
|
meta_data << { load: { type: type, loader: loader } }
|
54
49
|
meta_data << { load: { prefix: prefix } } unless prefix.nil?
|
55
50
|
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: key_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Calle Englund
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
11
|
+
date: 2018-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
-
type: :
|
19
|
+
version: '1.16'
|
20
|
+
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '1.16'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: git-version-bump
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0.15'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0.15'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,7 +80,21 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0.52'
|
83
|
-
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: ruby-prof
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.17'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.17'
|
97
|
+
description:
|
84
98
|
email:
|
85
99
|
- calle@discord.bofh.se
|
86
100
|
executables: []
|
@@ -106,8 +120,9 @@ files:
|
|
106
120
|
- lib/key_tree/loader/nil.rb
|
107
121
|
- lib/key_tree/meta_data.rb
|
108
122
|
- lib/key_tree/path.rb
|
123
|
+
- lib/key_tree/refine/deep_hash.rb
|
124
|
+
- lib/key_tree/refinements.rb
|
109
125
|
- lib/key_tree/tree.rb
|
110
|
-
- lib/key_tree/version.rb
|
111
126
|
homepage: https://github.com/notcalle/ruby-keytree
|
112
127
|
licenses:
|
113
128
|
- MIT
|
@@ -118,9 +133,9 @@ require_paths:
|
|
118
133
|
- lib
|
119
134
|
required_ruby_version: !ruby/object:Gem::Requirement
|
120
135
|
requirements:
|
121
|
-
- - "
|
136
|
+
- - "~>"
|
122
137
|
- !ruby/object:Gem::Version
|
123
|
-
version: '
|
138
|
+
version: '2.3'
|
124
139
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
140
|
requirements:
|
126
141
|
- - ">="
|