bblib 0.2.2 → 0.3.0

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