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,107 +1,123 @@
1
1
  module BBLib
2
-
3
- class TaskTimer < LazyClass
4
- attr_hash :tasks, default: Hash.new
2
+ # Simple timer that can track tasks based on time. Also provides aggregated metrics
3
+ # and history for each task run. Generally useful for benchmarking or logging.
4
+ #
5
+ # @author Brandon Black
6
+ # @attr [Hash] tasks The information on all running tasks and history of all tasks up to the retention.
7
+ # @attr [Integer] retention The number of runs to collect per task before truncation.
8
+ class TaskTimer
9
+ include Effortless
10
+ attr_hash :tasks, default: {}, serialize: false
5
11
  attr_int_between -1, nil, :retention, default: 100
6
12
 
7
- def time task = :default, type = :current
8
- return nil unless @tasks.keys.include? task
9
- numbers = @tasks[task][:history].map{ |v| v[:time] }
13
+ # Returns an aggregated metric for a given type.
14
+ #
15
+ # @param [Symbol] task The key value of the task to retrieve
16
+ # @param [Symbol] type The metric to return.
17
+ # Options are :avg, :min, :max, :first, :last, :sum, :all and :count.
18
+ # @return [Float, Integer, Array] Returns either the aggregation (Numeric) or an Array in the case of :all.
19
+ def time(task = :default, type = :current)
20
+ return nil unless tasks.keys.include?(task)
21
+ numbers = tasks[task][:history].map { |v| v[:time] }
10
22
  case type
11
23
  when :current
12
- return nil unless @tasks[task][:current]
13
- return Time.now.to_f - @tasks[task][:current]
14
- when :min
15
- return numbers.min
16
- when :max
17
- return numbers.max
24
+ return nil unless tasks[task][:current]
25
+ Time.now.to_f - tasks[task][:current]
26
+ when :min, :max, :first, :last
27
+ numbers.send(type)
18
28
  when :avg
19
- return numbers.inject{ |sum, n| sum + n }.to_f / numbers.size
29
+ numbers.size.zero? ? nil : numbers.inject { |sum, n| sum + n }.to_f / numbers.size
20
30
  when :sum
21
- return numbers.inject{ |sum, n| sum + n }
31
+ numbers.inject { |sum, n| sum + n }
22
32
  when :all
23
- return numbers
24
- when :first
25
- return numbers.first
26
- when :last
27
- return numbers.last
33
+ numbers
28
34
  when :count
29
- return numbers.size
35
+ numbers.size
30
36
  end
31
37
  end
32
38
 
33
- def clear task
34
- return nil unless @tasks.keys.include?(task)
39
+ # Removes all history for a given task
40
+ #
41
+ # @param [Symbol] task The name of the task to clear history from.
42
+ # @return [NilClass] Returns nil
43
+ def clear(task = :default)
44
+ return nil unless tasks.keys.include?(task)
35
45
  stop task
36
- @tasks[task][:history].clear
46
+ tasks[task][:history].clear
37
47
  end
38
48
 
39
- def start task = :default
40
- if !@tasks.keys.include?(task) then @tasks[task] = {history: [], current: nil} end
41
- if @tasks[task][:current] then stop task end
42
- @tasks[task][:current] = Time.now.to_f
43
- return 0
49
+ # Start a new timer for the referenced task. If a timer is already running for that task it will be stopped first.
50
+ #
51
+ # @param [Symbol] task The name of the task to start.
52
+ # @return [Integer] Returns 0
53
+ def start(task = :default)
54
+ tasks[task] = { history: [], current: nil } unless tasks.keys.include?(task)
55
+ stop task if tasks[task][:current]
56
+ tasks[task][:current] = Time.now.to_f
57
+ 0
44
58
  end
45
59
 
46
- def stop task = :default
47
- return nil unless @tasks.keys.include?(task) && active?(task)
48
- time_taken = Time.now.to_f - @tasks[task][:current].to_f
49
- @tasks[task][:history] << {start: @tasks[task][:current], stop: Time.now.to_f, time: time_taken}
50
- @tasks[task][:current] = nil
51
- if @retention && @tasks[task][:history].size > @retention then @tasks[task][:history].shift end
60
+ # Stop the referenced timer.
61
+ #
62
+ # @param [Symbol] task The name of the task to stop.
63
+ # @return [Float, NilClass] The amount of time the task had been running or nil if no matching task was found.
64
+ def stop(task = :default)
65
+ return nil unless tasks.keys.include?(task) && active?(task)
66
+ time_taken = Time.now.to_f - tasks[task][:current].to_f
67
+ tasks[task][:history] << { start: tasks[task][:current], stop: Time.now.to_f, time: time_taken }
68
+ tasks[task][:current] = nil
69
+ if retention && tasks[task][:history].size > retention then tasks[task][:history].shift end
52
70
  time_taken
53
71
  end
54
72
 
55
- def restart task = :default
73
+ def restart(task = :default)
56
74
  start(task) unless stop(task).nil?
57
75
  end
58
76
 
59
- def active? task
60
- return false unless @tasks.keys.include? task
61
- !@tasks[task][:current].nil?
77
+ def active?(task = :default)
78
+ return false unless tasks.keys.include?(task)
79
+ !tasks[task][:current].nil?
62
80
  end
63
81
 
64
- def stats task, pretty: false
65
- return nil unless @tasks.include?(task)
66
- stats = "#{task}" + "\n" + '-'*30 + "\n"
67
- TIMER_TYPES.each do |k,v|
82
+ def stats(task = :default, pretty: false)
83
+ return nil unless tasks.include?(task)
84
+ TIMER_TYPES.map do |k, _v|
68
85
  next if STATS_IGNORE.include?(k)
69
- stats+= k.to_s.capitalize.ljust(10) + "#{self.send(k, task, pretty:pretty)}\n"
70
- end
71
- stats
86
+ [k, send(k, task, pretty: pretty)]
87
+ end.compact.to_h
72
88
  end
73
89
 
74
- def method_missing *args, **named
75
- temp = args.first.to_sym
76
- pretty = named.delete :pretty
77
- type, task = TIMER_TYPES.keys.find{ |k| k == temp || TIMER_TYPES[k].include?(temp) }, args[1] ||= :default
90
+ def method_missing(*args, **named)
91
+ temp = args.first.to_sym
92
+ type = TIMER_TYPES.keys.find { |k| k == temp || TIMER_TYPES[k].include?(temp) }
78
93
  return super unless type
79
- t = time task, type
80
- pretty && type != :count && t ? (t.is_a?(Array) ? t.map{|m| m.to_duration} : t.to_duration) : t
94
+ t = time(args[1] || :default, type)
95
+ return t if type == :count || !named[:pretty]
96
+ t.is_a?(Array) ? t.map(&:to_duration) : t.to_duration
81
97
  end
82
98
 
83
- private
99
+ def respond_to_missing?(method, include_private = false)
100
+ TIMER_TYPES.keys.find { |k| k == method || TIMER_TYPES[k].include?(method) } || super
101
+ end
84
102
 
85
- STATS_IGNORE = [:current, :all]
103
+ TIMER_TYPES = {
104
+ current: [],
105
+ count: [:total],
106
+ first: [:initial],
107
+ last: [:latest],
108
+ min: [:minimum, :smallest],
109
+ max: [:maximum, :largest],
110
+ avg: [:average, :av],
111
+ sum: [],
112
+ all: [:times]
113
+ }.freeze
86
114
 
87
- TIMER_TYPES = {
88
- current: [],
89
- count: [:total],
90
- first: [:initial],
91
- last: [:latest],
92
- min: [:minimum, :smallest],
93
- max: [:maximum, :largest],
94
- avg: [:average, :av],
95
- sum: [],
96
- all: [:times]
97
- }
115
+ protected
98
116
 
99
- def lazy_init *args
100
- if args.first.is_a?(Symbol)
101
- start(args.first)
102
- end
103
- end
117
+ STATS_IGNORE = [:all].freeze
104
118
 
119
+ def simple_init(*args)
120
+ start(args.first) if args.first.is_a?(Symbol)
121
+ end
105
122
  end
106
-
107
123
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bblib
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Black
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-18 00:00:00.000000000 Z
11
+ date: 2018-03-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -87,17 +87,31 @@ files:
87
87
  - lib/array/bbarray.rb
88
88
  - lib/bblib.rb
89
89
  - lib/bblib/version.rb
90
+ - lib/class/effortless.rb
91
+ - lib/error/abstract.rb
90
92
  - lib/file/bbfile.rb
91
- - lib/gem/bbgem.rb
92
93
  - lib/hash/bbhash.rb
93
- - lib/hash/hash_path.rb
94
- - lib/hash/hash_path_proc.rb
95
- - lib/hash/path_hash.rb
94
+ - lib/hash/hash_struct.rb
95
+ - lib/hash/tree_hash.rb
96
+ - lib/hash_path/hash_path.rb
97
+ - lib/hash_path/part.rb
98
+ - lib/hash_path/path_hash.rb
99
+ - lib/hash_path/proc.rb
100
+ - lib/hash_path/processors.rb
101
+ - lib/html/bbhtml.rb
102
+ - lib/html/builder.rb
103
+ - lib/html/tag.rb
104
+ - lib/logging/bblogging.rb
105
+ - lib/mixins/attrs.rb
106
+ - lib/mixins/bbmixins.rb
107
+ - lib/mixins/bridge.rb
108
+ - lib/mixins/family_tree.rb
109
+ - lib/mixins/hooks.rb
110
+ - lib/mixins/logger.rb
111
+ - lib/mixins/serializer.rb
112
+ - lib/mixins/simple_init.rb
96
113
  - lib/number/bbnumber.rb
97
- - lib/object/attr.rb
98
114
  - lib/object/bbobject.rb
99
- - lib/object/hooks.rb
100
- - lib/object/lazy_class.rb
101
115
  - lib/opal/bbopal.rb
102
116
  - lib/os/bbos.rb
103
117
  - lib/os/bbsys.rb
@@ -105,7 +119,10 @@ files:
105
119
  - lib/string/cases.rb
106
120
  - lib/string/fuzzy_matcher.rb
107
121
  - lib/string/matching.rb
122
+ - lib/string/pluralization.rb
123
+ - lib/string/regexp.rb
108
124
  - lib/string/roman.rb
125
+ - lib/system/bbsystem.rb
109
126
  - lib/time/bbtime.rb
110
127
  - lib/time/cron.rb
111
128
  - lib/time/task_timer.rb
@@ -129,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
146
  version: '0'
130
147
  requirements: []
131
148
  rubyforge_project:
132
- rubygems_version: 2.4.5.1
149
+ rubygems_version: 2.7.4
133
150
  signing_key:
134
151
  specification_version: 4
135
152
  summary: A library containing many reusable, basic functions.
@@ -1,28 +0,0 @@
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,344 +0,0 @@
1
- require_relative 'hash_path_proc'
2
-
3
- module BBLib
4
-
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
10
- end
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]]
30
- else
31
- nil
32
- end
33
- end.flatten(1).reject{ |m| m.nil? }
34
- matches = BBLib.analyze_hash_path_formula(formula, matches)
35
- recursive = false
36
- end
37
- matches
38
- end
39
-
40
- def self.hash_path_keys hash
41
- hash.squish.keys
42
- end
43
-
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
60
- end
61
- end
62
- hash.bridge(path, value:value, symbols:symbols) if matches.empty? && bridge
63
- end
64
- hash
65
- end
66
-
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
83
- end
84
- to
85
- end
86
-
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)
97
- end
98
- end
99
- end
100
- deleted.flatten.reject{ |v| v.nil? }
101
- end
102
-
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
107
- end
108
-
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
113
- end
114
-
115
- protected
116
-
117
- def self.split_path *paths
118
- paths.map{|pth| pth.to_s.gsub('..', '. .').scan(/(?:[\(|\[].*?[\)|\]]|[^\.])+/)}.flatten
119
- end
120
-
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
135
- end
136
- formula = path.scan(/\(.*\)/).first
137
- return key, formula
138
- end
139
-
140
- def self.analyze_hash_path_formula formula, hashes
141
- return hashes unless formula
142
- hashes.map do |p|
143
- begin
144
- if eval(p.is_a?(Hash) ? formula.gsub('$', "(#{p})") : formula.gsub('$', p.to_s))
145
- p
146
- else
147
- nil
148
- end
149
- rescue StandardError, Exception => e
150
- # Do nothing, the formula failed and we reject the value as a false
151
- end
152
- end.reject{ |x| x.nil? }
153
- end
154
-
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
164
- end
165
- end
166
-
167
- end
168
-
169
-
170
-
171
- class Hash
172
-
173
- def hash_path *path
174
- BBLib.hash_path self, *path
175
- end
176
-
177
- def hash_path_set *paths
178
- BBLib.hash_path_set self, *paths
179
- end
180
-
181
- def hash_path_copy *paths
182
- BBLib.hash_path_copy self, *paths
183
- end
184
-
185
- def hash_path_copy_to to, *paths
186
- BBLib.hash_path_copy_to self, to, *paths
187
- end
188
-
189
- def hash_path_delete *paths
190
- BBLib.hash_path_delete self, *paths
191
- end
192
-
193
- def hash_path_move *paths
194
- BBLib.hash_path_move self, *paths
195
- end
196
-
197
- def hash_path_move_to to, *paths
198
- BBLib.hash_path_move_to self, to, *paths
199
- end
200
-
201
- def hash_paths
202
- BBLib.hash_path_keys self
203
- end
204
-
205
- def hash_path_for value
206
- BBLib.hash_path_key_for self, value
207
- end
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
-
219
- # Returns all matching values with a specific key (or Array of keys) recursively within a Hash (including nested Arrays)
220
- def dive *keys
221
- matches = Array.new
222
- self.each do |k, v|
223
- if keys.any?{ |a| (a.is_a?(Regexp) ? a =~ k : a == k ) } then matches << v end
224
- if v.respond_to? :dive
225
- matches+= v.dive(*keys)
226
- end
227
- end
228
- matches
229
- end
230
-
231
- # Turns nested values' keys into delimiter separated paths
232
- def squish delimiter: '.'
233
- sh = Hash.new
234
- BBLib.hash_path_nav(self.dup, nil, delimiter){ |k, v| sh[k] = v }
235
- sh
236
- end
237
-
238
- # Expands keys in a hash using a delimiter. Opposite of squish.
239
- def expand **args
240
- eh = Hash.new
241
- self.dup.each do |k,v|
242
- eh.bridge k, args.merge({value:v})
243
- end
244
- return eh
245
- end
246
-
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
257
- 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
261
- else
262
- hash[part] = path.first =~ /\A\[\d+\]\z/ ? Array.new : Hash.new
263
- hash = hash[part] unless bail || path.empty?
264
- end
265
- end
266
- hash[part] = value unless bail
267
- self
268
- end
269
-
270
- end
271
-
272
- class Array
273
-
274
- def hash_path *path
275
- BBLib.hash_path self, *path
276
- end
277
-
278
- def hash_path_set *paths
279
- BBLib.hash_path_set self, *paths
280
- end
281
-
282
- def hash_path_copy *paths
283
- BBLib.hash_path_copy self, *paths
284
- end
285
-
286
- def hash_path_copy_to to, *paths
287
- BBLib.hash_path_copy_to self, to, *paths
288
- end
289
-
290
- def hash_path_delete *paths
291
- BBLib.hash_path_delete self, *paths
292
- end
293
-
294
- def hash_path_move *paths
295
- BBLib.hash_path_move self, *paths
296
- end
297
-
298
- def hash_path_move_to to, *paths
299
- BBLib.hash_path_move_to self, to, *paths
300
- end
301
-
302
- def hash_paths
303
- BBLib.hash_path_keys self
304
- end
305
-
306
- def hash_path_for value
307
- BBLib.hash_path_key_for self, value
308
- end
309
-
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
321
- matches = []
322
- self.each do |i|
323
- matches+= i.dive(*keys) if i.respond_to?(:dive)
324
- end
325
- matches
326
- end
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
-
344
- end