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,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