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.
- checksums.yaml +5 -5
- data/.gitignore +11 -10
- data/.rspec +2 -2
- data/.travis.yml +4 -4
- data/CODE_OF_CONDUCT.md +13 -13
- data/Gemfile +4 -4
- data/LICENSE.txt +21 -21
- data/README.md +247 -757
- data/Rakefile +6 -6
- data/bblib.gemspec +34 -34
- data/bin/console +14 -14
- data/bin/setup +7 -7
- data/lib/array/bbarray.rb +71 -29
- data/lib/bblib.rb +12 -12
- data/lib/bblib/version.rb +3 -3
- data/lib/class/effortless.rb +23 -0
- data/lib/error/abstract.rb +3 -0
- data/lib/file/bbfile.rb +93 -52
- data/lib/hash/bbhash.rb +130 -46
- data/lib/hash/hash_struct.rb +24 -0
- data/lib/hash/tree_hash.rb +364 -0
- data/lib/hash_path/hash_path.rb +210 -0
- data/lib/hash_path/part.rb +83 -0
- data/lib/hash_path/path_hash.rb +84 -0
- data/lib/hash_path/proc.rb +93 -0
- data/lib/hash_path/processors.rb +239 -0
- data/lib/html/bbhtml.rb +2 -0
- data/lib/html/builder.rb +34 -0
- data/lib/html/tag.rb +49 -0
- data/lib/logging/bblogging.rb +42 -0
- data/lib/mixins/attrs.rb +422 -0
- data/lib/mixins/bbmixins.rb +7 -0
- data/lib/mixins/bridge.rb +17 -0
- data/lib/mixins/family_tree.rb +41 -0
- data/lib/mixins/hooks.rb +139 -0
- data/lib/mixins/logger.rb +31 -0
- data/lib/mixins/serializer.rb +71 -0
- data/lib/mixins/simple_init.rb +160 -0
- data/lib/number/bbnumber.rb +15 -7
- data/lib/object/bbobject.rb +46 -19
- data/lib/opal/bbopal.rb +0 -4
- data/lib/os/bbos.rb +24 -16
- data/lib/os/bbsys.rb +60 -43
- data/lib/string/bbstring.rb +165 -66
- data/lib/string/cases.rb +37 -29
- data/lib/string/fuzzy_matcher.rb +48 -50
- data/lib/string/matching.rb +43 -30
- data/lib/string/pluralization.rb +156 -0
- data/lib/string/regexp.rb +45 -0
- data/lib/string/roman.rb +17 -30
- data/lib/system/bbsystem.rb +42 -0
- data/lib/time/bbtime.rb +79 -58
- data/lib/time/cron.rb +174 -132
- data/lib/time/task_timer.rb +86 -70
- metadata +27 -10
- data/lib/gem/bbgem.rb +0 -28
- data/lib/hash/hash_path.rb +0 -344
- data/lib/hash/hash_path_proc.rb +0 -256
- data/lib/hash/path_hash.rb +0 -81
- data/lib/object/attr.rb +0 -182
- data/lib/object/hooks.rb +0 -69
- data/lib/object/lazy_class.rb +0 -73
data/lib/number/bbnumber.rb
CHANGED
@@ -1,12 +1,20 @@
|
|
1
1
|
module BBLib
|
2
|
-
|
3
|
-
#
|
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
|
6
|
-
|
7
|
-
if
|
8
|
-
if
|
9
|
-
|
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
|
data/lib/object/bbobject.rb
CHANGED
@@ -1,44 +1,71 @@
|
|
1
|
-
|
2
|
-
require_relative '
|
3
|
-
require_relative '
|
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
|
8
|
-
return {obj => nil}
|
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?
|
13
|
-
hash[var.to_s.delete(
|
14
|
-
elsif value.is_a?
|
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
|
-
|
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(
|
25
|
+
hash[var.to_s.delete('@')] = {}
|
19
26
|
end
|
20
27
|
value.each do |k, v|
|
21
|
-
hash[var.to_s.delete(
|
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(
|
31
|
+
hash[var.to_s.delete('@')] = value.obj_to_hash
|
25
32
|
else
|
26
|
-
hash[var.to_s.delete(
|
33
|
+
hash[var.to_s.delete('@')] = value
|
27
34
|
end
|
28
35
|
end
|
29
|
-
|
36
|
+
hash
|
30
37
|
end
|
31
38
|
|
32
|
-
def self.named_args
|
33
|
-
args.last.is_a?(Hash) && args.last.keys.all?{|k|k.is_a?(Symbol)} ? args.last :
|
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!
|
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
|
-
|
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
|
data/lib/opal/bbopal.rb
CHANGED
data/lib/os/bbos.rb
CHANGED
@@ -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 =
|
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(
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
79
|
+
def self.parse_wmic(cmd)
|
82
80
|
`#{cmd} /format:list`
|
83
81
|
.split("\n\n\n").reject(&:empty?)
|
84
|
-
.map
|
85
|
-
|
86
|
-
|
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
|
data/lib/os/bbsys.rb
CHANGED
@@ -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
|
-
|
127
|
-
|
128
|
-
lines = tasks.split("\n")[3..-1].map{ |l| l.split(/\s{2,}/) }
|
129
|
-
|
130
|
-
|
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:
|
135
|
-
pid:
|
136
|
-
user:
|
137
|
-
|
138
|
-
|
139
|
-
cmd:
|
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:
|
149
|
-
pid:
|
150
|
-
user:
|
151
|
-
cpu:
|
152
|
-
|
153
|
-
cmd:
|
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
|
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
|
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]
|
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
|
data/lib/string/bbstring.rb
CHANGED
@@ -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
|
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
|
20
|
-
BBLib.extract_numbers(str, convert:false).reject{ |r| r.include?('.') }
|
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
|
25
|
-
BBLib.extract_numbers(str, convert:false).reject{ |r| !r.include?('.') }
|
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
|
30
|
-
str.scan(/\d+\.\d+[^\.]|\d+[^\.]
|
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
|
35
|
+
def self.move_articles(str, position = :front, capitalize: true)
|
35
36
|
return str unless [:front, :back, :none].include?(position)
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
107
|
+
ary
|
73
108
|
end
|
74
109
|
|
75
|
-
|
76
|
-
|
110
|
+
# Split on delimiters
|
111
|
+
def quote_split(*delimiters)
|
112
|
+
encap_split('"\'', *delimiters)
|
77
113
|
end
|
78
114
|
|
79
|
-
|
80
|
-
|
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
|
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
|
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
|
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
|
-
|
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?
|
176
|
+
def encap_by?(str)
|
113
177
|
case str
|
114
178
|
when '('
|
115
|
-
|
179
|
+
start_with?(str) && end_with?(')')
|
116
180
|
when '['
|
117
|
-
|
181
|
+
start_with?(str) && end_with?(']')
|
118
182
|
when '{'
|
119
|
-
|
183
|
+
start_with?(str) && end_with?('}')
|
120
184
|
when '<'
|
121
|
-
|
185
|
+
start_with?(str) && end_with?('>')
|
122
186
|
else
|
123
|
-
|
187
|
+
start_with?(str) && end_with?(str)
|
124
188
|
end
|
125
189
|
end
|
126
190
|
|
127
|
-
def
|
128
|
-
case char
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
249
|
+
to_s.to_clean_sym
|
151
250
|
end
|
152
251
|
end
|