key_tree 0.5.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'