bblib 0.3.0 → 0.4.1
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 +5 -5
- data/.gitignore +11 -10
- data/.rspec +2 -2
- data/.travis.yml +4 -4
- data/CODE_OF_CONDUCT.md +13 -13
- data/Gemfile +4 -4
- data/LICENSE.txt +21 -21
- data/README.md +247 -757
- data/Rakefile +6 -6
- data/bblib.gemspec +34 -34
- data/bin/console +14 -14
- data/bin/setup +7 -7
- data/lib/array/bbarray.rb +71 -29
- data/lib/bblib.rb +12 -12
- data/lib/bblib/version.rb +3 -3
- data/lib/class/effortless.rb +23 -0
- data/lib/error/abstract.rb +3 -0
- data/lib/file/bbfile.rb +93 -52
- data/lib/hash/bbhash.rb +130 -46
- data/lib/hash/hash_struct.rb +24 -0
- data/lib/hash/tree_hash.rb +364 -0
- data/lib/hash_path/hash_path.rb +210 -0
- data/lib/hash_path/part.rb +83 -0
- data/lib/hash_path/path_hash.rb +84 -0
- data/lib/hash_path/proc.rb +93 -0
- data/lib/hash_path/processors.rb +239 -0
- data/lib/html/bbhtml.rb +2 -0
- data/lib/html/builder.rb +34 -0
- data/lib/html/tag.rb +49 -0
- data/lib/logging/bblogging.rb +42 -0
- data/lib/mixins/attrs.rb +422 -0
- data/lib/mixins/bbmixins.rb +7 -0
- data/lib/mixins/bridge.rb +17 -0
- data/lib/mixins/family_tree.rb +41 -0
- data/lib/mixins/hooks.rb +139 -0
- data/lib/mixins/logger.rb +31 -0
- data/lib/mixins/serializer.rb +71 -0
- data/lib/mixins/simple_init.rb +160 -0
- data/lib/number/bbnumber.rb +15 -7
- data/lib/object/bbobject.rb +46 -19
- data/lib/opal/bbopal.rb +0 -4
- data/lib/os/bbos.rb +24 -16
- data/lib/os/bbsys.rb +60 -43
- data/lib/string/bbstring.rb +165 -66
- data/lib/string/cases.rb +37 -29
- data/lib/string/fuzzy_matcher.rb +48 -50
- data/lib/string/matching.rb +43 -30
- data/lib/string/pluralization.rb +156 -0
- data/lib/string/regexp.rb +45 -0
- data/lib/string/roman.rb +17 -30
- data/lib/system/bbsystem.rb +42 -0
- data/lib/time/bbtime.rb +79 -58
- data/lib/time/cron.rb +174 -132
- data/lib/time/task_timer.rb +86 -70
- metadata +27 -10
- data/lib/gem/bbgem.rb +0 -28
- data/lib/hash/hash_path.rb +0 -344
- data/lib/hash/hash_path_proc.rb +0 -256
- data/lib/hash/path_hash.rb +0 -81
- data/lib/object/attr.rb +0 -182
- data/lib/object/hooks.rb +0 -69
- data/lib/object/lazy_class.rb +0 -73
data/lib/hash/bbhash.rb
CHANGED
@@ -1,73 +1,157 @@
|
|
1
|
-
require_relative 'hash_path'
|
2
|
-
require_relative 'path_hash'
|
3
1
|
|
4
|
-
|
2
|
+
require_relative 'tree_hash'
|
3
|
+
require_relative 'hash_struct'
|
5
4
|
|
5
|
+
class Hash
|
6
6
|
# Merges with another hash but also merges all nested hashes and arrays/values.
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
def deep_merge! with, merge_arrays: true, overwrite: true
|
24
|
-
replace self.deep_merge(with, merge_arrays: merge_arrays, overwrite: overwrite)
|
7
|
+
def deep_merge(with, merge_arrays: true, overwrite: true, uniq: false)
|
8
|
+
merger = proc do |_k, v1, v2|
|
9
|
+
if BBLib.are_all?(Hash, v1, v2)
|
10
|
+
v1.merge(v2, &merger)
|
11
|
+
elsif merge_arrays && BBLib.are_all?(Array, v1, v2)
|
12
|
+
uniq ? (v1 + v2).uniq : v1 + v2
|
13
|
+
else
|
14
|
+
overwrite || v1 == v2 ? v2 : (uniq ? [v1, v2].flatten.uniq : [v1, v2].flatten)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
merge(with, &merger)
|
18
|
+
end
|
19
|
+
|
20
|
+
# In place version of deep_merge
|
21
|
+
def deep_merge!(*args)
|
22
|
+
replace deep_merge(*args)
|
25
23
|
end
|
26
24
|
|
27
25
|
# Converts the keys of the hash as well as any nested hashes to symbols.
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
def keys_to_sym(clean: false, recursive: true)
|
27
|
+
each_with_object({}) do |(k, v), memo|
|
28
|
+
key = clean ? k.to_s.to_clean_sym : k.to_s.to_sym
|
29
|
+
memo[key] = recursive && v.respond_to?(:keys_to_sym) ? v.keys_to_sym(clean: clean) : v
|
30
|
+
end
|
31
31
|
end
|
32
32
|
|
33
|
-
|
34
|
-
|
33
|
+
# In place version of keys_to_sym
|
34
|
+
def keys_to_sym!(clean: false, recursive: true)
|
35
|
+
replace(keys_to_sym(clean: clean, recursive: recursive))
|
35
36
|
end
|
36
37
|
|
37
38
|
# Converts the keys of the hash as well as any nested hashes to strings.
|
38
|
-
def keys_to_s
|
39
|
-
|
39
|
+
def keys_to_s(recursive: true)
|
40
|
+
each_with_object({}) do |(k, v), memo|
|
41
|
+
memo[k.to_s] = recursive && v.respond_to?(:keys_to_sym) ? v.keys_to_s : v
|
42
|
+
end
|
40
43
|
end
|
41
44
|
|
42
|
-
|
43
|
-
|
45
|
+
# In place version of keys_to_s
|
46
|
+
def keys_to_s!(recursive: true)
|
47
|
+
replace(keys_to_s(recursive: recursive))
|
44
48
|
end
|
45
49
|
|
46
50
|
# Reverses the order of keys in the Hash
|
47
51
|
def reverse
|
48
|
-
|
52
|
+
to_a.reverse.to_h
|
49
53
|
end
|
50
54
|
|
55
|
+
# In place version of reverse
|
51
56
|
def reverse!
|
52
|
-
replace
|
57
|
+
replace(reverse)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Like unshift for Arrays. Adds a key to the beginning of a Hash rather than the end.
|
61
|
+
def unshift(hash, value = nil)
|
62
|
+
hash = { hash => value } unless hash.is_a?(Hash)
|
63
|
+
replace(hash.merge(self).merge(hash))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Displays all of the differences between this hash and another.
|
67
|
+
# Checks both key and value pairs.
|
68
|
+
def diff(hash)
|
69
|
+
to_a.diff(hash.to_a).to_h
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns all matching values with a specific key (or keys) recursively within a Hash (including nested Arrays)
|
73
|
+
def dive(*keys)
|
74
|
+
matches = []
|
75
|
+
each do |k, v|
|
76
|
+
matches << v if keys.any? { |key| (key.is_a?(Regexp) ? key =~ k : key == k) }
|
77
|
+
matches += v.dive(*keys) if v.respond_to?(:dive)
|
78
|
+
end
|
79
|
+
matches
|
80
|
+
end
|
81
|
+
|
82
|
+
# Navigate a hash using a dot delimited path.
|
83
|
+
def path_nav(obj, path = '', delimiter = '.', &block)
|
84
|
+
case obj
|
85
|
+
when Hash
|
86
|
+
obj.each do |k, v|
|
87
|
+
path_nav(
|
88
|
+
v,
|
89
|
+
(path ? [path, k.to_s.gsub(delimiter, "\\#{delimiter}")].join(delimiter) : k.to_s.gsub(delimiter, "\\#{delimiter}")).to_s,
|
90
|
+
delimiter,
|
91
|
+
&block
|
92
|
+
)
|
93
|
+
end
|
94
|
+
when Array
|
95
|
+
obj.each_with_index do |ob, index|
|
96
|
+
path_nav(
|
97
|
+
ob,
|
98
|
+
(path ? [path, "[#{index}]"].join(delimiter) : "[#{index}]").to_s,
|
99
|
+
delimiter,
|
100
|
+
&block
|
101
|
+
)
|
102
|
+
end
|
103
|
+
else
|
104
|
+
yield path, obj
|
105
|
+
end
|
53
106
|
end
|
54
107
|
|
55
|
-
|
56
|
-
|
57
|
-
|
108
|
+
# Turns nested values' keys into delimiter separated paths
|
109
|
+
def squish(delimiter: '.')
|
110
|
+
sh = {}
|
111
|
+
path_nav(dup, nil, delimiter) { |k, v| sh[k] = v }
|
112
|
+
sh
|
58
113
|
end
|
59
114
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
"#{value}\n" +
|
68
|
-
"\t" * level +
|
69
|
-
(array ? '' : "</#{k}>\n")
|
70
|
-
end.join.split("\n").reject{ |r| r.strip == '' }.join("\n")
|
115
|
+
# Expands keys in a hash using a delimiter. Opposite of squish.
|
116
|
+
def expand
|
117
|
+
{}.to_tree_hash.tap do |hash|
|
118
|
+
each do |k, v|
|
119
|
+
hash.bridge(k => v)
|
120
|
+
end
|
121
|
+
end.value
|
71
122
|
end
|
72
123
|
|
124
|
+
# Returns a version of the hash not including the specified keys
|
125
|
+
def only(*args)
|
126
|
+
select { |k, _v| args.include?(k) }
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns a version of the hash with the specified keys removed.
|
130
|
+
def except(*args)
|
131
|
+
reject { |k, _v| args.include?(k) }
|
132
|
+
end
|
133
|
+
|
134
|
+
# Convert this hash into a TreeHash object.
|
135
|
+
def to_tree_hash
|
136
|
+
TreeHash.new(self)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Run a map iterator over the values in the hash without changing the keys.
|
140
|
+
def vmap
|
141
|
+
return map unless block_given?
|
142
|
+
map { |k, v| [k, yield(v)] }.to_h
|
143
|
+
end
|
144
|
+
|
145
|
+
# Run a map iterator over the keys in the hash without changing the values.
|
146
|
+
def kmap
|
147
|
+
return map unless block_given?
|
148
|
+
map { |k, v| [yield(k), v] }.to_h
|
149
|
+
end
|
150
|
+
|
151
|
+
# Map for hash that automatically converts the yield block to a hash.
|
152
|
+
# Each yield must produce an array with exactly two elements.
|
153
|
+
def hmap
|
154
|
+
return map unless block_given?
|
155
|
+
map { |k, v| yield(k, v) }.to_h
|
156
|
+
end
|
73
157
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Similar to Ruby's OpenStruct but uses hash as its parent class providing
|
2
|
+
# all of the typical Hash methods. Useful for storing settings on objects.
|
3
|
+
module BBLib
|
4
|
+
class HashStruct < Hash
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
def method_missing(method, *args, &block)
|
9
|
+
if args.empty?
|
10
|
+
define_singleton_method(method) do
|
11
|
+
self[method]
|
12
|
+
end
|
13
|
+
self[method]
|
14
|
+
elsif method.to_s.end_with?('=')
|
15
|
+
define_singleton_method(method) do |arg|
|
16
|
+
self[method[0..-2].to_sym] = arg
|
17
|
+
end
|
18
|
+
self[method[0..-2].to_sym] = args.first
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,364 @@
|
|
1
|
+
class TreeHash
|
2
|
+
attr_reader :node_class, :parent, :children
|
3
|
+
|
4
|
+
def initialize(object = {}, parent = nil)
|
5
|
+
@children = {}
|
6
|
+
@parent = parent
|
7
|
+
object = object.value if object.is_a?(TreeHash)
|
8
|
+
replace_with(object)
|
9
|
+
end
|
10
|
+
|
11
|
+
def replace_with(object)
|
12
|
+
@node_class = object.class
|
13
|
+
build_from(object)
|
14
|
+
end
|
15
|
+
|
16
|
+
def root?
|
17
|
+
@parent.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
def child?
|
21
|
+
!root?
|
22
|
+
end
|
23
|
+
|
24
|
+
def child(key, symbol_sensitive = false)
|
25
|
+
case [node_class]
|
26
|
+
when [Hash]
|
27
|
+
children[key] || (symbol_sensitive ? nil : children[key.to_s.to_sym])
|
28
|
+
when [Array]
|
29
|
+
children[key.to_i]
|
30
|
+
else
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def child_exists?(key, symbol_sensitive = false)
|
36
|
+
!case [node_class]
|
37
|
+
when [Hash]
|
38
|
+
children[key] || (!symbol_sensitive && children[key.to_s.to_sym])
|
39
|
+
when [Array]
|
40
|
+
[0...children.size] === key.to_i
|
41
|
+
else
|
42
|
+
false
|
43
|
+
end.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
def children?
|
47
|
+
(node_class == Hash || node_class == Array) && !children.empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
def descendants
|
51
|
+
return [] unless children?
|
52
|
+
desc = []
|
53
|
+
children.each do |key, child|
|
54
|
+
desc << child
|
55
|
+
desc += child.descendants if child.children?
|
56
|
+
end
|
57
|
+
desc
|
58
|
+
end
|
59
|
+
|
60
|
+
def [](key)
|
61
|
+
children[key]
|
62
|
+
end
|
63
|
+
|
64
|
+
def []=(key, value)
|
65
|
+
add_child(key, value)
|
66
|
+
end
|
67
|
+
|
68
|
+
def find(path)
|
69
|
+
path = HashPath.new(*path) unless path.is_a?(HashPath)
|
70
|
+
matches = [self]
|
71
|
+
path.parts.each do |part|
|
72
|
+
break if matches.empty?
|
73
|
+
matches = matches.flat_map do |match|
|
74
|
+
part.matches(match)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
matches
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_multi(*paths)
|
81
|
+
paths.map do |path|
|
82
|
+
find(path)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def find_join(*paths)
|
87
|
+
results = find_multi(*paths)
|
88
|
+
(0..(results.max_by(&:size).size - 1)).map do |index|
|
89
|
+
results.map do |result|
|
90
|
+
result[index]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def find_join_hash(key_path, val_path)
|
96
|
+
find_join(key_path, val_path).to_h
|
97
|
+
end
|
98
|
+
|
99
|
+
def set(paths)
|
100
|
+
paths.each do |path, value|
|
101
|
+
find(path).each { |child| child.replace_with(value) }
|
102
|
+
end
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
# Generate a path using a dot (.) delimited path
|
107
|
+
# e.g. bridge('cats.jackson' => :my_value)
|
108
|
+
# This modifies the underlying container in place
|
109
|
+
def bridge(paths)
|
110
|
+
paths = paths.map { |a| [a, nil] }.to_h if paths.is_a?(Array)
|
111
|
+
paths.each do |path, value|
|
112
|
+
parts = path.to_s.split(/(?<=[^\\])\./)
|
113
|
+
node = self
|
114
|
+
next_part = false
|
115
|
+
until next_part.nil?
|
116
|
+
part = next_part || process_bridge_part(parts.shift)
|
117
|
+
next_part = process_bridge_part(parts.shift)
|
118
|
+
if node.child_exists?(part)
|
119
|
+
if next_part.is_a?(Integer)
|
120
|
+
next_next = process_bridge_part(parts.first)
|
121
|
+
if next_next.is_a?(Integer)
|
122
|
+
node[part][next_part] = [] unless node[part][next_part] && node[part][next_part].node_class == Array
|
123
|
+
else
|
124
|
+
node[part][next_part] = {} unless node[part][next_part] && node[part][next_part].node_class == Hash
|
125
|
+
end
|
126
|
+
end
|
127
|
+
next_part.nil? ? node[part] = value : node = node.child(part)
|
128
|
+
else
|
129
|
+
if next_part.nil?
|
130
|
+
node[part] = value
|
131
|
+
else
|
132
|
+
node[part] = next_part.is_a?(Integer) ? Array.new(next_part) : {}
|
133
|
+
end
|
134
|
+
node[part][next_part] = process_bridge_part(parts.first).is_a?(Integer) ? [] : {} if next_part.is_a?(Integer)
|
135
|
+
node = node[part]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
def copy(paths)
|
143
|
+
paths.each do |from, to|
|
144
|
+
value = find(from).first
|
145
|
+
bridge(to => value)
|
146
|
+
end
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
def copy_all(paths)
|
151
|
+
paths.each do |from, to|
|
152
|
+
value = find(from)
|
153
|
+
bridge(to => value)
|
154
|
+
end
|
155
|
+
self
|
156
|
+
end
|
157
|
+
|
158
|
+
def move(paths)
|
159
|
+
paths.each do |from, to|
|
160
|
+
values = find(from)
|
161
|
+
next if values.empty?
|
162
|
+
value = values.first
|
163
|
+
bridge(to => value)
|
164
|
+
value.kill if value
|
165
|
+
end
|
166
|
+
self
|
167
|
+
end
|
168
|
+
|
169
|
+
def move_all(paths)
|
170
|
+
paths.each do |from, to|
|
171
|
+
value = find(from)
|
172
|
+
bridge(to => value)
|
173
|
+
value.map(&:kill)
|
174
|
+
end
|
175
|
+
self
|
176
|
+
end
|
177
|
+
|
178
|
+
def copy_to(hash, *paths)
|
179
|
+
hash = TreeHash.new(hash) unless hash.is_a?(TreeHash)
|
180
|
+
paths.each do |path|
|
181
|
+
hash.bridge(path => find(path).first)
|
182
|
+
end
|
183
|
+
hash
|
184
|
+
end
|
185
|
+
|
186
|
+
def move_to(hash, *paths)
|
187
|
+
hash = TreeHash.new(hash) unless hash.is_a?(TreeHash)
|
188
|
+
paths.each do |path|
|
189
|
+
value = find(path).first
|
190
|
+
hash.bridge(path => value)
|
191
|
+
value.kill if value
|
192
|
+
end
|
193
|
+
hash
|
194
|
+
end
|
195
|
+
|
196
|
+
def to_tree_hash
|
197
|
+
self
|
198
|
+
end
|
199
|
+
|
200
|
+
def process(processor)
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
def size
|
205
|
+
@children.respond_to?(:size) ? @children.size : 1
|
206
|
+
end
|
207
|
+
|
208
|
+
def paths
|
209
|
+
case [node_class]
|
210
|
+
when [Array], [Hash]
|
211
|
+
value.squish.keys
|
212
|
+
else
|
213
|
+
[]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def ancestors
|
218
|
+
return [] if root?
|
219
|
+
return [parent] if parent.root?
|
220
|
+
parent.ancestors + [parent]
|
221
|
+
end
|
222
|
+
|
223
|
+
def absolute_path
|
224
|
+
anc = ancestors[1..-1]
|
225
|
+
(anc.nil? ? [] : anc.map(&:path) + [path]).join('.')
|
226
|
+
end
|
227
|
+
|
228
|
+
def inspect
|
229
|
+
value
|
230
|
+
end
|
231
|
+
|
232
|
+
def to_s
|
233
|
+
value.to_s
|
234
|
+
end
|
235
|
+
|
236
|
+
def siblings
|
237
|
+
parent.children.values.map { |c| c == self ? nil : c }.compact
|
238
|
+
end
|
239
|
+
|
240
|
+
def index
|
241
|
+
parent.children.values.index(self)
|
242
|
+
end
|
243
|
+
|
244
|
+
def delete(*paths)
|
245
|
+
paths.flat_map do |path|
|
246
|
+
find(path).map do |child|
|
247
|
+
if child.root?
|
248
|
+
delete_child(path)
|
249
|
+
else
|
250
|
+
child.parent.delete_child(child.key)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def kill
|
257
|
+
root.delete(absolute_path)
|
258
|
+
end
|
259
|
+
|
260
|
+
def value
|
261
|
+
case [node_class]
|
262
|
+
when [Hash]
|
263
|
+
children.map { |k, v| [k, v.value] }.to_h
|
264
|
+
when [Array]
|
265
|
+
children.values.map(&:value)
|
266
|
+
else
|
267
|
+
children
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def key
|
272
|
+
return nil if root?
|
273
|
+
case [parent.node_class]
|
274
|
+
when [Hash]
|
275
|
+
parent.keys[index]
|
276
|
+
when [Array]
|
277
|
+
index
|
278
|
+
else
|
279
|
+
nil
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def path
|
284
|
+
parent.node_class == Array ? "[#{key}]" : key.to_s.gsub('.', '\\.')
|
285
|
+
end
|
286
|
+
|
287
|
+
def absolute_paths
|
288
|
+
root.paths
|
289
|
+
end
|
290
|
+
|
291
|
+
def leaf_children
|
292
|
+
return self unless children?
|
293
|
+
children.map do |k, v|
|
294
|
+
v.children? ? v.leaf_children : v
|
295
|
+
end.flatten
|
296
|
+
end
|
297
|
+
|
298
|
+
def keys
|
299
|
+
return [] unless @children.respond_to?(:keys)
|
300
|
+
@children.keys
|
301
|
+
end
|
302
|
+
|
303
|
+
def following_siblings(limit = 0)
|
304
|
+
siblings[(index + 1)..-(limit.to_i + 1)]
|
305
|
+
end
|
306
|
+
|
307
|
+
def preceeding_siblings(limit = 0)
|
308
|
+
limit = limit.to_i
|
309
|
+
siblings[(limit.zero? ? limit : (index - limit))..(index - 1)]
|
310
|
+
end
|
311
|
+
|
312
|
+
def sibling(offset)
|
313
|
+
siblings[index + offset.to_i]
|
314
|
+
end
|
315
|
+
|
316
|
+
def next_sibling
|
317
|
+
sibling(1)
|
318
|
+
end
|
319
|
+
|
320
|
+
def previous_sibling
|
321
|
+
sibling(-1)
|
322
|
+
end
|
323
|
+
|
324
|
+
def root
|
325
|
+
return self if root?
|
326
|
+
ancestors.find(&:root?)
|
327
|
+
end
|
328
|
+
|
329
|
+
def delete_child(key, symbol_sensitive = false)
|
330
|
+
case [node_class]
|
331
|
+
when [Hash]
|
332
|
+
child = symbol_sensitive ? nil : children.delete(key.to_s.to_sym)
|
333
|
+
child = children.delete(key) unless child
|
334
|
+
when [Array]
|
335
|
+
children.delete(key.to_i)
|
336
|
+
else
|
337
|
+
nil
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
protected
|
342
|
+
|
343
|
+
def build_from(object)
|
344
|
+
@children = {}
|
345
|
+
case object
|
346
|
+
when Hash
|
347
|
+
object.each { |k, v| @children[k] = TreeHash.new(v, self) }
|
348
|
+
when Array
|
349
|
+
object.each_with_index { |a, i| @children[i] = TreeHash.new(a, self) }
|
350
|
+
else
|
351
|
+
@children = object
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def add_child(key, child)
|
356
|
+
@children[key] = TreeHash.new(child, self)
|
357
|
+
end
|
358
|
+
|
359
|
+
def process_bridge_part(part)
|
360
|
+
return unless part
|
361
|
+
part =~ /^\[\d+\]$/ ? part.uncapsulate('[').to_i : part.to_sym
|
362
|
+
end
|
363
|
+
|
364
|
+
end
|