bblib 0.3.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|