bblib 0.3.0 → 0.4.1

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.
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,12 +1,20 @@
1
1
  module BBLib
2
-
3
- # Used to keep any numeric number between a set of bounds. Passing nil as min or max represents no bounds in that direction.
2
+ # Used to keep any numeric number between a set of bounds.
3
+ # Passing nil as min or max represents no bounds in that direction.
4
4
  # min and max are inclusive to the allowed bounds.
5
- def self.keep_between num, min, max
6
- raise "Argument must be numeric: #{num} (#{num.class})" unless Numeric === num
7
- if !min.nil? && num < min then num = min end
8
- if !max.nil? && num > max then num = max end
9
- return num
5
+ def self.keep_between(num, min, max)
6
+ num = num.to_f unless num.is_a?(Numeric)
7
+ num = min if min && num < min
8
+ num = max if max && num > max
9
+ num
10
10
  end
11
11
 
12
+ # Similar to keep between but when a number exceeds max or is less than min
13
+ # it is looped to the min or max value respectively.
14
+ def self.loop_between(num, min, max)
15
+ num = num.to_f unless num.is_a?(Numeric)
16
+ num = max if min && num < min
17
+ num = min if max && num > max
18
+ num
19
+ end
12
20
  end
@@ -1,44 +1,71 @@
1
- require_relative 'attr'
2
- require_relative 'hooks'
3
- require_relative 'simple_serialize'
1
+ # frozen_string_literal: true
2
+ # require_relative 'attr'
3
+ # require_relative 'hooks'
4
4
 
5
5
  module BBLib
6
+ def self.are_all?(klass, *vars)
7
+ vars.all? { |var| var.is_a?(klass) }
8
+ end
9
+
10
+ def self.is_a?(obj, *klasses)
11
+ klasses.any? { |klass| obj.is_a?(klass) }
12
+ end
6
13
 
7
- def self.to_hash obj
8
- return {obj => nil} unless !obj.instance_variables.empty?
14
+ def self.to_hash(obj)
15
+ return { obj => nil } if obj.instance_variables.empty?
9
16
  hash = {}
10
17
  obj.instance_variables.each do |var|
11
18
  value = obj.instance_variable_get(var)
12
- if value.is_a? Array
13
- hash[var.to_s.delete("@")] = value.map{ |v| v.respond_to?(:obj_to_hash) && !v.instance_variables.empty? ? v.obj_to_hash : v }
14
- elsif value.is_a? Hash
19
+ if value.is_a?(Array)
20
+ hash[var.to_s.delete('@')] = value.map { |v| v.respond_to?(:obj_to_hash) && !v.instance_variables.empty? ? v.obj_to_hash : v }
21
+ elsif value.is_a?(Hash)
15
22
  begin
16
- if !hash[var.to_s.delete("@")].is_a?(Hash) then hash[var.to_s.delete("@")] = Hash.new end
23
+ unless hash[var.to_s.delete('@')].is_a?(Hash) then hash[var.to_s.delete('@')] = {} end
17
24
  rescue
18
- hash[var.to_s.delete("@")] = Hash.new
25
+ hash[var.to_s.delete('@')] = {}
19
26
  end
20
27
  value.each do |k, v|
21
- hash[var.to_s.delete("@")][k.to_s.delete("@")] = v.respond_to?(:obj_to_hash) && !v.instance_variables.empty? ? v.obj_to_hash : v
28
+ hash[var.to_s.delete('@')][k.to_s.delete('@')] = v.respond_to?(:obj_to_hash) && !v.instance_variables.empty? ? v.obj_to_hash : v
22
29
  end
23
30
  elsif value.respond_to?(:obj_to_hash) && !value.instance_variables.empty?
24
- hash[var.to_s.delete("@")] = value.obj_to_hash
31
+ hash[var.to_s.delete('@')] = value.obj_to_hash
25
32
  else
26
- hash[var.to_s.delete("@")] = value
33
+ hash[var.to_s.delete('@')] = value
27
34
  end
28
35
  end
29
- return hash
36
+ hash
30
37
  end
31
38
 
32
- def self.named_args *args
33
- args.last.is_a?(Hash) && args.last.keys.all?{|k|k.is_a?(Symbol)} ? args.last : Hash.new
39
+ def self.named_args(*args)
40
+ args.last.is_a?(Hash) && args.last.keys.all? { |k| k.is_a?(Symbol) } ? args.last : {}
34
41
  end
35
42
 
36
- def self.named_args! *args
37
- if args.last.is_a?(Hash) && args.last.keys.all?{|k|k.is_a?(Symbol)}
43
+ def self.named_args!(*args)
44
+ if args.last.is_a?(Hash) && args.last.keys.all? { |k| k.is_a?(Symbol) }
38
45
  args.delete_at(-1)
39
46
  else
40
- Hash.new
47
+ {}
41
48
  end
42
49
  end
43
50
 
51
+ def self.hash_args(*args)
52
+ args.find_all { |a| a.is_a?(Hash) }.each_with_object({}) { |a, h| h.merge!(a) }
53
+ end
54
+
55
+ def self.recursive_send(obj, *methods)
56
+ methods.each do |args|
57
+ obj = obj.send(*args)
58
+ end
59
+ obj
60
+ end
61
+
62
+ def self.namespace_of(klass)
63
+ split = klass.to_s.split('::')
64
+ return klass if split.size == 1
65
+ Object.const_get(split[0..-2].join('::'))
66
+ end
67
+
68
+ def self.root_namespace_of(klass)
69
+ Object.const_get(klass.to_s.gsub(/::.*/, ''))
70
+ end
44
71
  end
@@ -1,16 +1,12 @@
1
1
  module BBLib
2
-
3
2
  def self.in_opal?
4
3
  RUBY_ENGINE == 'opal'
5
4
  end
6
-
7
5
  end
8
6
 
9
7
  if BBLib.in_opal?
10
8
  class Element
11
-
12
9
  alias_native :replace_with, :replaceWith
13
10
  alias_native :prepend
14
- alias_native :get_context, :getContext
15
11
  end
16
12
  end
@@ -1,9 +1,7 @@
1
1
  require_relative 'bbsys'
2
2
 
3
3
  module BBLib
4
-
5
4
  module OS
6
-
7
5
  def self.os
8
6
  return :windows if windows?
9
7
  return :mac if mac?
@@ -11,7 +9,7 @@ module BBLib
11
9
  end
12
10
 
13
11
  def self.windows?
14
- builds = ['mingw', 'mswin', 'cygwin', 'bccwin']
12
+ builds = %w(mingw mswin cygwin bccwin)
15
13
  !(/#{builds.join('|')}/i =~ RUBY_PLATFORM).nil?
16
14
  end
17
15
 
@@ -31,13 +29,13 @@ module BBLib
31
29
  def self.os_info
32
30
  if windows?
33
31
  data = `wmic os get manufacturer,name,organization,osarchitecture,version /format:list`
34
- data = data.split("\n").reject{ |r| r.strip == '' }.map do |m|
32
+ data = data.split("\n").reject { |r| r.strip == '' }.map do |m|
35
33
  spl = m.split('=')
36
34
  [spl.first.to_clean_sym.downcase, spl[1..-1].join('=')]
37
35
  end.to_h
38
36
  data[:name] = data[:name].split('|').first
39
37
  data[:osarchitecture] = data[:osarchitecture].extract_integers.first
40
- data.hpath_move( 'osarchitecture' => 'bits' )
38
+ data.hpath_move('osarchitecture' => 'bits')
41
39
  data[:host] = `hostname`.strip
42
40
  data[:os] = os
43
41
  data
@@ -57,12 +55,12 @@ module BBLib
57
55
  # Try finding the release file and parsing it instead of lsb_release
58
56
  begin
59
57
  release = `cat /etc/*release`
60
- .split("\n")
61
- .reject{ |l| !(l.include?(':') || l.include?('=')) }
62
- .map{|l| l.msplit('=',':') }
63
- .map{ |a| [a.first.downcase.to_clean_sym, a[1..-1].join(':').uncapsulate] }
64
- .to_h
65
- rescue
58
+ .split("\n")
59
+ .reject { |l| !(l.include?(':') || l.include?('=')) }
60
+ .map { |l| l.msplit('=', ':') }
61
+ .map { |a| [a.first.downcase.to_clean_sym, a[1..-1].join(':').uncapsulate] }
62
+ .to_h
63
+ rescue StandardError => e
66
64
  # Both attempts failed
67
65
  end
68
66
  end
@@ -78,16 +76,26 @@ module BBLib
78
76
  # The following is Windows specific code
79
77
  if windows?
80
78
 
81
- def self.parse_wmic cmd
79
+ def self.parse_wmic(cmd)
82
80
  `#{cmd} /format:list`
83
81
  .split("\n\n\n").reject(&:empty?)
84
- .map{ |l| l.split("\n\n")
85
- .map{ |l| spl = l.split('='); [spl.first.strip.downcase.to_clean_sym, spl[1..-1].join('=').strip ] }.to_h
86
- }.reject(&:empty?)
82
+ .map do |l|
83
+ l.split("\n\n")
84
+ .map { |l| spl = l.split('='); [spl.first.strip.downcase.to_clean_sym, spl[1..-1].join('=').strip] }.to_h
85
+ end.reject(&:empty?)
87
86
  end
88
87
 
89
88
  end
90
89
 
90
+ # Mostly platform agnost way to find the full path of an executable in the current env path.
91
+ def self.which(cmd)
92
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
93
+ (ENV['PATHEXT']&.split(';') || ['']).each do |ext|
94
+ executable = File.join(path, "#{cmd}#{ext.downcase}").pathify
95
+ return executable if File.executable?(executable) && !File.directory?(executable)
96
+ end
97
+ end
98
+ nil
99
+ end
91
100
  end
92
-
93
101
  end
@@ -1,24 +1,21 @@
1
1
 
2
-
3
-
4
2
  module BBLib
5
3
  module OS
6
-
7
4
  def self.cpu_usages
8
5
  if windows?
9
- {
10
- total: `wmic cpu get loadpercentage /format:value`.extract_numbers.first.to_f
11
- }
6
+ { total: `wmic cpu get loadpercentage /format:value`.extract_numbers.first.to_f }
12
7
  elsif linux? || mac?
13
8
  system_stats[:cpu]
14
- else
15
- nil
16
9
  end
17
10
  end
18
11
 
12
+ def self.up_since
13
+ Time.now - uptime
14
+ end
15
+
19
16
  def self.uptime
20
17
  if windows?
21
- uptime = `net statistics server`.split("\n").find{|l| l.start_with?('Statistics since ')}.split(/since /i).last.strip
18
+ uptime = `net statistics server`.split("\n").find { |l| l.start_with?('Statistics since ') }.split(/since /i).last.strip
22
19
  Time.now - Time.strptime(uptime, '%m/%d/%Y %l:%M:%S %p')
23
20
  else
24
21
  `cat /proc/uptime`.extract_numbers.first
@@ -33,13 +30,20 @@ module BBLib
33
30
  100 - cpu_used
34
31
  end
35
32
 
33
+ def self.current_memory_usage(output = :byte)
34
+ if windows?
35
+ `tasklist /FI "PID eq #{$$}" /FO list`
36
+ .split("\n").find { |l| l =~ /^Mem Usage:/ }&.gsub(',', '').parse_file_size(output: output)
37
+ else
38
+ processes.find { |pr| pr[:pid] == $$ }[:memory]
39
+ end
40
+ end
41
+
36
42
  def self.mem_total
37
43
  if windows?
38
44
  `wmic computersystem get TotalPhysicalMemory`.extract_numbers.first / 1024.0
39
45
  elsif linux?
40
46
  system_stats.hpath('memory.total')
41
- else
42
- nil
43
47
  end
44
48
  end
45
49
 
@@ -56,8 +60,6 @@ module BBLib
56
60
  `wmic os get freephysicalmemory /format:value`.extract_numbers.first
57
61
  elsif linux?
58
62
  system_stats.hpath('memory.free')
59
- else
60
- nil
61
63
  end
62
64
  end
63
65
 
@@ -83,16 +85,16 @@ module BBLib
83
85
  }
84
86
  else
85
87
  stats = `top -b -n2 -d 0.1`.split("\n")
86
- cpu = stats.find_all{|l| l =~ /\A\%?Cpu\(s\)/i }.last.extract_numbers
87
- loads = stats.find_all{|l| l =~ / load average\: /i }.last.scan(/load average:.*/i).first.extract_numbers
88
- mem = stats.find_all{|l| l =~ /KiB Mem|Mem\:/i }.last.extract_numbers
88
+ cpu = stats.find_all { |l| l =~ /\A\%?Cpu\(s\)/i }.last.extract_numbers
89
+ loads = stats.find_all { |l| l =~ / load average\: /i }.last.scan(/load average:.*/i).first.extract_numbers
90
+ mem = stats.find_all { |l| l =~ /KiB Mem|Mem\:/i }.last.extract_numbers
89
91
  time = `cat /proc/uptime`.extract_numbers
90
92
  {
91
93
  cpu: {
92
94
  user: cpu[0],
93
95
  system: cpu[1],
94
96
  nice: cpu[2],
95
- total: cpu[0..2].inject(0){ |sum, v| sum += v.to_f },
97
+ total: cpu[0..2].inject(0) { |sum, v| sum += v.to_f },
96
98
  idle: cpu[3],
97
99
  wait: cpu[4],
98
100
  hardware_interrupts: cpu[5],
@@ -123,39 +125,52 @@ module BBLib
123
125
  tasks = `tasklist /v`
124
126
  cpu = `wmic path win32_perfformatteddata_perfproc_process get PercentProcessorTime,percentusertime,IDProcess /format:list`
125
127
  cpu = cpu.split("\n\n\n\n").reject(&:empty?)
126
- .map{ |l| l.scan(/\d+/).map(&:to_i)}
127
- .map{ |n|[ n[0], {cpu: n[1], user: n[2] }]}.to_h
128
- lines = tasks.split("\n")[3..-1].map{ |l| l.split(/\s{2,}/) }
129
- mem = mem_total
130
- cmds = `wmic process get processid,commandline /format:csv`.split("\n")[1..-1].reject{ |r| r.strip == ''}.map{ |l| l.split(',')[1..-1] }.map{ |l| [l.last.to_i, l[0..-2].join(',')]}.to_h
128
+ .map { |l| l.scan(/\d+/).map(&:to_i) }
129
+ .map { |n| [n[0], { cpu: n[1], user: n[2] }] }.to_h
130
+ lines = tasks.split("\n")[3..-1].map { |l| l.split(/\s{2,}/) }
131
+ cmds = `wmic process get processid,commandline /format:csv`.split("\n")[1..-1].reject { |r| r.strip == '' }
132
+ .map { |l| l.split(',')[1..-1] }
133
+ .map { |l| [l.last.to_i, l[0..-2].join(',')] }.to_h
131
134
  lines.map do |l|
132
135
  pid = l[1].extract_numbers.first
133
136
  {
134
- name: l[0],
135
- pid: pid,
136
- user: l[4],
137
- mem: (((l[3].gsub(',', '').extract_numbers.first / mem_total) * 100) rescue nil),
138
- cpu: (cpu[pid][:cpu] rescue nil),
139
- cmd: cmds[pid]
137
+ name: l[0],
138
+ pid: pid,
139
+ user: l[4],
140
+ cpu: (cpu[pid][:cpu] rescue nil),
141
+ memory: (((l[3].delete(',').extract_numbers.first / mem_total) * 100) rescue nil),
142
+ cmd: cmds[pid]
140
143
  }
141
144
  end
142
145
  else
143
146
  t = `ps -e -o comm,pid,ruser,%cpu,%mem,cmd`
144
- lines = t.split("\n")[1..-1].map{ |l| l.split(/\s+/) }
145
- lines.map{ |l| l.size == 6 ? l : [l[0], l[1], l[2], l[3], l[4], l[5..-1].join(' ')] }
147
+ lines = t.split("\n")[1..-1].map { |l| l.split(/\s+/) }
148
+ lines.map { |l| l.size == 6 ? l : [l[0], l[1], l[2], l[3], l[4], l[5..-1].join(' ')] }
146
149
  lines.map do |l|
147
150
  {
148
- name: l[0],
149
- pid: l[1].to_i,
150
- user: l[2],
151
- cpu: l[3].to_f,
152
- mem: l[4].to_f,
153
- cmd: l[5]
151
+ name: l[0],
152
+ pid: l[1].to_i,
153
+ user: l[2],
154
+ cpu: l[3].to_f,
155
+ memory: l[4].to_f,
156
+ cmd: l[5]
154
157
  }
155
158
  end
156
159
  end
157
160
  end
158
161
 
162
+ def self.process_list
163
+ if windows?
164
+ tasks = `tasklist /v`
165
+ lines = tasks.split("\n")[3..-1].map { |l| l.split(/\s{2,}/) }
166
+ else
167
+ t = `ps -e -o comm,pid,ruser,%cpu,%mem,cmd`
168
+ lines = t.split("\n")[1..-1].map { |l| l.split(/\s+/) }
169
+ lines.map { |l| l.size == 6 ? l : [l[0], l[1], l[2], l[3], l[4], l[5..-1].join(' ')] }
170
+ end
171
+ lines.map { |l| l[0] }.sort
172
+ end
173
+
159
174
  def self.filesystems
160
175
  if windows?
161
176
  types = {
@@ -184,14 +199,16 @@ module BBLib
184
199
  v[:size] = v[:size].to_i
185
200
  v[:used] = v[:size] - v[:free]
186
201
  v[:free_p] = (v[:free] / v[:size].to_f) * 100
202
+ v[:free_p] = nil if v[:free_p].nan?
187
203
  v[:used_p] = (v[:used] / v[:size].to_f) * 100
204
+ v[:used_p] = nil if v[:used_p].nan?
188
205
  end
189
206
  v
190
207
  end
191
208
  else
192
209
  `df -aTB 1`
193
210
  .split("\n")[1..-1]
194
- .map{ |l| l.split(/\s{2,}|(?<=\d)\s|(?<=%)\s|(?<=\-)\s|(?<=\w)\s+(?=\w+\s+\d)/)}
211
+ .map { |l| l.split(/\s{2,}|(?<=\d)\s|(?<=%)\s|(?<=\-)\s|(?<=\w)\s+(?=\w+\s+\d)/) }
195
212
  .map do |i|
196
213
  {
197
214
  filesystem: i[0],
@@ -200,18 +217,17 @@ module BBLib
200
217
  used: i[3].to_i,
201
218
  available: i[4].to_i,
202
219
  used_p: i[5].extract_integers.first.to_f,
203
- mount: i[6],
220
+ mount: i[6]
204
221
  }
205
222
  end
206
223
  end
207
224
  end
208
225
 
209
-
210
226
  # A mostly platform agnostic call to get root volumes
211
227
  def self.root_volumes
212
228
  if BBLib.windows?
213
229
  begin # For windows
214
- `wmic logicaldisk get name`.split("\n").map{ |m| m.strip }[1..-1].reject{ |r| r == '' }
230
+ `wmic logicaldisk get name`.split("\n").map(&:strip)[1..-1].reject { |r| r == '' }
215
231
  rescue
216
232
  begin # Windows attempt 2
217
233
  `fsutil fsinfo drives`.scan(/(?<=\s)\w\:/)
@@ -221,7 +237,7 @@ module BBLib
221
237
  end
222
238
  else
223
239
  begin
224
- `ls /`.split("\n").map{ |m| m.strip }.reject{ |r| r == '' }
240
+ `ls /`.split("\n").map(&:strip).reject { |r| r == '' }
225
241
  rescue # All attempts failed
226
242
  nil
227
243
  end
@@ -231,8 +247,9 @@ module BBLib
231
247
  # Windows only method to get the volume labels of disk drives
232
248
  def self.root_volume_labels
233
249
  return nil unless BBLib.windows?
234
- `wmic logicaldisk get caption,volumename`.split("\n")[1..-1].map{ |m| [m.split(" ").first.to_s.strip, m.split(" ")[1..-1].to_a.join(' ').strip] }.reject{ |o,t| o == '' }.to_h
250
+ `wmic logicaldisk get caption,volumename`.split("\n")[1..-1]
251
+ .map { |m| [m.split(' ').first.to_s.strip, m.split(' ')[1..-1].to_a.join(' ').strip] }
252
+ .reject { |o, _t| o == '' }.to_h
235
253
  end
236
-
237
254
  end
238
255
  end
@@ -1,41 +1,42 @@
1
1
 
2
+ # frozen_string_literal: true
2
3
  require_relative 'matching'
3
4
  require_relative 'roman'
4
5
  require_relative 'fuzzy_matcher'
5
6
  require_relative 'cases'
7
+ require_relative 'regexp'
8
+ require_relative 'pluralization'
6
9
 
7
10
  module BBLib
8
-
9
- ##############################################
10
- # General Functions
11
- ##############################################
12
-
13
11
  # Quickly remove any symbols from a string leaving only alpha-numeric characters and white space.
14
- def self.drop_symbols str
12
+ def self.drop_symbols(str)
15
13
  str.gsub(/[^\w\s\d]|_/, '')
16
14
  end
17
15
 
18
16
  # Extract all integers from a string. Use extract_floats if numbers may contain decimal places.
19
- def self.extract_integers str, convert: true
20
- BBLib.extract_numbers(str, convert:false).reject{ |r| r.include?('.') }.map{ |m| convert ? m.to_i : m }
17
+ def self.extract_integers(str, convert: true)
18
+ BBLib.extract_numbers(str, convert: false).reject { |r| r.include?('.') }
19
+ .map { |m| convert ? m.to_i : m }
21
20
  end
22
21
 
23
22
  # Extracts all integers or decimals from a string into an array.
24
- def self.extract_floats str, convert: true
25
- BBLib.extract_numbers(str, convert:false).reject{ |r| !r.include?('.') }.map{ |m| convert ? m.to_f : m }
23
+ def self.extract_floats(str, convert: true)
24
+ BBLib.extract_numbers(str, convert: false).reject { |r| !r.include?('.') }
25
+ .map { |m| convert ? m.to_f : m }
26
26
  end
27
27
 
28
28
  # Extracts any correctly formed integers or floats from a string
29
- def self.extract_numbers str, convert: true
30
- str.scan(/\d+\.\d+[^\.]|\d+[^\.]/).map{ |f| convert ? (f.include?('.') ? f.to_f : f.to_i) : f }
29
+ def self.extract_numbers(str, convert: true)
30
+ str.scan(/\d+\.\d+(?<=[^\.])|\d+(?<=[^\.])|\d+\.\d+$|\d+$/)
31
+ .map { |f| convert ? (f.include?('.') ? f.to_f : f.to_i) : f }
31
32
  end
32
33
 
33
34
  # Used to move the position of the articles 'the', 'a' and 'an' in strings for normalization.
34
- def self.move_articles str, position = :front, capitalize: true
35
+ def self.move_articles(str, position = :front, capitalize: true)
35
36
  return str unless [:front, :back, :none].include?(position)
36
- articles = ["the", "a", "an"]
37
- articles.each do |a|
38
- starts, ends = str.downcase.start_with?(a + ' '), str.downcase.end_with?(' ' + a)
37
+ %w(the a an).each do |a|
38
+ starts = str.downcase.start_with?(a + ' ')
39
+ ends = str.downcase.end_with?(' ' + a)
39
40
  if starts && position != :front
40
41
  if position == :none
41
42
  str = str[(a.length + 1)..str.length]
@@ -43,41 +44,104 @@ module BBLib
43
44
  str = str[(a.length + 1)..str.length] + (!ends ? ", #{capitalize ? a.capitalize : a}" : '')
44
45
  end
45
46
  end
46
- if ends && position != :back
47
- if position == :none
48
- str = str[0..-(a.length + 2)]
49
- elsif position == :front
50
- str = (!starts ? "#{capitalize ? a.capitalize : a} " : '') + str[0..-(a.length + 2)]
51
- end
47
+ next unless ends && position != :back
48
+ if position == :none
49
+ str = str[0..-(a.length + 2)]
50
+ elsif position == :front
51
+ str = (!starts ? "#{capitalize ? a.capitalize : a} " : '') + str[0..-(a.length + 2)]
52
52
  end
53
53
  end
54
- while str.strip.end_with?(',')
55
- str = str.strip
56
- str = str.chop
57
- end
54
+ str = str.strip.chop while str.strip.end_with?(',')
58
55
  str
59
56
  end
60
57
 
58
+ # Displays a portion of an object (as a string) with an ellipse displayed
59
+ # if the string is over a certain size.
60
+ # Supported styles:
61
+ # => front - "for exam..."
62
+ # => back - "... example"
63
+ # => middle - "... exam..."
64
+ # => outter - "for e...ple"
65
+ # The length of the too_long string is NOT factored into the cap
66
+ def self.chars_up_to(str, cap, too_long = '...', style: :front)
67
+ return str if str.to_s.size <= cap
68
+ str = str.to_s
69
+ case style
70
+ when :back
71
+ "#{too_long}#{str[(str.size - cap)..-1]}"
72
+ when :outter
73
+ "#{str[0...(cap / 2).to_i + (cap.odd? ? 1 : 0)]}#{too_long}#{str[-(cap / 2).to_i..-1]}"
74
+ when :middle
75
+ "#{too_long}#{str[(str.size / 2 - cap / 2 - (cap.odd? ? 1 : 0)).to_i...(str.size / 2 + cap / 2).to_i]}#{too_long}"
76
+ else
77
+ "#{str[0...cap]}#{too_long}"
78
+ end
79
+ end
80
+
81
+ # Takes two strings and tries to apply the same capitalization from
82
+ # the first string to the second.
83
+ # Supports lower case, upper case and capital case
84
+ def self.copy_capitalization(str_a, str_b)
85
+ str_a = str_a.to_s
86
+ str_b = str_b.to_s
87
+ if str_a.upper?
88
+ str_b.upcase
89
+ elsif str_a.lower?
90
+ str_b.downcase
91
+ elsif str_a.capital?
92
+ str_b.capitalize
93
+ else
94
+ str_b
95
+ end
96
+ end
61
97
  end
62
98
 
63
99
  class String
64
100
  # Multi-split. Similar to split, but can be passed an array of delimiters to split on.
65
- def msplit *delims, keep_empty: false
66
- return [self] unless !delims.nil? && !delims.empty?
67
- ar = [self]
68
- [delims].flatten.each do |d|
69
- ar.map!{ |a| a.split d }
70
- ar.flatten!
101
+ def msplit(*delims)
102
+ ary = [self]
103
+ return ary if delims.empty?
104
+ delims.flatten.each do |d|
105
+ ary = ary.flat_map { |a| a.split d }
71
106
  end
72
- keep_empty ? ar : ar.reject{ |l| l.empty? }
107
+ ary
73
108
  end
74
109
 
75
- def move_articles position = :front, capitalize = true
76
- BBLib.move_articles self, position, capitalize:capitalize
110
+ # Split on delimiters
111
+ def quote_split(*delimiters)
112
+ encap_split('"\'', *delimiters)
77
113
  end
78
114
 
79
- def move_articles! position = :front, capitalize = true
80
- replace BBLib.move_articles(self, position, capitalize:capitalize)
115
+ alias qsplit quote_split
116
+
117
+ # Split on only delimiters not between specific encapsulators
118
+ # Various characters are special and automatically recognized such as parens
119
+ # which automatically match anything between a begin and end character.
120
+ def encap_split(encapsulator, *delimiters)
121
+ pattern = case encapsulator
122
+ when '('
123
+ '\\(\\)'
124
+ when '['
125
+ '\\[\\]'
126
+ when '{'
127
+ '\\{\\}'
128
+ when '<'
129
+ '\\<\\>'
130
+ else
131
+ encapsulator
132
+ end
133
+ patterns = delimiters.map { |d| /#{d}(?=(?:[^#{pattern}]|[#{pattern}][^#{pattern}]*[#{pattern}])*$)/}
134
+ msplit(*patterns)
135
+ end
136
+
137
+ alias esplit encap_split
138
+
139
+ def move_articles(position = :front, capitalize = true)
140
+ BBLib.move_articles self, position, capitalize: capitalize
141
+ end
142
+
143
+ def move_articles!(position = :front, capitalize = true)
144
+ replace BBLib.move_articles(self, position, capitalize: capitalize)
81
145
  end
82
146
 
83
147
  def drop_symbols
@@ -88,20 +152,20 @@ class String
88
152
  replace BBLib.drop_symbols(self)
89
153
  end
90
154
 
91
- def extract_integers convert: true
92
- BBLib.extract_integers self, convert:convert
155
+ def extract_integers(convert: true)
156
+ BBLib.extract_integers self, convert: convert
93
157
  end
94
158
 
95
- def extract_floats convert: true
96
- BBLib.extract_floats self, convert:convert
159
+ def extract_floats(convert: true)
160
+ BBLib.extract_floats self, convert: convert
97
161
  end
98
162
 
99
- def extract_numbers convert: true
100
- BBLib.extract_numbers self, convert:convert
163
+ def extract_numbers(convert: true)
164
+ BBLib.extract_numbers self, convert: convert
101
165
  end
102
166
 
103
167
  def to_clean_sym
104
- self.snake_case.to_sym
168
+ snake_case.to_sym
105
169
  end
106
170
 
107
171
  # Simple method to convert a string into an array containing only itself
@@ -109,44 +173,79 @@ class String
109
173
  [self]
110
174
  end
111
175
 
112
- def encap_by? str
176
+ def encap_by?(str)
113
177
  case str
114
178
  when '('
115
- self.start_with?(str) && self.end_with?(')')
179
+ start_with?(str) && end_with?(')')
116
180
  when '['
117
- self.start_with?(str) && self.end_with?(']')
181
+ start_with?(str) && end_with?(']')
118
182
  when '{'
119
- self.start_with?(str) && self.end_with?('}')
183
+ start_with?(str) && end_with?('}')
120
184
  when '<'
121
- self.start_with?(str) && self.end_with?('>')
185
+ start_with?(str) && end_with?('>')
122
186
  else
123
- self.start_with?(str) && self.end_with?(str)
187
+ start_with?(str) && end_with?(str)
124
188
  end
125
189
  end
126
190
 
127
- def uncapsulate char = '"'
128
- case char
129
- when '('
130
- back = ')'
131
- when '['
132
- back = ']'
133
- when '{'
134
- back = '}'
135
- when '<'
136
- back = '>'
137
- else
138
- back = char
191
+ def encapsulate(char = '"')
192
+ back = case char
193
+ when '('
194
+ ')'
195
+ when '['
196
+ ']'
197
+ when '{'
198
+ '}'
199
+ when '<'
200
+ '>'
201
+ else
202
+ char
203
+ end
204
+ "#{char}#{self}#{back}"
205
+ end
206
+
207
+ def uncapsulate(char = '"', limit: nil)
208
+ back = case char
209
+ when '('
210
+ ')'
211
+ when '['
212
+ ']'
213
+ when '{'
214
+ '}'
215
+ when '<'
216
+ '>'
217
+ else
218
+ char
219
+ end
220
+ temp = dup
221
+ count = 0
222
+ while temp.start_with?(char) && temp != char && (limit.nil? || count < limit)
223
+ temp = temp[(char.size)..-1]
224
+ count += 1
225
+ end
226
+ count = 0
227
+ while temp.end_with?(back) && temp != char && (limit.nil? || count < limit)
228
+ temp = temp[0..-(char.size + 1)]
229
+ count += 1
139
230
  end
140
- temp = self.dup
141
- temp = temp[(char.size)..-1] while temp.start_with?(char) && temp != char
142
- temp = temp[0..-(char.size + 1)] while temp.end_with?(back) && temp != char
143
231
  temp
144
232
  end
145
233
 
234
+ def upper?
235
+ chars.all? { |letter| /[[:upper:]]|\W/.match(letter) }
236
+ end
237
+
238
+ def lower?
239
+ chars.all? { |letter| /[[:lower:]]|\W/.match(letter) }
240
+ end
241
+
242
+ def capital?
243
+ chars.first.upper?
244
+ end
146
245
  end
147
246
 
148
247
  class Symbol
149
248
  def to_clean_sym
150
- self.to_s.to_clean_sym
249
+ to_s.to_clean_sym
151
250
  end
152
251
  end