key_tree 0.5.2 → 0.8.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.
@@ -1,7 +1,12 @@
1
- require 'key_tree/tree'
2
- require 'key_tree/meta_data'
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 << KeyTree[content]
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
@@ -22,41 +30,59 @@ module KeyTree
22
30
  # key path matches in trees further away, returning nil. This preserves
23
31
  # the constraints that only leaves may contain a value.
24
32
  #
25
- def [](key, &merger)
33
+ def [](key)
26
34
  return super(key) if key.is_a?(Numeric)
27
- fetch(key, &merger)
28
- rescue KeyError
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
 
32
- def fetch(key)
33
- return tree_with_key(key)[key] unless block_given?
44
+ # Fetch a value from a forest
45
+ #
46
+ # :call-seq:
47
+ # fetch(key) => value
48
+ # fetch(key, default) => value
49
+ # fetch(key) { |key| } => value
50
+ #
51
+ # The first form raises a +KeyError+ unless +key+ has a value.
52
+ def fetch(key, *default)
53
+ trees.lazy.each do |tree|
54
+ catch do |ball|
55
+ return tree.fetch(key) { throw ball }
56
+ end
57
+ end
58
+ return yield(key) if block_given?
59
+ return default.first unless default.empty?
34
60
 
35
- values = trees_with_key(key).map { |tree| tree[key] }
36
- values.reverse.reduce { |left, right| yield(key, left, right) }
61
+ raise KeyError, %(key not found: "#{key}")
37
62
  end
38
63
 
39
- def tree_with_key(key)
40
- result = trees.detect do |tree|
41
- tree.prefix?(key) || tree.default_key?(key)
42
- end
43
- result || raise(KeyError, "key not found: #{key}")
64
+ def key?(key)
65
+ trees.lazy.any? { |tree| tree.key?(key) }
44
66
  end
67
+ alias has_key? key?
45
68
 
46
- def trees_with_key(key)
47
- result = trees.select do |tree|
48
- tree.prefix?(key) || tree.default_key?(key)
49
- end
50
- raise(KeyError, "key not found: #{key}") if result.empty?
51
- result
69
+ def prefix?(key)
70
+ trees.lazy.any? { |tree| tree.prefix?(key) }
52
71
  end
72
+ alias has_prefix? prefix?
53
73
 
54
- def key?(key)
55
- trees.any? { |tree| tree.key?(key) }
74
+ def key_path?(key)
75
+ trees.lazy.any? { |tree| tree.key_path?(key) }
56
76
  end
77
+ alias has_key_path? key_path?
57
78
 
58
- def prefix?(key)
59
- trees.any? { |tree_or_forest| tree_or_forest.prefix?(key) }
79
+ def include?(needle)
80
+ case needle
81
+ when Tree, Forest
82
+ super(needle)
83
+ else
84
+ key_path?(needle)
85
+ end
60
86
  end
61
87
 
62
88
  # Flattening a forest produces a tree with the equivalent view of key paths
@@ -74,9 +100,15 @@ module KeyTree
74
100
  remaining = [self]
75
101
  remaining.each do |woods|
76
102
  next yielder << woods if woods.is_a?(Tree)
103
+
77
104
  woods.each { |wood| remaining << wood }
78
105
  end
79
106
  end
80
107
  end
108
+
109
+ # Return all visible key paths in the forest
110
+ def key_paths
111
+ trees.reduce(Set.new) { |result, tree| result.merge(tree.key_paths) }
112
+ end
81
113
  end
82
114
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'key_tree/loader/nil'
2
4
 
3
5
  module KeyTree
@@ -8,26 +10,27 @@ module KeyTree
8
10
  yaml: 'YAML', yml: 'YAML'
9
11
  }.freeze
10
12
 
11
- def self.[](type)
12
- type = type.to_sym if type.respond_to?(:to_sym)
13
- loaders[type] || @fallback
14
- end
13
+ class << self
14
+ def [](type)
15
+ type = type.to_sym if type.respond_to?(:to_sym)
16
+ loaders[type] || @fallback
17
+ end
15
18
 
16
- def self.[]=(type, loader_class)
17
- type = type.to_sym if type.respond_to?(:to_sym)
18
- loaders[type] = loader_class
19
- end
19
+ def []=(type, loader_class)
20
+ type = type.to_sym if type.respond_to?(:to_sym)
21
+ loaders[type] = loader_class
22
+ end
20
23
 
21
- def self.fallback(loader)
22
- @fallback = loader
23
- end
24
+ attr_writer :fallback, :loaders
25
+ alias fallback fallback=
24
26
 
25
- private_class_method
27
+ private
26
28
 
27
- def self.loaders
28
- @loaders ||= BUILTIN_LOADERS.each_with_object({}) do |pair, result|
29
- type, name = pair
30
- result[type] = const_get(name) if const_defined?(name)
29
+ def loaders
30
+ @loaders ||= BUILTIN_LOADERS.each_with_object({}) do |pair, result|
31
+ type, name = pair
32
+ result[type] = const_get(name) if const_defined?(name)
33
+ end
31
34
  end
32
35
  end
33
36
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module KeyTree
2
4
  module Loader
3
5
  # KeyTree loader that ignores payload and produces an empty tree
@@ -1,4 +1,4 @@
1
- require 'key_tree/tree'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module KeyTree
4
4
  #
@@ -20,3 +20,5 @@ module KeyTree
20
20
  end
21
21
  end
22
22
  end
23
+
24
+ require_relative 'tree'
data/lib/key_tree/path.rb CHANGED
@@ -1,4 +1,10 @@
1
- module KeyTree
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.[](*keys_or_paths)
12
- keys_or_paths.reduce(Path.new) do |result, key_or_path|
13
- result << Path.new(key_or_path)
17
+ def self.[](*key_paths)
18
+ key_paths.reduce(Path.new) do |result, key_path|
19
+ result << Path.new(key_path)
14
20
  end
15
21
  end
16
22
 
@@ -22,21 +28,16 @@ module KeyTree
22
28
  #
23
29
  # Example:
24
30
  # KeyTree::Path.new("a.b.c")
25
- # => ["a", "b", "c"]
31
+ # => [:a, :b, :c]
26
32
  #
27
- def initialize(key_or_path = [])
28
- case key_or_path
29
- when String
30
- initialize(key_or_path.split('.'))
31
- when Symbol
32
- initialize(key_or_path.to_s)
33
- when Array
34
- key_or_path.each { |key| append(key.to_sym) }
35
- else
36
- raise ArgumentError, 'key path must be String, Symbol or Array of those'
37
- end
33
+ def initialize(key_path = [])
34
+ key_path = key_path.to_key_path unless key_path.is_a? Array
35
+
36
+ super(key_path.map(&:to_sym))
38
37
  end
39
38
 
39
+ alias to_key_path itself
40
+
40
41
  def to_s
41
42
  join('.')
42
43
  end
@@ -46,45 +47,36 @@ module KeyTree
46
47
  end
47
48
 
48
49
  def <<(other)
49
- case other
50
- when Path
51
- other.reduce(self) do |result, key|
52
- result.append(key)
53
- end
54
- else
55
- self << Path[other]
56
- end
50
+ concat(other.to_key_path)
57
51
  end
58
52
 
59
53
  def +(other)
60
- dup << other
54
+ dup.concat(other.to_key_path)
61
55
  end
62
56
 
63
- # drop(+prefix+)
57
+ # Returns a key path without the leading +prefix+
64
58
  #
65
- # Returns a key path without the leading prefix
66
- #
67
- # drop(+n+)
68
- #
69
- # Returns a key path without the first n elements
70
- #
71
- def drop(prefix)
72
- case prefix
73
- when Path
74
- return self unless prefix?(other)
75
- drop(other.length)
76
- else
77
- super(prefix)
78
- end
59
+ # :call-seq:
60
+ # drop(other) => Path
61
+ def drop(other)
62
+ other = other.to_key_path
63
+ raise KeyError unless prefix?(other)
64
+
65
+ super(other.length)
79
66
  end
80
67
 
81
68
  # Is +other+ a prefix?
82
69
  #
70
+ # :call-seq:
71
+ # prefix?(other) => boolean
83
72
  def prefix?(other)
73
+ other = other.to_key_path
84
74
  return false if other.length > length
75
+
85
76
  key_enum = each
86
77
  other.all? { |other_key| key_enum.next == other_key }
87
78
  end
79
+ alias === prefix?
88
80
 
89
81
  # Would +other+ conflict?
90
82
  #
@@ -0,0 +1,157 @@
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
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, *default)
26
+ catch do |ball|
27
+ result = key_path.reduce(self) do |hash, key|
28
+ throw ball unless hash.is_a?(Hash)
29
+ hash.fetch(key) { throw ball }
30
+ end
31
+ return result unless result.is_a?(Hash)
32
+ end
33
+ return yield(key_path) if block_given?
34
+ return default.first unless default.empty?
35
+
36
+ raise KeyError, %(key path invalid: "#{key_path}")
37
+ end
38
+
39
+ # Store a new value in a nested hash structure, expanding it
40
+ # if necessary.
41
+ #
42
+ # :call-seq:
43
+ # deep_store(key_path, new_value) => new_value
44
+ #
45
+ # Raises KeyError if a prefix of the +key_path+ has a value.
46
+ def deep_store(key_path, new_value)
47
+ *prefix_path, last_key = key_path
48
+ result = prefix_path.reduce(self) do |hash, key|
49
+ result = hash.fetch(key) { hash[key] = {} }
50
+ next result if result.is_a?(Hash)
51
+
52
+ raise KeyError, %(prefix has value: "#{key_path}")
53
+ end
54
+ result[last_key] = new_value
55
+ end
56
+
57
+ # Delete a leaf value in a nested hash structure
58
+ #
59
+ # :call-seq:
60
+ # deep_delete(key_path)
61
+ #
62
+ # Raises KeyError if a prefix of the +key_path+ has a value.
63
+ def deep_delete(key_path)
64
+ *prefix_path, last_key = key_path
65
+ result = prefix_path.reduce(self) do |hash, key|
66
+ result = hash.fetch(key, nil)
67
+ next result if result.is_a?(Hash)
68
+
69
+ raise KeyError, %(prefix has value: "#{key_path}")
70
+ end
71
+ result.delete(last_key)
72
+ end
73
+
74
+ # Deeply merge nested hash structures
75
+ #
76
+ # :call-seq:
77
+ # deep_merge!(other) => self
78
+ # deep_merge!(other) { |key_path, lhs, rhs| } => self
79
+ def deep_merge!(other, prefix = [], &block)
80
+ merge!(other) do |key, lhs, rhs|
81
+ key_path = prefix + [key]
82
+ both_are_hashes = lhs.is_a?(Hash) && rhs.is_a?(Hash)
83
+ next lhs.deep_merge!(rhs, key_path, &block) if both_are_hashes
84
+ next yield(key_path, lhs, rhs) unless block.nil?
85
+
86
+ rhs
87
+ end
88
+ end
89
+
90
+ # Deeply merge nested hash structures
91
+ #
92
+ # :call-seq:
93
+ # deep_merge(other) => self
94
+ # deep_merge(other) { |key_path, lhs, rhs| } => self
95
+ def deep_merge(other, prefix = [], &block)
96
+ merge(other) do |key, lhs, rhs|
97
+ key_path = prefix + [key]
98
+ both_are_hashes = lhs.is_a?(Hash) && rhs.is_a?(Hash)
99
+ next lhs.deep_merge(rhs, key_path, &block) if both_are_hashes
100
+ next yield(key_path, lhs, rhs) unless block.nil?
101
+
102
+ rhs
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
+
115
+ value.deep_transform_keys(&block)
116
+ end
117
+ end
118
+
119
+ # Transform keys in a nested hash structure
120
+ #
121
+ # :call-seq:
122
+ # deep_transform_keys! { |key| block }
123
+ def deep_transform_keys!(&block)
124
+ result = transform_keys!(&block)
125
+ result.transform_values! do |value|
126
+ next value unless value.is_a?(Hash)
127
+
128
+ value.deep_transform_keys!(&block)
129
+ end
130
+ end
131
+
132
+ # Comvert any keys containing a +.+ in a hash structure
133
+ # to nested hashes.
134
+ #
135
+ # :call-seq:
136
+ # deep_key_pathify => Hash
137
+ def deep_key_pathify
138
+ each_with_object({}) do |(key, value), result|
139
+ key_path = Path[key]
140
+ value = value.deep_key_pathify if value.is_a?(Hash)
141
+ result.deep_store(key_path, value)
142
+ end
143
+ end
144
+
145
+ def deep_enumerator(yielder, prefix = [])
146
+ each do |key, value|
147
+ key_path = prefix + [key]
148
+ yielder << [key_path, value]
149
+ value.deep_enumerator(yielder, key_path) if value.is_a?(Hash)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ require_relative '../path'