bblib 0.3.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +11 -10
  3. data/.rspec +2 -2
  4. data/.travis.yml +4 -4
  5. data/CODE_OF_CONDUCT.md +13 -13
  6. data/Gemfile +4 -4
  7. data/LICENSE.txt +21 -21
  8. data/README.md +247 -757
  9. data/Rakefile +6 -6
  10. data/bblib.gemspec +34 -34
  11. data/bin/console +14 -14
  12. data/bin/setup +7 -7
  13. data/lib/array/bbarray.rb +71 -29
  14. data/lib/bblib.rb +12 -12
  15. data/lib/bblib/version.rb +3 -3
  16. data/lib/class/effortless.rb +23 -0
  17. data/lib/error/abstract.rb +3 -0
  18. data/lib/file/bbfile.rb +93 -52
  19. data/lib/hash/bbhash.rb +130 -46
  20. data/lib/hash/hash_struct.rb +24 -0
  21. data/lib/hash/tree_hash.rb +364 -0
  22. data/lib/hash_path/hash_path.rb +210 -0
  23. data/lib/hash_path/part.rb +83 -0
  24. data/lib/hash_path/path_hash.rb +84 -0
  25. data/lib/hash_path/proc.rb +93 -0
  26. data/lib/hash_path/processors.rb +239 -0
  27. data/lib/html/bbhtml.rb +2 -0
  28. data/lib/html/builder.rb +34 -0
  29. data/lib/html/tag.rb +49 -0
  30. data/lib/logging/bblogging.rb +42 -0
  31. data/lib/mixins/attrs.rb +422 -0
  32. data/lib/mixins/bbmixins.rb +7 -0
  33. data/lib/mixins/bridge.rb +17 -0
  34. data/lib/mixins/family_tree.rb +41 -0
  35. data/lib/mixins/hooks.rb +139 -0
  36. data/lib/mixins/logger.rb +31 -0
  37. data/lib/mixins/serializer.rb +71 -0
  38. data/lib/mixins/simple_init.rb +160 -0
  39. data/lib/number/bbnumber.rb +15 -7
  40. data/lib/object/bbobject.rb +46 -19
  41. data/lib/opal/bbopal.rb +0 -4
  42. data/lib/os/bbos.rb +24 -16
  43. data/lib/os/bbsys.rb +60 -43
  44. data/lib/string/bbstring.rb +165 -66
  45. data/lib/string/cases.rb +37 -29
  46. data/lib/string/fuzzy_matcher.rb +48 -50
  47. data/lib/string/matching.rb +43 -30
  48. data/lib/string/pluralization.rb +156 -0
  49. data/lib/string/regexp.rb +45 -0
  50. data/lib/string/roman.rb +17 -30
  51. data/lib/system/bbsystem.rb +42 -0
  52. data/lib/time/bbtime.rb +79 -58
  53. data/lib/time/cron.rb +174 -132
  54. data/lib/time/task_timer.rb +86 -70
  55. metadata +27 -10
  56. data/lib/gem/bbgem.rb +0 -28
  57. data/lib/hash/hash_path.rb +0 -344
  58. data/lib/hash/hash_path_proc.rb +0 -256
  59. data/lib/hash/path_hash.rb +0 -81
  60. data/lib/object/attr.rb +0 -182
  61. data/lib/object/hooks.rb +0 -69
  62. data/lib/object/lazy_class.rb +0 -73
@@ -1,73 +1,157 @@
1
- require_relative 'hash_path'
2
- require_relative 'path_hash'
3
1
 
4
- class Hash
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
- # Based on method found @ http://stackoverflow.com/questions/9381553/ruby-merge-nested-hash
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
- }
20
- self.merge(with, &merger)
21
- end
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
- # Based on method found @ http://stackoverflow.com/questions/800122/best-way-to-convert-strings-to-symbols-in-hash
29
- def keys_to_sym clean: false
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}
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
- def keys_to_sym! clean: false
34
- replace(self.keys_to_sym clean:clean)
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
- self.inject({}){|memo,(k,v)| memo[k.to_s] = (v.is_a?(Hash) || v.is_a?(Array) ? v.keys_to_s : v); memo}
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
- def keys_to_s!
43
- replace(self.keys_to_s)
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
- self.to_a.reverse.to_h
52
+ to_a.reverse.to_h
49
53
  end
50
54
 
55
+ # In place version of reverse
51
56
  def reverse!
52
- replace self.reverse
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
- def unshift hash, value = nil
56
- hash = {hash => value} if !hash.is_a? Hash
57
- replace hash.merge(self).merge(hash)
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
- def to_xml level: 0, key:nil
61
- map do |k,v|
62
- nested = v.respond_to?(:to_xml)
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")
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