bblib 0.2.2 → 0.3.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/README.md +178 -20
- data/bblib.gemspec +1 -0
- data/lib/array/bbarray.rb +22 -13
- data/lib/bblib.rb +27 -17
- data/lib/bblib/version.rb +1 -1
- data/lib/file/bbfile.rb +20 -31
- data/lib/gem/bbgem.rb +28 -0
- data/lib/hash/bbhash.rb +26 -13
- data/lib/hash/hash_path.rb +214 -304
- data/lib/hash/hash_path_proc.rb +82 -82
- data/lib/hash/path_hash.rb +81 -0
- data/lib/object/attr.rb +182 -0
- data/lib/object/bbobject.rb +16 -0
- data/lib/object/hooks.rb +69 -0
- data/lib/object/lazy_class.rb +73 -0
- data/lib/opal/bbopal.rb +16 -0
- data/lib/os/bbos.rb +93 -0
- data/lib/os/bbsys.rb +238 -0
- data/lib/string/bbstring.rb +36 -6
- data/lib/string/cases.rb +1 -1
- data/lib/string/fuzzy_matcher.rb +14 -19
- data/lib/string/matching.rb +1 -1
- data/lib/string/roman.rb +5 -6
- data/lib/time/cron.rb +27 -27
- data/lib/time/task_timer.rb +34 -28
- metadata +24 -2
data/lib/gem/bbgem.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
if defined? Gem
|
3
|
+
|
4
|
+
module BBLib
|
5
|
+
|
6
|
+
def self.gem_list
|
7
|
+
Gem::Specification.map(&:name).uniq
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.gem_installed? name
|
11
|
+
BBLib.gem_list.include? name
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
# Convenience method that will try to download and install a gem before requiring it
|
17
|
+
# only if the gem is not already installed
|
18
|
+
def require_gem gem, name = nil
|
19
|
+
name = gem if name.nil?
|
20
|
+
if !BBLib.gem_installed? name
|
21
|
+
return false unless Gem.install gem
|
22
|
+
end
|
23
|
+
require name
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
end
|
data/lib/hash/bbhash.rb
CHANGED
@@ -1,23 +1,33 @@
|
|
1
1
|
require_relative 'hash_path'
|
2
|
+
require_relative 'path_hash'
|
2
3
|
|
3
4
|
class Hash
|
4
5
|
|
5
6
|
# Merges with another hash but also merges all nested hashes and arrays/values.
|
6
7
|
# Based on method found @ http://stackoverflow.com/questions/9381553/ruby-merge-nested-hash
|
7
|
-
def deep_merge with, merge_arrays: true,
|
8
|
-
merger = proc{ |k, v1, v2|
|
8
|
+
def deep_merge with, merge_arrays: true, overwrite: true
|
9
|
+
merger = proc{ |k, v1, v2|
|
10
|
+
if v1.is_a?(Hash) && v2.is_a?(Hash)
|
11
|
+
v1.merge(v2, &merger)
|
12
|
+
else
|
13
|
+
if merge_arrays && v1.is_a?(Array) && v2.is_a?(Array)
|
14
|
+
v1 + v2
|
15
|
+
else
|
16
|
+
overwrite || v1 == v2 ? v2 : [v1, v2].flatten
|
17
|
+
end
|
18
|
+
end
|
19
|
+
}
|
9
20
|
self.merge(with, &merger)
|
10
21
|
end
|
11
22
|
|
12
|
-
def deep_merge! with, merge_arrays: true,
|
13
|
-
replace self.deep_merge(with, merge_arrays: merge_arrays,
|
23
|
+
def deep_merge! with, merge_arrays: true, overwrite: true
|
24
|
+
replace self.deep_merge(with, merge_arrays: merge_arrays, overwrite: overwrite)
|
14
25
|
end
|
15
26
|
|
16
27
|
# Converts the keys of the hash as well as any nested hashes to symbols.
|
17
28
|
# Based on method found @ http://stackoverflow.com/questions/800122/best-way-to-convert-strings-to-symbols-in-hash
|
18
29
|
def keys_to_sym clean: false
|
19
|
-
self.inject({}){|memo,(k,v)| memo[clean ? k.to_s.to_clean_sym : k.to_s.to_sym] = (Hash
|
20
|
-
# self.inject({}){|memo,(k,v)| memo[clean ? k.to_s.to_clean_sym : k.to_s.to_sym] = (Hash === v ? v.keys_to_sym : (Array === v ? v.flatten.map{ |a| Hash === a ? a.keys_to_sym : a } : v) ); memo}
|
30
|
+
self.inject({}){|memo,(k,v)| memo[clean ? k.to_s.to_clean_sym : k.to_s.to_sym] = (v.is_a?(Hash) || v.is_a?(Array) ? v.keys_to_sym(clean:clean) : v); memo}
|
21
31
|
end
|
22
32
|
|
23
33
|
def keys_to_sym! clean: false
|
@@ -26,8 +36,7 @@ class Hash
|
|
26
36
|
|
27
37
|
# Converts the keys of the hash as well as any nested hashes to strings.
|
28
38
|
def keys_to_s
|
29
|
-
self.inject({}){|memo,(k,v)| memo[k.to_s] = (Hash
|
30
|
-
# self.inject({}){|memo,(k,v)| memo[k.to_s] = (Hash === v ? v.keys_to_s : (Array === v ? v.flatten.map{ |a| Hash === a ? a.keys_to_s : a } : v)); memo}
|
39
|
+
self.inject({}){|memo,(k,v)| memo[k.to_s] = (v.is_a?(Hash) || v.is_a?(Array) ? v.keys_to_s : v); memo}
|
31
40
|
end
|
32
41
|
|
33
42
|
def keys_to_s!
|
@@ -44,17 +53,21 @@ class Hash
|
|
44
53
|
end
|
45
54
|
|
46
55
|
def unshift hash, value = nil
|
47
|
-
|
56
|
+
hash = {hash => value} if !hash.is_a? Hash
|
48
57
|
replace hash.merge(self).merge(hash)
|
49
58
|
end
|
50
59
|
|
51
60
|
def to_xml level: 0, key:nil
|
52
61
|
map do |k,v|
|
53
62
|
nested = v.respond_to?(:to_xml)
|
54
|
-
array = Array
|
55
|
-
value = nested ? v.to_xml(level:level+(array ? 0 : 1), key:k) : v
|
56
|
-
|
57
|
-
|
63
|
+
array = v.is_a?(Array)
|
64
|
+
value = nested ? v.to_xml(level:level + (array ? 0 : 1), key:k) : v
|
65
|
+
(array ? '' : ("\t" * level + "<#{k}>\n")) +
|
66
|
+
(nested ? '' : "\t" * (level + 1)) +
|
67
|
+
"#{value}\n" +
|
68
|
+
"\t" * level +
|
69
|
+
(array ? '' : "</#{k}>\n")
|
70
|
+
end.join.split("\n").reject{ |r| r.strip == '' }.join("\n")
|
58
71
|
end
|
59
72
|
|
60
73
|
end
|
data/lib/hash/hash_path.rb
CHANGED
@@ -2,433 +2,343 @@ require_relative 'hash_path_proc'
|
|
2
2
|
|
3
3
|
module BBLib
|
4
4
|
|
5
|
-
def self.hash_path
|
6
|
-
if
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
matches, p = Array.new, path.first
|
11
|
-
hashes.each do |hash|
|
12
|
-
if recursive
|
13
|
-
patterns = Regexp === p[:key] ? p[:key] : p[:key].to_s == '*' ? /.*/ : (symbol_sensitive ? p[:key] : [p[:key].to_sym, p[:key].to_s])
|
14
|
-
hash.dig(patterns)[p[:slice]].each{ |va| matches.push va }
|
15
|
-
else
|
16
|
-
if p[:key].nil?
|
17
|
-
if hash.is_a?(Array) then hash[p[:slice]].each{ |h| matches << h } end
|
18
|
-
elsif Symbol === p[:key] || String === p[:key]
|
19
|
-
if p[:key].to_s == '*'
|
20
|
-
hash.values[p[:slice]].each{ |va| matches.push va }
|
21
|
-
else
|
22
|
-
next unless symbol_sensitive ? hash.include?(p[:key]) : (hash.include?(p[:key].to_sym) || hash.include?(p[:key].to_s) )
|
23
|
-
mat = (symbol_sensitive ? hash[p[:key]] : ( if hash.include?(p[:key].to_sym) then hash[p[:key].to_sym] else hash[p[:key].to_s] end ))
|
24
|
-
matches.push mat.is_a?(Array) ? mat[p[:slice]] : mat
|
25
|
-
end
|
26
|
-
elsif Regexp === p[:key]
|
27
|
-
hash.keys.find_all{ |k| k =~ p[:key] }.each{ |m| matches << hash[m] }
|
28
|
-
end
|
29
|
-
end
|
5
|
+
def self.hash_path hash, *paths, multi_path: false, multi_join: false
|
6
|
+
if multi_path || multi_join
|
7
|
+
results = paths.map{ |path| BBLib.hash_path(hash, path)}
|
8
|
+
results = (0..results.max_by{ |m| m.size }.size - 1).map{ |i| results.map{ |r| r[i] } } if multi_join
|
9
|
+
return results
|
30
10
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
next unless details[:bridge] || exists
|
51
|
-
key = details[:symbol_sensitive] || d[:last][:key].is_a?(Regexp) ? d[:last][:key] : (h.include?(d[:last][:key].to_sym) ? d[:last][:key].to_sym : d[:last][:key].to_s)
|
52
|
-
# if details[:symbols] then key = key.to_sym elsif !exists then key = d[:last][:key] end
|
53
|
-
if Fixnum === d[:last][:slice]
|
54
|
-
h[key][d[:last][:slice]] = d[:value]
|
11
|
+
path = split_path(*paths)
|
12
|
+
matches, recursive = [hash], false
|
13
|
+
until path.empty? || matches.empty?
|
14
|
+
current = path.shift.to_s
|
15
|
+
current = current[0..-2] + '.' + path.shift.to_s if current.end_with?("\\")
|
16
|
+
if current.strip == ''
|
17
|
+
recursive = true
|
18
|
+
next
|
19
|
+
end
|
20
|
+
key, formula = BBLib.analyze_hash_path(current)
|
21
|
+
matches = matches.map do |match|
|
22
|
+
if recursive
|
23
|
+
match.dive(key.to_sym, key)
|
24
|
+
elsif key == '*'
|
25
|
+
match.is_a?(Hash) ? match.values : (match.is_a?(Array) ? match : nil)
|
26
|
+
elsif match.is_a?(Hash)
|
27
|
+
key.is_a?(Regexp) ? match.map{ |k,v| k.to_s =~ key ? v : nil } : [(BBLib::in_opal? ? nil : match[key.to_sym]), match[key]]
|
28
|
+
elsif match.is_a?(Array) && (key.is_a?(Fixnum) || key.is_a?(Range))
|
29
|
+
key.is_a?(Range) ? match[key] : [match[key]]
|
55
30
|
else
|
56
|
-
|
57
|
-
h[key] = d[:value]
|
58
|
-
end
|
31
|
+
nil
|
59
32
|
end
|
60
|
-
end
|
61
|
-
|
33
|
+
end.flatten(1).reject{ |m| m.nil? }
|
34
|
+
matches = BBLib.analyze_hash_path_formula(formula, matches)
|
35
|
+
recursive = false
|
62
36
|
end
|
63
|
-
|
37
|
+
matches
|
64
38
|
end
|
65
39
|
|
66
|
-
def self.
|
67
|
-
|
40
|
+
def self.hash_path_keys hash
|
41
|
+
hash.squish.keys
|
68
42
|
end
|
69
43
|
|
70
|
-
def self.
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
def self.hash_path_copy hash, *args
|
88
|
-
details = BBLib.hash_path_setup(hash, args)
|
89
|
-
details[:paths].each do |path, d|
|
90
|
-
d[:hashes].each do |h|
|
91
|
-
if Hash === h || Array === h
|
92
|
-
exist = details[:symbol_sensitive] ? h.include?(d[:last][:key]) : (h.include?(d[:last][:key].to_sym) || h.include?(d[:last][:key].to_s) )
|
93
|
-
next unless exist || details[:bridge]
|
94
|
-
value = details[:symbol_sensitive] ? h[d[:last][:key]] : (if h.include?(d[:last][:key].to_sym) then h[d[:last][:key].to_sym] else h[d[:last][:key].to_s] end )
|
95
|
-
if value
|
96
|
-
BBLib.hash_path_set hash, d[:value] => value, symbols:details[:symbols]
|
97
|
-
end
|
98
|
-
elsif !details[:stop_on_nil]
|
99
|
-
BBLib.hash_path_set hash, d[:value] => nil, symbols:details[:symbols]
|
44
|
+
def self.hash_path_key_for hash, value
|
45
|
+
hash.squish.find_all{ |k,v| value.is_a?(Regexp) ? v =~ value : v == value }.to_h.keys
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.hash_path_set hash, *paths, symbols: true, bridge: true
|
49
|
+
paths = paths.find{ |a| a.is_a?(Hash) }
|
50
|
+
paths.each do |path, value|
|
51
|
+
parts = split_path(path)
|
52
|
+
matches = BBLib.hash_path(hash, *parts[0..-2])
|
53
|
+
matches.each do |match|
|
54
|
+
key, formula = BBLib.analyze_hash_path(parts.last)
|
55
|
+
key = match.include?(key.to_sym) || (symbols && !match.include?(key) ) ? key.to_sym : key
|
56
|
+
if match.is_a?(Hash)
|
57
|
+
match[key] = value
|
58
|
+
elsif match.is_a?(Array) && key.is_a?(Fixnum)
|
59
|
+
match[key] = value
|
100
60
|
end
|
101
61
|
end
|
62
|
+
hash.bridge(path, value:value, symbols:symbols) if matches.empty? && bridge
|
102
63
|
end
|
103
64
|
hash
|
104
65
|
end
|
105
66
|
|
106
|
-
def self.
|
107
|
-
|
108
|
-
|
109
|
-
value =
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
67
|
+
def self.hash_path_copy hash, *paths, symbols: true, array: false, overwrite: true, skip_nil: true
|
68
|
+
paths = paths.find{ |a| a.is_a?(Hash) }
|
69
|
+
paths.each do |from, to|
|
70
|
+
value = BBLib.hash_path(hash, from)
|
71
|
+
value = value.first unless array
|
72
|
+
hash.bridge(to, value: value, symbols:symbols, overwrite: overwrite) unless value.nil? && skip_nil
|
73
|
+
end
|
74
|
+
hash
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.hash_path_copy_to from, to, *paths, symbols: true, array: false, overwrite: true, skip_nil: true
|
78
|
+
paths = paths.find{ |a| a.is_a?(Hash) }
|
79
|
+
paths.each do |p_from, p_to|
|
80
|
+
value = BBLib.hash_path(from, p_from)
|
81
|
+
value = value.first unless array
|
82
|
+
to.bridge(p_to, value:value, symbols:symbols, overwrite: overwrite) unless value.nil? && skip_nil
|
114
83
|
end
|
115
84
|
to
|
116
85
|
end
|
117
86
|
|
118
|
-
def self.hash_path_delete hash, *
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
if details[:symbol_sensitive]
|
129
|
-
deleted << h.delete(d[:last][:key])
|
130
|
-
else
|
131
|
-
if h.include?(d[:last][:key].to_sym) then deleted << h.delete(d[:last][:key].to_sym) end
|
132
|
-
if h.include?(d[:last][:key].to_s) then deleted << h.delete(d[:last][:key].to_s) end
|
133
|
-
end
|
87
|
+
def self.hash_path_delete hash, *paths
|
88
|
+
deleted = Array.new
|
89
|
+
paths.each do |path|
|
90
|
+
parts = split_path(path)
|
91
|
+
BBLib.hash_path(hash, *parts[0..-2]).each do |match|
|
92
|
+
key, formula = BBLib.analyze_hash_path(parts.last)
|
93
|
+
if match.is_a?(Hash)
|
94
|
+
deleted << match.delete(key) << match.delete(key.to_sym)
|
95
|
+
elsif match.is_a?(Array) && key.is_a?(Fixnum)
|
96
|
+
deleted << match.delete_at(key)
|
134
97
|
end
|
135
98
|
end
|
136
99
|
end
|
137
|
-
|
100
|
+
deleted.flatten.reject{ |v| v.nil? }
|
138
101
|
end
|
139
102
|
|
140
|
-
def self.
|
141
|
-
hash
|
103
|
+
def self.hash_path_move hash, *paths
|
104
|
+
BBLib.hash_path_copy hash, *paths
|
105
|
+
BBLib.hash_path_delete hash, *paths.find{|pt| pt.is_a?(Hash) }.keys
|
106
|
+
hash
|
142
107
|
end
|
143
108
|
|
144
|
-
def self.
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
def self.path_nav obj, path = '', delimiter = '.', &block
|
149
|
-
case [obj.class]
|
150
|
-
when [Hash]
|
151
|
-
obj.each{ |k,v| path_nav v, "#{path}#{path.nil? || path.empty? ? nil : delimiter}#{k}", delimiter, &block }
|
152
|
-
when [Array]
|
153
|
-
index = 0
|
154
|
-
obj.each{ |o| path_nav o, "#{path}#{path.end_with?(']') ? delimiter : nil}[#{index}]", delimiter, &block ; index+=1 }
|
155
|
-
else
|
156
|
-
yield path, obj
|
157
|
-
end
|
109
|
+
def self.hash_path_move_to from, to, *paths
|
110
|
+
BBLib.hash_path_copy_to from, to, *paths
|
111
|
+
BBLib.hash_path_delete from, *paths.find{|pt| pt.is_a?(Hash) }.keys
|
112
|
+
to
|
158
113
|
end
|
159
114
|
|
160
|
-
|
115
|
+
protected
|
161
116
|
|
162
|
-
def self.
|
163
|
-
|
164
|
-
key = path.scan(/\A.*^[^\[\(\{]*/i).first.to_s
|
165
|
-
if key.encap_by?('/') || key.start_with?('/') && key.end_with?('i')
|
166
|
-
key = eval(key)
|
167
|
-
elsif key.start_with? ':'
|
168
|
-
key = key[1..-1].to_sym
|
169
|
-
end
|
170
|
-
slice = eval(path.scan(/(?<=\[).*?(?=\])/).first.to_s)
|
171
|
-
no_slice = false
|
172
|
-
formula = path.scan(/(?<=\().*(?=\))/).first
|
173
|
-
if !slice.is_a?(Range) && !slice.is_a?(Fixnum) then slice = (0..-1); no_slice = true end
|
174
|
-
if (key.nil? || key == '') && (!slice.nil? || !no_slice) then key = nil end
|
175
|
-
if slice.nil? then slice = (0..-1) end
|
176
|
-
{key:key, slice:slice, formula:formula}
|
117
|
+
def self.split_path *paths
|
118
|
+
paths.map{|pth| pth.to_s.gsub('..', '. .').scan(/(?:[\(|\[].*?[\)|\]]|[^\.])+/)}.flatten
|
177
119
|
end
|
178
120
|
|
179
|
-
def self.
|
180
|
-
if path
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
if
|
189
|
-
|
121
|
+
def self.analyze_hash_path path
|
122
|
+
return '', nil if path == '' || path.nil?
|
123
|
+
key = path.scan(/^.*^[^\(]*/i).first.to_s
|
124
|
+
if key =~ /^\[\d+\]$/
|
125
|
+
key = key[1..-2].to_i
|
126
|
+
elsif key =~ /\[\-?\d+\.\s?\.{1,2}\-?\d+\]/
|
127
|
+
bounds = key.scan(/\-?\d+/).map{|x| x.to_i}
|
128
|
+
key = key =~ /\.\s?\.{2}/ ? (bounds.first...bounds.last) : (bounds.first..bounds.last)
|
129
|
+
elsif key =~ /\/.*[\/|\/i]$/
|
130
|
+
if key.end_with?('i')
|
131
|
+
key = /#{key[1..-3]}/i
|
132
|
+
else
|
133
|
+
key = /#{key[1..-2]}/
|
134
|
+
end
|
190
135
|
end
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
def self.split_and_analyze path, delimiter = '.'
|
195
|
-
split_hash_path(path, delimiter).collect{ |p| hash_path_analyze(p) }
|
136
|
+
formula = path.scan(/\(.*\)/).first
|
137
|
+
return key, formula
|
196
138
|
end
|
197
139
|
|
198
140
|
def self.analyze_hash_path_formula formula, hashes
|
199
141
|
return hashes unless formula
|
200
|
-
|
201
|
-
hashes.flatten.each do |p|
|
142
|
+
hashes.map do |p|
|
202
143
|
begin
|
203
144
|
if eval(p.is_a?(Hash) ? formula.gsub('$', "(#{p})") : formula.gsub('$', p.to_s))
|
204
|
-
|
145
|
+
p
|
146
|
+
else
|
147
|
+
nil
|
205
148
|
end
|
206
|
-
rescue StandardError,
|
149
|
+
rescue StandardError, Exception => e
|
207
150
|
# Do nothing, the formula failed and we reject the value as a false
|
208
151
|
end
|
209
|
-
end
|
210
|
-
temp
|
152
|
+
end.reject{ |x| x.nil? }
|
211
153
|
end
|
212
154
|
|
213
|
-
def self.
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
if info[:keys_to_sym] then hash.keys_to_sym! end
|
223
|
-
map.each do |path, value|
|
224
|
-
info[:paths][path] = Hash.new
|
225
|
-
info[:paths][path][:value] = value
|
226
|
-
info[:paths][path][:paths] = BBLib.split_hash_path(path, info[:delimiter])
|
227
|
-
info[:paths][path][:last] = BBLib.hash_path_analyze info[:paths][path][:paths].last
|
228
|
-
info[:paths][path][:hashes] = BBLib.hash_path(hash, info[:paths][path][:paths][0..-2].join(info[:delimiter]), delimiter:info[:delimiter], symbol_sensitive:info[:symbol_sensitive] )
|
155
|
+
def self.hash_path_nav obj, path = '', delimiter = '.', &block
|
156
|
+
case [obj.class]
|
157
|
+
when [Hash]
|
158
|
+
obj.each{ |k,v| hash_path_nav(v, "#{path.nil? ? k.to_s : [path, k].join(delimiter)}", delimiter, &block) }
|
159
|
+
when [Array]
|
160
|
+
index = 0
|
161
|
+
obj.each{ |o| hash_path_nav(o, "#{path.nil? ? "[#{index}]" : [path, "[#{index}]" ].join(delimiter)}", delimiter, &block) ; index+=1 }
|
162
|
+
else
|
163
|
+
yield path, obj
|
229
164
|
end
|
230
|
-
return info
|
231
165
|
end
|
232
166
|
|
233
|
-
HASH_PATH_PARAMS = {
|
234
|
-
delimiter: {default:'.'},
|
235
|
-
bridge: {default:true},
|
236
|
-
symbols: {default:true},
|
237
|
-
symbol_sensitive: {default:false},
|
238
|
-
stop_on_nil: {default:true},
|
239
|
-
arrays: {default:[]},
|
240
|
-
keys_to_sym: {default:false}
|
241
|
-
}
|
242
|
-
|
243
167
|
end
|
244
168
|
|
245
169
|
|
246
170
|
|
247
171
|
class Hash
|
248
172
|
|
249
|
-
def hash_path path
|
250
|
-
BBLib.hash_path self, path
|
173
|
+
def hash_path *path
|
174
|
+
BBLib.hash_path self, *path
|
251
175
|
end
|
252
176
|
|
253
|
-
def hash_path_set *
|
254
|
-
BBLib.hash_path_set self,
|
177
|
+
def hash_path_set *paths
|
178
|
+
BBLib.hash_path_set self, *paths
|
255
179
|
end
|
256
180
|
|
257
|
-
def hash_path_copy *
|
258
|
-
BBLib.hash_path_copy self,
|
181
|
+
def hash_path_copy *paths
|
182
|
+
BBLib.hash_path_copy self, *paths
|
259
183
|
end
|
260
184
|
|
261
|
-
def hash_path_copy_to
|
262
|
-
BBLib.hash_path_copy_to self,
|
185
|
+
def hash_path_copy_to to, *paths
|
186
|
+
BBLib.hash_path_copy_to self, to, *paths
|
263
187
|
end
|
264
188
|
|
265
|
-
def
|
266
|
-
BBLib.
|
189
|
+
def hash_path_delete *paths
|
190
|
+
BBLib.hash_path_delete self, *paths
|
267
191
|
end
|
268
192
|
|
269
|
-
def hash_path_move *
|
270
|
-
BBLib.hash_path_move self,
|
193
|
+
def hash_path_move *paths
|
194
|
+
BBLib.hash_path_move self, *paths
|
271
195
|
end
|
272
196
|
|
273
|
-
def
|
274
|
-
BBLib.
|
197
|
+
def hash_path_move_to to, *paths
|
198
|
+
BBLib.hash_path_move_to self, to, *paths
|
275
199
|
end
|
276
200
|
|
277
|
-
def
|
201
|
+
def hash_paths
|
278
202
|
BBLib.hash_path_keys self
|
279
203
|
end
|
280
204
|
|
281
|
-
def
|
282
|
-
BBLib.
|
205
|
+
def hash_path_for value
|
206
|
+
BBLib.hash_path_key_for self, value
|
283
207
|
end
|
284
208
|
|
209
|
+
alias_method :hpath, :hash_path
|
210
|
+
alias_method :hpath_set, :hash_path_set
|
211
|
+
alias_method :hpath_move, :hash_path_move
|
212
|
+
alias_method :hpath_move_to, :hash_path_move_to
|
213
|
+
alias_method :hpath_delete, :hash_path_delete
|
214
|
+
alias_method :hpath_copy, :hash_path_copy
|
215
|
+
alias_method :hpath_copy_to, :hash_path_copy_to
|
216
|
+
alias_method :hpaths, :hash_paths
|
217
|
+
alias_method :hpath_for, :hash_path_for
|
218
|
+
|
285
219
|
# Returns all matching values with a specific key (or Array of keys) recursively within a Hash (including nested Arrays)
|
286
|
-
def
|
287
|
-
|
288
|
-
matches = []
|
220
|
+
def dive *keys
|
221
|
+
matches = Array.new
|
289
222
|
self.each do |k, v|
|
290
223
|
if keys.any?{ |a| (a.is_a?(Regexp) ? a =~ k : a == k ) } then matches << v end
|
291
|
-
if v.
|
292
|
-
matches+= v.
|
293
|
-
elsif v.is_a?(Array) && search_arrays
|
294
|
-
v.flatten.each{ |i| if i.is_a?(Hash) then matches+= i.dig(keys) end }
|
224
|
+
if v.respond_to? :dive
|
225
|
+
matches+= v.dive(*keys)
|
295
226
|
end
|
296
227
|
end
|
297
228
|
matches
|
298
229
|
end
|
299
230
|
|
300
231
|
# Turns nested values' keys into delimiter separated paths
|
301
|
-
def squish delimiter: '.'
|
232
|
+
def squish delimiter: '.'
|
302
233
|
sh = Hash.new
|
303
|
-
BBLib.
|
234
|
+
BBLib.hash_path_nav(self.dup, nil, delimiter){ |k, v| sh[k] = v }
|
304
235
|
sh
|
305
236
|
end
|
306
237
|
|
307
238
|
# Expands keys in a hash using a delimiter. Opposite of squish.
|
308
|
-
def expand
|
239
|
+
def expand **args
|
309
240
|
eh = Hash.new
|
310
241
|
self.dup.each do |k,v|
|
311
|
-
eh.bridge k,
|
242
|
+
eh.bridge k, args.merge({value:v})
|
312
243
|
end
|
313
244
|
return eh
|
314
245
|
end
|
315
246
|
|
316
|
-
#
|
317
|
-
def bridge path, delimiter: '.',
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
# If the first value is a slice the value is inserted into an empty array
|
327
|
-
if Fixnum === paths.first[:slice]
|
328
|
-
current[paths.first[:key]] = ([].insert paths.first[:slice], value )
|
329
|
-
return self
|
247
|
+
# Add a hash path to a hash
|
248
|
+
def bridge *path, value:nil, delimiter: '.', symbols: true, overwrite: false
|
249
|
+
path = path.msplit(delimiter).flatten
|
250
|
+
hash, part, bail, last = self, nil, false, nil
|
251
|
+
while !path.empty? && !bail
|
252
|
+
part = path.shift
|
253
|
+
if part =~ /\A\[\d+\]\z/
|
254
|
+
part = part[1..-2].to_i
|
255
|
+
else
|
256
|
+
part = part.to_sym if symbols
|
330
257
|
end
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
end
|
335
|
-
index, count = -1, 0
|
336
|
-
paths.each do |p|
|
337
|
-
count+=1
|
338
|
-
last = paths.size == count
|
339
|
-
if p[:slice].is_a?(Fixnum)
|
340
|
-
index = p[:slice]
|
341
|
-
if paths[count-2] && Fixnum === paths[count-2][:slice]
|
342
|
-
current[paths[count-2][:slice]] = current[paths[count-2][:slice]] ||= Array.new
|
343
|
-
current = current[paths[count-2][:slice]]
|
344
|
-
else
|
345
|
-
if current[p[:key]].nil?
|
346
|
-
current[p[:key]] = []
|
347
|
-
end
|
348
|
-
current = current[p[:key]]
|
349
|
-
end
|
350
|
-
if last then current[index] = value end
|
258
|
+
if (hash.is_a?(Hash) && hash.include?(part) || hash.is_a?(Array) && hash.size > part.to_i) && !overwrite
|
259
|
+
bail = true if !hash[part].is_a?(Hash) && !hash[part].is_a?(Array)
|
260
|
+
hash = hash[part] unless bail
|
351
261
|
else
|
352
|
-
|
353
|
-
|
354
|
-
current[p[:key]] = value
|
355
|
-
else
|
356
|
-
current[p[:key]] = current[p[:key]] ||= Hash.new
|
357
|
-
end
|
358
|
-
current = current[p[:key]]
|
359
|
-
else
|
360
|
-
if last
|
361
|
-
if current[index]
|
362
|
-
if current[index].is_a? Hash
|
363
|
-
current[index].merge({p[:key] => value})
|
364
|
-
else
|
365
|
-
current[index] = ({p[:key] => value})
|
366
|
-
end
|
367
|
-
else
|
368
|
-
current.insert index, {p[:key] => value}
|
369
|
-
end
|
370
|
-
else
|
371
|
-
temp = { p[:key] => {} }
|
372
|
-
if current[index].is_a? Hash
|
373
|
-
current[index].merge!(temp)
|
374
|
-
else
|
375
|
-
current[index] = temp
|
376
|
-
end
|
377
|
-
end
|
378
|
-
if !last then current = temp[p[:key]] end
|
379
|
-
end
|
380
|
-
index = -1
|
262
|
+
hash[part] = path.first =~ /\A\[\d+\]\z/ ? Array.new : Hash.new
|
263
|
+
hash = hash[part] unless bail || path.empty?
|
381
264
|
end
|
382
265
|
end
|
383
|
-
|
266
|
+
hash[part] = value unless bail
|
267
|
+
self
|
384
268
|
end
|
385
269
|
|
386
270
|
end
|
387
271
|
|
388
272
|
class Array
|
389
273
|
|
390
|
-
def hash_path path
|
391
|
-
BBLib.hash_path self, path
|
274
|
+
def hash_path *path
|
275
|
+
BBLib.hash_path self, *path
|
392
276
|
end
|
393
277
|
|
394
|
-
def hash_path_set *
|
395
|
-
BBLib.hash_path_set self,
|
278
|
+
def hash_path_set *paths
|
279
|
+
BBLib.hash_path_set self, *paths
|
396
280
|
end
|
397
281
|
|
398
|
-
def hash_path_copy *
|
399
|
-
BBLib.hash_path_copy self,
|
282
|
+
def hash_path_copy *paths
|
283
|
+
BBLib.hash_path_copy self, *paths
|
400
284
|
end
|
401
285
|
|
402
|
-
def hash_path_copy_to
|
403
|
-
BBLib.hash_path_copy_to self,
|
286
|
+
def hash_path_copy_to to, *paths
|
287
|
+
BBLib.hash_path_copy_to self, to, *paths
|
404
288
|
end
|
405
289
|
|
406
|
-
def
|
407
|
-
BBLib.
|
290
|
+
def hash_path_delete *paths
|
291
|
+
BBLib.hash_path_delete self, *paths
|
408
292
|
end
|
409
293
|
|
410
|
-
def hash_path_move *
|
411
|
-
BBLib.hash_path_move self,
|
294
|
+
def hash_path_move *paths
|
295
|
+
BBLib.hash_path_move self, *paths
|
412
296
|
end
|
413
297
|
|
414
|
-
def
|
415
|
-
BBLib.
|
298
|
+
def hash_path_move_to to, *paths
|
299
|
+
BBLib.hash_path_move_to self, to, *paths
|
416
300
|
end
|
417
301
|
|
418
|
-
def
|
302
|
+
def hash_paths
|
419
303
|
BBLib.hash_path_keys self
|
420
304
|
end
|
421
305
|
|
422
|
-
def
|
423
|
-
BBLib.
|
306
|
+
def hash_path_for value
|
307
|
+
BBLib.hash_path_key_for self, value
|
424
308
|
end
|
425
309
|
|
426
|
-
|
310
|
+
alias_method :hpath, :hash_path
|
311
|
+
alias_method :hpath_set, :hash_path_set
|
312
|
+
alias_method :hpath_move, :hash_path_move
|
313
|
+
alias_method :hpath_move_to, :hash_path_move_to
|
314
|
+
alias_method :hpath_delete, :hash_path_delete
|
315
|
+
alias_method :hpath_copy, :hash_path_copy
|
316
|
+
alias_method :hpath_copy_to, :hash_path_copy_to
|
317
|
+
alias_method :hpaths, :hash_paths
|
318
|
+
alias_method :hpath_for, :hash_path_for
|
319
|
+
|
320
|
+
def dive *keys
|
427
321
|
matches = []
|
428
322
|
self.each do |i|
|
429
|
-
matches
|
323
|
+
matches+= i.dive(*keys) if i.respond_to?(:dive)
|
430
324
|
end
|
431
325
|
matches
|
432
326
|
end
|
433
327
|
|
328
|
+
# Turns nested values' keys into delimiter separated paths
|
329
|
+
def squish delimiter: '.'
|
330
|
+
sh = Hash.new
|
331
|
+
BBLib.hash_path_nav(self.dup, nil, delimiter){ |k, v| sh[k] = v }
|
332
|
+
sh
|
333
|
+
end
|
334
|
+
|
335
|
+
# Expands keys in a hash using a delimiter. Opposite of squish.
|
336
|
+
def expand **args
|
337
|
+
eh = Hash.new
|
338
|
+
self.dup.each do |k,v|
|
339
|
+
eh.bridge k, args.merge({value:v})
|
340
|
+
end
|
341
|
+
return eh
|
342
|
+
end
|
343
|
+
|
434
344
|
end
|