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.
@@ -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
@@ -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, overwrite_vals: true
8
- merger = proc{ |k, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.merge(v2, &merger) : (merge_arrays && v1.is_a?(Array) && v2.is_a?(Array) ? (v1 + v2) : (overwrite_vals || v1 == v2 ? v2 : [v1, v2].flatten)) }
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, overwrite_vals: true
13
- replace self.deep_merge(with, merge_arrays: merge_arrays, overwrite_vals: overwrite_vals)
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 === v || Array === v ? v.keys_to_sym(clean:clean) : v); memo}
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 === v || Array === v ? v.keys_to_s : v); memo}
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
- if !hash.is_a? Hash then hash = {hash => value} end
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 === v
55
- value = nested ? v.to_xml(level:level+(array ? 0 : 1), key:k) : v
56
- "\t" * level + (array ? '' : "<#{k}>\n") + (nested ? '' : "\t" * (level+1)) + "#{value}\n" + "\t" * level + (array ? '' : "</#{k}>\n")
57
- end.join
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
@@ -2,433 +2,343 @@ require_relative 'hash_path_proc'
2
2
 
3
3
  module BBLib
4
4
 
5
- def self.hash_path hashes, path, recursive: false, delimiter: '.', symbol_sensitive: false
6
- if String === path then path = BBLib.split_and_analyze(path, delimiter) end
7
- if !hashes.is_a? Array then hashes = [hashes] end
8
- return hashes if path.nil? || path.empty?
9
- if path[0][:key] == '' then return BBLib.hash_path(hashes, path[1..-1], recursive: true, symbol_sensitive:symbol_sensitive) end
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
- matches = BBLib.analyze_hash_path_formula p[:formula], matches
32
- if path.size > 1 && !matches.empty?
33
- BBLib.hash_path(matches.reject{ |r| !(r.is_a?(Hash) || r.is_a?(Array)) }, path[1..-1], symbol_sensitive:symbol_sensitive)
34
- else
35
- return matches
36
- end
37
- end
38
-
39
- def self.hash_path_set hash, *args
40
- details = BBLib.hash_path_setup(hash, args)
41
- count = 0
42
- details[:paths].each do |path, d|
43
- d[:hashes].each do |h|
44
- count+=1
45
- if d[:last][:key].is_a?(Regexp)
46
- exists = h.keys.any?{ |k| k.to_s =~ d[:last][:key] }
47
- else
48
- exists = (details[:symbol_sensitive] ? h.include?(d[:last][:key]) : (h.include?(d[:last][:key].to_sym) || h.include?(d[:last][:key].to_s) ))
49
- end
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
- if h.is_a?(Hash)
57
- h[key] = d[:value]
58
- end
31
+ nil
59
32
  end
60
- end
61
- if count == 0 && details[:bridge] then hash.bridge(path, value:d[:value], symbols:details[:symbols]) end
33
+ end.flatten(1).reject{ |m| m.nil? }
34
+ matches = BBLib.analyze_hash_path_formula(formula, matches)
35
+ recursive = false
62
36
  end
63
- hash
37
+ matches
64
38
  end
65
39
 
66
- def self.hash_path_exists? hash, path, delimiter: '.', symbol_sensitive: false
67
- return !BBLib.hash_path(hash, path, delimiter:delimiter, symbol_sensitive:symbol_sensitive).empty?
40
+ def self.hash_path_keys hash
41
+ hash.squish.keys
68
42
  end
69
43
 
70
- def self.hash_path_move hash, *args
71
- BBLib.hash_path_copy hash, args
72
- details = BBLib.hash_path_setup(hash, args)
73
- opts = Hash.new
74
- details.each do |k, v|
75
- if HASH_PATH_PARAMS.include?(k) then opts[k] = v end
76
- end
77
- BBLib.hash_path_delete hash, [details[:paths].keys, opts ].flatten
78
- return hash
79
- end
80
-
81
- def self.hash_path_move_to from, to, *args
82
- BBLib.hash_path_copy_to from, to, args
83
- BBLib.hash_path_delete from, args
84
- return to
85
- end
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.hash_path_copy_to from, to, *args
107
- details = BBLib.hash_path_setup(from, args)
108
- details[:paths].each do |path, d|
109
- value = from.hash_path(path)
110
- if !value.empty? || !details[:stop_on_nil]
111
- if !details[:arrays].include?(d[:value]) then value = value.first end
112
- to = to.bridge(d[:value], value: value, symbols:details[:symbols])
113
- end
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, *args
119
- args.flatten!
120
- details = BBLib.hash_path_setup hash, [args.find{ |f| Hash === f }.to_h.merge(args.find_all{ |a| String === a }.zip([]).to_h)]
121
- deleted = []
122
- details[:paths].each do |path, d|
123
- d[:hashes].each do |h|
124
- next unless details[:symbol_sensitive] ? h.include?(d[:last][:key]) : (h.include?(d[:last][:key].to_sym) || h.include?(d[:last][:key].to_s) )
125
- if Fixnum === d[:last][:slice]
126
- (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)).delete_at d[:last][:slice]
127
- else
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
- return deleted.flatten
100
+ deleted.flatten.reject{ |v| v.nil? }
138
101
  end
139
102
 
140
- def self.hash_path_keys hash
141
- hash.squish.keys
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.hash_path_key_for hash, value
145
- hash.squish.find_all{ |k,v| v == value }.to_h.keys
146
- end
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
- private
115
+ protected
161
116
 
162
- def self.hash_path_analyze path
163
- return {key: '', slice: (0..-1), formula: nil} if path == '' || path.nil?
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.split_hash_path path, delimiter = '.'
180
- if path.to_s.start_with?(delimiter) then path = path.to_s.sub(delimiter, '') end
181
- paths, stop, open, popen, ropen = [], 0, false, false, false
182
- path.chars.each do |t|
183
- if t == '[' then open = true end
184
- if t == ']' then open = false end
185
- if t == '(' then popen = true end
186
- if t == ')' then popen = false end
187
- if t == '/' then ropen = !ropen end
188
- if t == delimiter && !open && !popen && !ropen then paths << path[0..stop].reverse.sub(delimiter,'').reverse; path = path[stop+1..-1]; stop = -1 end
189
- stop += 1
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
- paths << path
192
- end
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
- temp = []
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
- temp << p
145
+ p
146
+ else
147
+ nil
205
148
  end
206
- rescue StandardError, SyntaxError => se
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.hash_path_setup hash, *args
214
- args.flatten!
215
- return nil unless args && args[0].class == Hash
216
- info = Hash.new
217
- info[:paths] = Hash.new
218
- map = args[0].dup
219
- HASH_PATH_PARAMS.each do |p, h|
220
- info[p] = (map.include?(p) ? map.delete(p) : h[:default])
221
- end
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, delimiter: '.'
250
- BBLib.hash_path self, path, delimiter:delimiter
173
+ def hash_path *path
174
+ BBLib.hash_path self, *path
251
175
  end
252
176
 
253
- def hash_path_set *args
254
- BBLib.hash_path_set self, args
177
+ def hash_path_set *paths
178
+ BBLib.hash_path_set self, *paths
255
179
  end
256
180
 
257
- def hash_path_copy *args
258
- BBLib.hash_path_copy self, args
181
+ def hash_path_copy *paths
182
+ BBLib.hash_path_copy self, *paths
259
183
  end
260
184
 
261
- def hash_path_copy_to hash, *args
262
- BBLib.hash_path_copy_to self, hash, args
185
+ def hash_path_copy_to to, *paths
186
+ BBLib.hash_path_copy_to self, to, *paths
263
187
  end
264
188
 
265
- def hash_path_move_to hash, *args
266
- BBLib.hash_path_move_to self, hash, args
189
+ def hash_path_delete *paths
190
+ BBLib.hash_path_delete self, *paths
267
191
  end
268
192
 
269
- def hash_path_move *args
270
- BBLib.hash_path_move self, args
193
+ def hash_path_move *paths
194
+ BBLib.hash_path_move self, *paths
271
195
  end
272
196
 
273
- def hash_path_delete *args
274
- BBLib.hash_path_delete self, args
197
+ def hash_path_move_to to, *paths
198
+ BBLib.hash_path_move_to self, to, *paths
275
199
  end
276
200
 
277
- def hash_path_keys
201
+ def hash_paths
278
202
  BBLib.hash_path_keys self
279
203
  end
280
204
 
281
- def hash_path_exists? path, delimiter: '.', symbol_sensitive: false
282
- BBLib.hash_path_exists? self, path, delimiter:delimiter, symbol_sensitive:symbol_sensitive
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 dig keys, search_arrays: true
287
- keys = [keys].flatten
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.is_a? Hash
292
- matches+= v.dig(keys)
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: '.', path_start: nil
232
+ def squish delimiter: '.'
302
233
  sh = Hash.new
303
- BBLib.path_nav(self.dup, path_start, delimiter){ |k, v| sh[k] = v }
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 delimiter: '.', symbols: false
239
+ def expand **args
309
240
  eh = Hash.new
310
241
  self.dup.each do |k,v|
311
- eh.bridge k, delimiter: delimiter, value:v, symbols: true
242
+ eh.bridge k, args.merge({value:v})
312
243
  end
313
244
  return eh
314
245
  end
315
246
 
316
- # Builds out a shell of a hash path using a delimited path. Use value to set a value.
317
- def bridge path, delimiter: '.', value: nil, symbols: false
318
- # Ensure the path is a delimiter string. This supports arrays being passed in
319
- path = (path.is_a?(Array) ? path.join(delimiter) : path.to_s)
320
- #Generate the paths and set the current variable
321
- current, paths = self, BBLib.split_and_analyze(path, delimiter)
322
- # If symbols is true, then all keys are turned into symbols
323
- if symbols then paths.each{ |p| p[:key] = p[:key].to_s.to_sym } end
324
- # Check to see if there is only one path. If there is return either an Array of Hash
325
- if paths.size == 1
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
- # If the value does not have a slice, a hash with a single key-value pair is returned
332
- current[paths.first[:key]] = value
333
- return self
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
- if current.is_a?(Hash)
353
- if last
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
- return self
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, delimiter: '.'
391
- BBLib.hash_path self, path, delimiter:delimiter
274
+ def hash_path *path
275
+ BBLib.hash_path self, *path
392
276
  end
393
277
 
394
- def hash_path_set *args
395
- BBLib.hash_path_set self, args
278
+ def hash_path_set *paths
279
+ BBLib.hash_path_set self, *paths
396
280
  end
397
281
 
398
- def hash_path_copy *args
399
- BBLib.hash_path_copy self, args
282
+ def hash_path_copy *paths
283
+ BBLib.hash_path_copy self, *paths
400
284
  end
401
285
 
402
- def hash_path_copy_to hash, *args
403
- BBLib.hash_path_copy_to self, hash, args
286
+ def hash_path_copy_to to, *paths
287
+ BBLib.hash_path_copy_to self, to, *paths
404
288
  end
405
289
 
406
- def hash_path_move_to hash, *args
407
- BBLib.hash_path_move_to self, hash, args
290
+ def hash_path_delete *paths
291
+ BBLib.hash_path_delete self, *paths
408
292
  end
409
293
 
410
- def hash_path_move *args
411
- BBLib.hash_path_move self, args
294
+ def hash_path_move *paths
295
+ BBLib.hash_path_move self, *paths
412
296
  end
413
297
 
414
- def hash_path_delete *args
415
- BBLib.hash_path_delete self, args
298
+ def hash_path_move_to to, *paths
299
+ BBLib.hash_path_move_to self, to, *paths
416
300
  end
417
301
 
418
- def hash_path_keys
302
+ def hash_paths
419
303
  BBLib.hash_path_keys self
420
304
  end
421
305
 
422
- def hash_path_exists? path, delimiter: '.', symbol_sensitive: false
423
- BBLib.hash_path_exists? self, path, delimiter:delimiter, symbol_sensitive:symbol_sensitive
306
+ def hash_path_for value
307
+ BBLib.hash_path_key_for self, value
424
308
  end
425
309
 
426
- def dig keys
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 << i.dig(keys) if i.is_a?(Hash) || i.is_a?(Array)
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