greenhat 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/greenhat/accessors/disk.rb +13 -13
- data/lib/greenhat/accessors/gitlab.rb +75 -0
- data/lib/greenhat/accessors/memory.rb +10 -10
- data/lib/greenhat/accessors/process.rb +4 -0
- data/lib/greenhat/cli.rb +127 -52
- data/lib/greenhat/color.rb +27 -0
- data/lib/greenhat/logbot.rb +9 -9
- data/lib/greenhat/settings.rb +27 -7
- data/lib/greenhat/shell/args.rb +146 -0
- data/lib/greenhat/shell/cat.rb +25 -73
- data/lib/greenhat/shell/color_string.rb +8 -8
- data/lib/greenhat/shell/disk.rb +31 -4
- data/lib/greenhat/shell/faststats.rb +79 -59
- data/lib/greenhat/shell/{filter.rb → filter_help.rb} +48 -33
- data/lib/greenhat/shell/gitlab.rb +61 -2
- data/lib/greenhat/shell/help.rb +96 -15
- data/lib/greenhat/shell/list.rb +46 -0
- data/lib/greenhat/shell/log.rb +77 -104
- data/lib/greenhat/shell/page.rb +11 -5
- data/lib/greenhat/shell/process.rb +29 -17
- data/lib/greenhat/shell/report.rb +27 -43
- data/lib/greenhat/shell/{helper.rb → shell_helper.rb} +204 -187
- data/lib/greenhat/shell.rb +23 -9
- data/lib/greenhat/thing/file_types.rb +20 -1
- data/lib/greenhat/thing/formatters/json_shellwords.rb +0 -3
- data/lib/greenhat/thing/helpers.rb +4 -4
- data/lib/greenhat/thing/kind.rb +9 -2
- data/lib/greenhat/thing/spinner.rb +3 -3
- data/lib/greenhat/thing.rb +3 -3
- data/lib/greenhat/tty/columns.rb +4 -0
- data/lib/greenhat/version.rb +1 -1
- data/lib/greenhat.rb +14 -14
- metadata +22 -18
data/lib/greenhat/shell/page.rb
CHANGED
@@ -3,14 +3,18 @@ module GreenHat
|
|
3
3
|
# Helper to organize page check / methods
|
4
4
|
module Page
|
5
5
|
# Check if paging should be skipped
|
6
|
-
|
7
|
-
|
6
|
+
# True / False / Not Set
|
7
|
+
def self.skip?(flags, data)
|
8
|
+
# Pass if Explicitly Set / Inverse for skip
|
9
|
+
return !flags[:page] if flags.key? :page
|
8
10
|
|
9
|
-
|
11
|
+
LogBot.debug('Page', count_rows(data, flags)) if ENV['DEBUG']
|
12
|
+
|
13
|
+
count_rows(data, flags)
|
10
14
|
end
|
11
15
|
|
12
16
|
# Array/Hash and String pagination check. Don't unncessarily loop through everything
|
13
|
-
def self.count_rows(data)
|
17
|
+
def self.count_rows(data, flags)
|
14
18
|
height = TTY::Screen.height
|
15
19
|
size = 0
|
16
20
|
|
@@ -19,7 +23,9 @@ module GreenHat
|
|
19
23
|
when Hash then entry.keys.count
|
20
24
|
when Array then entry.count
|
21
25
|
else
|
22
|
-
|
26
|
+
# Each Boxed Entry is 3 Lines
|
27
|
+
flags.key?(:row_size) ? flags[:row_size] : 1
|
28
|
+
|
23
29
|
end
|
24
30
|
|
25
31
|
break if size > height
|
@@ -11,16 +11,22 @@ module GreenHat
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.help
|
14
|
-
puts "\u2500".
|
15
|
-
puts "#{'Process'.
|
16
|
-
puts "\u2500".
|
14
|
+
puts "\u2500".pastel(:cyan) * 22
|
15
|
+
puts "#{'Process'.pastel(:yellow)} - ps helper"
|
16
|
+
puts "\u2500".pastel(:cyan) * 22
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
ShellHelper.common_opts
|
19
|
+
|
20
|
+
puts 'Command Summary'.pastel(:blue)
|
21
|
+
puts ' ps'.pastel(:green)
|
20
22
|
puts ' Raw `ps`'
|
21
|
-
puts
|
22
|
-
puts '
|
23
|
-
puts
|
23
|
+
puts
|
24
|
+
puts ' filter'.pastel(:green)
|
25
|
+
puts " Key/Field Filtering. See #{'filter_help'.pastel(:blue)}"
|
26
|
+
puts ' Examples'
|
27
|
+
puts ' filter --sort=mem --reverse'.pastel(:green)
|
28
|
+
puts ' filter --user=gitlab'.pastel(:green)
|
29
|
+
|
24
30
|
puts
|
25
31
|
end
|
26
32
|
|
@@ -29,25 +35,31 @@ module GreenHat
|
|
29
35
|
end
|
30
36
|
|
31
37
|
def self.ps(raw = {})
|
32
|
-
|
33
|
-
|
38
|
+
# Extract Args
|
39
|
+
files_list, flags, _args = Args.parse(raw)
|
40
|
+
|
41
|
+
# Collect Files
|
42
|
+
files = ShellHelper.files(files_list, GreenHat::Ps.things, flags)
|
43
|
+
|
44
|
+
# Output
|
45
|
+
ShellHelper.file_output(files, flags)
|
34
46
|
end
|
35
47
|
|
36
48
|
def self.filter(raw = {})
|
37
|
-
|
49
|
+
# Argument Parsing
|
50
|
+
files, flags, args = Args.parse(raw)
|
38
51
|
|
39
|
-
#
|
40
|
-
|
52
|
+
# Prepare Log List
|
53
|
+
file_list = ShellHelper.prepare_list(files, GreenHat::Ps.things)
|
41
54
|
|
42
|
-
|
43
|
-
results = ShellHelper.filter_start(GreenHat::Ps.ps(args).map(&:name), filter_type, args, opts)
|
55
|
+
results = ShellHelper.filter_start(file_list, flags, args)
|
44
56
|
|
45
57
|
# Check Search Results
|
46
58
|
if results.instance_of?(Hash) && results.values.flatten.empty?
|
47
|
-
puts 'No results'.
|
59
|
+
puts 'No results'.pastel(:red)
|
48
60
|
else
|
49
61
|
# This causes the key 'colorized' output to also be included
|
50
|
-
ShellHelper.show(results.to_a.compact.flatten,
|
62
|
+
ShellHelper.show(results.to_a.compact.flatten, flags)
|
51
63
|
end
|
52
64
|
end
|
53
65
|
end
|
@@ -45,11 +45,11 @@ module GreenHat
|
|
45
45
|
|
46
46
|
def show
|
47
47
|
output = [
|
48
|
-
archive.friendly_name.
|
48
|
+
archive.friendly_name.pastel(:blue)
|
49
49
|
]
|
50
50
|
|
51
51
|
# OS
|
52
|
-
output << 'OS'.
|
52
|
+
output << 'OS'.pastel(:bright_yellow)
|
53
53
|
output << hostname if host
|
54
54
|
output << distro if os_release
|
55
55
|
output << selinux if selinux_status
|
@@ -62,7 +62,7 @@ module GreenHat
|
|
62
62
|
|
63
63
|
# Memory
|
64
64
|
if meminfo || free_m
|
65
|
-
output << 'Memory'.
|
65
|
+
output << 'Memory'.pastel(:bright_yellow)
|
66
66
|
output << memory_perc if meminfo
|
67
67
|
output << memory_free if free_m
|
68
68
|
output << ''
|
@@ -75,7 +75,7 @@ module GreenHat
|
|
75
75
|
end
|
76
76
|
|
77
77
|
# Gitlab
|
78
|
-
output << 'GitLab'.
|
78
|
+
output << 'GitLab'.pastel(:bright_yellow) if gitlab_manifest
|
79
79
|
output << gitlab_version if gitlab_manifest
|
80
80
|
output << gitlab_services if gitlab_status
|
81
81
|
output << title('Errors') if production_log || api_log || application_log || sidekiq_log
|
@@ -95,8 +95,8 @@ module GreenHat
|
|
95
95
|
color = count.zero? ? :green : :red
|
96
96
|
|
97
97
|
[
|
98
|
-
title(' Production', :
|
99
|
-
count.to_s.
|
98
|
+
title(' Production', :bright_red, 18),
|
99
|
+
count.to_s.pastel(color)
|
100
100
|
].join
|
101
101
|
end
|
102
102
|
|
@@ -105,8 +105,8 @@ module GreenHat
|
|
105
105
|
color = count.zero? ? :green : :red
|
106
106
|
|
107
107
|
[
|
108
|
-
title(' API', :
|
109
|
-
count.to_s.
|
108
|
+
title(' API', :bright_red, 18),
|
109
|
+
count.to_s.pastel(color)
|
110
110
|
].join
|
111
111
|
end
|
112
112
|
|
@@ -115,8 +115,8 @@ module GreenHat
|
|
115
115
|
color = count.zero? ? :green : :red
|
116
116
|
|
117
117
|
[
|
118
|
-
title(' Application', :
|
119
|
-
count.to_s.
|
118
|
+
title(' Application', :bright_red, 18),
|
119
|
+
count.to_s.pastel(color)
|
120
120
|
].join
|
121
121
|
end
|
122
122
|
|
@@ -125,32 +125,16 @@ module GreenHat
|
|
125
125
|
color = count.zero? ? :green : :red
|
126
126
|
|
127
127
|
[
|
128
|
-
title(' Sidekiq', :
|
129
|
-
count.to_s.
|
128
|
+
title(' Sidekiq', :bright_red, 18),
|
129
|
+
count.to_s.pastel(color)
|
130
130
|
].join
|
131
131
|
end
|
132
132
|
|
133
133
|
def gitlab_services
|
134
|
-
list = gitlab_status.data.keys.sort.map do |service|
|
135
|
-
color = gitlab_status.data.dig(service, 0, :status) == 'run' ? :green : :red
|
136
|
-
service.colorize(color)
|
137
|
-
end
|
138
|
-
|
139
|
-
# Keep Alphabetical Sort
|
140
|
-
groups = list.each_slice((list.size / 3.to_f).round).to_a
|
141
|
-
|
142
|
-
table = TTY::Table.new do |t|
|
143
|
-
loop do
|
144
|
-
break if groups.all?(&:empty?)
|
145
|
-
|
146
|
-
t << groups.map(&:shift)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
134
|
[
|
151
135
|
title('Services'),
|
152
136
|
"\n ",
|
153
|
-
|
137
|
+
GitLab.services(archive, 3)
|
154
138
|
].join
|
155
139
|
rescue StandardError => e
|
156
140
|
LogBot.fatal('GitLab Services', message: e.message, backtrace: e.backtrace.first)
|
@@ -173,7 +157,7 @@ module GreenHat
|
|
173
157
|
def distro
|
174
158
|
[
|
175
159
|
title('Distro'),
|
176
|
-
"[#{os_release.data.ID}] ".
|
160
|
+
"[#{os_release.data.ID}] ".pastel(:bright_black),
|
177
161
|
os_release.data.PRETTY_NAME
|
178
162
|
].join
|
179
163
|
end
|
@@ -184,7 +168,7 @@ module GreenHat
|
|
184
168
|
|
185
169
|
[
|
186
170
|
title('SeLinux'),
|
187
|
-
status.
|
171
|
+
status.pastel(status_color),
|
188
172
|
' (',
|
189
173
|
selinux_status.data['Current mode'],
|
190
174
|
')'
|
@@ -204,7 +188,7 @@ module GreenHat
|
|
204
188
|
[
|
205
189
|
title('Kernel'),
|
206
190
|
value,
|
207
|
-
" (#{build})".
|
191
|
+
" (#{build})".pastel(:bright_black)
|
208
192
|
].join
|
209
193
|
end
|
210
194
|
|
@@ -231,9 +215,9 @@ module GreenHat
|
|
231
215
|
[
|
232
216
|
title('Sys Time'),
|
233
217
|
timedatectl.data['Local time'],
|
234
|
-
' (ntp: '.
|
235
|
-
ntp_status.
|
236
|
-
')'.
|
218
|
+
' (ntp: '.pastel(:bright_black),
|
219
|
+
ntp_status.pastel(ntp_color),
|
220
|
+
')'.pastel(:bright_black)
|
237
221
|
].join
|
238
222
|
end
|
239
223
|
|
@@ -258,14 +242,14 @@ module GreenHat
|
|
258
242
|
[
|
259
243
|
interval,
|
260
244
|
' (',
|
261
|
-
"#{value}%".
|
245
|
+
"#{value}%".pastel(color),
|
262
246
|
')'
|
263
247
|
].join
|
264
248
|
end
|
265
249
|
|
266
250
|
[
|
267
251
|
title('LoadAvg'),
|
268
|
-
"[CPU #{cpu_count}] ".
|
252
|
+
"[CPU #{cpu_count}] ".pastel(:bright_white),
|
269
253
|
intervals_text.join(', ')
|
270
254
|
].join
|
271
255
|
end
|
@@ -277,11 +261,11 @@ module GreenHat
|
|
277
261
|
|
278
262
|
[
|
279
263
|
title('Usage'),
|
280
|
-
' ['.
|
281
|
-
'='.
|
264
|
+
' ['.pastel(:bright_black),
|
265
|
+
'='.pastel(:green) * (used / 2),
|
282
266
|
' ' * (50 - used / 2),
|
283
|
-
']'.
|
284
|
-
" #{100 - percent(free, total)}%".
|
267
|
+
']'.pastel(:bright_black),
|
268
|
+
" #{100 - percent(free, total)}%".pastel(:green) # Inverse
|
285
269
|
].join
|
286
270
|
end
|
287
271
|
|
@@ -317,7 +301,7 @@ module GreenHat
|
|
317
301
|
|
318
302
|
# Preapre / Indent List
|
319
303
|
[
|
320
|
-
'Disks(Top % Usage)'.
|
304
|
+
'Disks'.pastel(:bright_yellow) + ' (Top % Usage)'.pastel(:bright_black),
|
321
305
|
"\n",
|
322
306
|
disk_list.each { |x| x.prepend(' ' * 4) }.join("\n")
|
323
307
|
].join
|
@@ -332,7 +316,7 @@ module GreenHat
|
|
332
316
|
|
333
317
|
# Helper to Make Cyan Titles
|
334
318
|
def title(name, color = :cyan, ljust = 12)
|
335
|
-
" #{name}:".ljust(ljust).
|
319
|
+
" #{name}:".ljust(ljust).pastel(color)
|
336
320
|
end
|
337
321
|
end
|
338
322
|
# rubocop:enable Metrics/ClassLength
|
@@ -2,94 +2,8 @@ module GreenHat
|
|
2
2
|
# Common Helpers
|
3
3
|
# rubocop:disable Metrics/ModuleLength
|
4
4
|
module ShellHelper
|
5
|
-
# Generic Parameter Parsing
|
6
|
-
def self.param_parse(params, skip_args = [])
|
7
|
-
# Turn Params into Hash
|
8
|
-
opts = params.flat_map { |param| param_opt_scan(param) }
|
9
|
-
|
10
|
-
# Collect and Remove Args
|
11
|
-
args = params.each_with_object({}) { |param, obj| param_arg_scan(param, obj) }
|
12
|
-
|
13
|
-
# Move Special Arguments
|
14
|
-
opts.reject! do |opt|
|
15
|
-
if param_special_opts.include? opt.field
|
16
|
-
args[opt.field] = opt.value.split(',').map(&:to_sym)
|
17
|
-
true
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
# Remove Opts and Args
|
22
|
-
params.reject! do |param|
|
23
|
-
opt_field_remove?(opts, param) || arg_field_remove?(args, param)
|
24
|
-
end
|
25
|
-
|
26
|
-
# Update other user defaults
|
27
|
-
Settings.default_log_args(args, skip_args)
|
28
|
-
|
29
|
-
[params, opts, args]
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.opt_field_remove?(opts, param)
|
33
|
-
opts.any? do |opt|
|
34
|
-
param.include? "--#{opt.field}#{opt.bang ? '!' : nil}=#{opt.value}"
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.arg_field_remove?(args, param)
|
39
|
-
args.keys.any? { |field| param.include? "--#{field}" }
|
40
|
-
end
|
41
|
-
|
42
|
-
# Def Param Scan (Split -- values into keys)
|
43
|
-
def self.param_opt_scan(param)
|
44
|
-
param.scan(/--([^=]+)=(.*)/).map do |field, value|
|
45
|
-
bang = false
|
46
|
-
if field.include? '!'
|
47
|
-
field.delete!('!')
|
48
|
-
bang = true
|
49
|
-
end
|
50
|
-
|
51
|
-
{ field: field.to_sym, value: value, bang: bang }
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Params that should be set as Args
|
56
|
-
# Comma Delimited
|
57
|
-
# --slice=thing,thing2
|
58
|
-
# --except=time,params
|
59
|
-
# --round=2
|
60
|
-
|
61
|
-
def self.param_special_opts
|
62
|
-
%i[
|
63
|
-
slice except stats uniq pluck round archive start end sort limit truncate page case
|
64
|
-
]
|
65
|
-
end
|
66
|
-
|
67
|
-
# Parameter Extraction
|
68
|
-
# Special Opts: --slice, --except
|
69
|
-
def self.param_arg_scan(param, obj)
|
70
|
-
# TODO: Why is capture group doing two arrays
|
71
|
-
param.scan(/--([^=]+)$/).flatten.each do |field|
|
72
|
-
obj[field.to_sym] = param_arg_defaults(field.to_sym)
|
73
|
-
end
|
74
|
-
|
75
|
-
obj
|
76
|
-
end
|
77
|
-
|
78
|
-
# Arg Defaults
|
79
|
-
def self.param_arg_defaults(field)
|
80
|
-
case field
|
81
|
-
when :round then [2]
|
82
|
-
when :limit then [TTY::Screen.height / 2]
|
83
|
-
when :truncate then [TTY::Screen.width * 4]
|
84
|
-
when :page, :case then [:true]
|
85
|
-
when *param_special_opts then []
|
86
|
-
else
|
87
|
-
true
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
5
|
# Use File Process for Output
|
92
|
-
def self.file_output(files)
|
6
|
+
def self.file_output(files, flags = {})
|
93
7
|
results = file_process(files) do |file|
|
94
8
|
[
|
95
9
|
file.friendly_name,
|
@@ -98,7 +12,7 @@ module GreenHat
|
|
98
12
|
]
|
99
13
|
end
|
100
14
|
|
101
|
-
ShellHelper.show(results.flatten)
|
15
|
+
ShellHelper.show(results.flatten, flags)
|
102
16
|
end
|
103
17
|
|
104
18
|
def self.file_process(files, &block)
|
@@ -119,7 +33,7 @@ module GreenHat
|
|
119
33
|
end
|
120
34
|
|
121
35
|
# Show Data / Auto Paginate Helper
|
122
|
-
def self.show(data,
|
36
|
+
def self.show(data, flags = {})
|
123
37
|
# If Block of String
|
124
38
|
if data.instance_of?(String)
|
125
39
|
TTY::Pager.page data
|
@@ -127,23 +41,24 @@ module GreenHat
|
|
127
41
|
end
|
128
42
|
|
129
43
|
# If raw just print out
|
130
|
-
if
|
44
|
+
if flags[:raw]
|
131
45
|
puts data.join("\n")
|
132
46
|
return true
|
133
47
|
end
|
134
48
|
|
135
49
|
# Check if content needs to paged, or if auto_height is off
|
136
|
-
if Page.skip?(
|
137
|
-
puts data.map { |entry| entry_show(
|
50
|
+
if Page.skip?(flags, data)
|
51
|
+
puts data.map { |entry| entry_show(flags, entry) }.compact.join("\n")
|
138
52
|
return true
|
139
53
|
end
|
140
54
|
|
141
55
|
# Default Pager
|
142
56
|
TTY::Pager.page do |pager|
|
143
57
|
data.each do |entry|
|
144
|
-
output = entry_show(
|
58
|
+
output = entry_show(flags, entry)
|
145
59
|
|
146
|
-
|
60
|
+
# Breaks any intentional spaces
|
61
|
+
# next if output.blank?
|
147
62
|
|
148
63
|
pager.write("\n#{output}") # write line to the pager
|
149
64
|
end
|
@@ -151,12 +66,12 @@ module GreenHat
|
|
151
66
|
end
|
152
67
|
|
153
68
|
# Entry Shower / Top Level
|
154
|
-
def self.entry_show(
|
69
|
+
def self.entry_show(flags, entry, key = nil)
|
155
70
|
LogBot.debug('Entry Show', entry.class) if ENV['DEBUG']
|
156
71
|
case entry
|
157
|
-
when Hash then render_table(entry,
|
72
|
+
when Hash then render_table(entry, flags)
|
158
73
|
when Float, Integer, Array
|
159
|
-
format_table_entry(
|
74
|
+
format_table_entry(flags, entry, key)
|
160
75
|
# Ignore Special Formatting for Strings / Usually already formatted
|
161
76
|
when String
|
162
77
|
entry
|
@@ -167,39 +82,42 @@ module GreenHat
|
|
167
82
|
end
|
168
83
|
|
169
84
|
# Format Table Entries
|
170
|
-
def self.format_table_entry(
|
85
|
+
def self.format_table_entry(flags, entry, key = nil)
|
171
86
|
formatted_entry = case entry
|
172
87
|
# Rounding
|
173
88
|
when Float, Integer || entry.numeric?
|
174
|
-
|
89
|
+
flags.key?(:round) ? entry.to_f.round(flags.round).ai : entry.ai
|
175
90
|
|
176
91
|
# General Inspecting
|
177
92
|
when Hash then entry.ai(ruby19_syntax: true)
|
178
93
|
|
179
94
|
# Arrays often contain Hashes. Dangerous Recursive?
|
180
95
|
when Array
|
181
|
-
entry.map { |x| format_table_entry(
|
96
|
+
entry.map { |x| format_table_entry(flags, x) }.join("\n")
|
182
97
|
|
183
98
|
when Time
|
184
|
-
entry.to_s.
|
99
|
+
entry.to_s.pastel(:bright_white)
|
185
100
|
|
186
101
|
# Default String Formatting
|
187
102
|
else
|
188
103
|
StringColor.do(key, entry)
|
189
104
|
end
|
190
105
|
|
191
|
-
if
|
192
|
-
entry_truncate(formatted_entry,
|
106
|
+
if flags[:truncate]
|
107
|
+
entry_truncate(formatted_entry, flags[:truncate])
|
193
108
|
else
|
194
109
|
formatted_entry
|
195
110
|
end
|
196
111
|
rescue StandardError => e
|
197
|
-
|
112
|
+
if ENV['DEBUG']
|
113
|
+
LogBot.warn('Table Format Entry', message: e.message)
|
114
|
+
ap e.backtrace
|
115
|
+
end
|
198
116
|
end
|
199
117
|
|
200
118
|
# Print the Table in a Nice way
|
201
|
-
def self.render_table(entry,
|
202
|
-
entry = entry.map { |k, v| [k, format_table_entry(
|
119
|
+
def self.render_table(entry, flags)
|
120
|
+
entry = entry.map { |k, v| [k, format_table_entry(flags, v, k)] }.to_h
|
203
121
|
# Pre-format Entry
|
204
122
|
|
205
123
|
table = TTY::Table.new(header: entry.keys, rows: [entry], orientation: :vertical)
|
@@ -207,10 +125,6 @@ module GreenHat
|
|
207
125
|
LogBot.debug('Rendering Entries') if ENV['DEBUG']
|
208
126
|
table.render(:unicode, padding: [0, 1, 0, 1], multiline: true) do |renderer|
|
209
127
|
renderer.border.style = :cyan
|
210
|
-
|
211
|
-
# renderer.filter = lambda do |val, _row_index, col_index|
|
212
|
-
# render_table_entry(val, col_index, args)
|
213
|
-
# end
|
214
128
|
end
|
215
129
|
|
216
130
|
# LogBot.debug('Finish Render Table') if ENV['DEBUG']
|
@@ -223,33 +137,41 @@ module GreenHat
|
|
223
137
|
|
224
138
|
[
|
225
139
|
entry.ai,
|
226
|
-
('_' * (TTY::Screen.width / 3)).
|
140
|
+
('_' * (TTY::Screen.width / 3)).pastel(:cyan),
|
227
141
|
"\n"
|
228
142
|
].join("\n")
|
229
143
|
end
|
230
144
|
|
231
|
-
def self.render_table_entry(val, col_index,
|
145
|
+
def self.render_table_entry(val, col_index, flags)
|
232
146
|
return val.to_s unless col_index == 1
|
233
147
|
|
234
|
-
format_table_entry(
|
148
|
+
format_table_entry(flags, val)
|
235
149
|
end
|
236
150
|
|
237
151
|
# Main Entry Point for Filtering
|
238
|
-
def self.filter_start(
|
152
|
+
def self.filter_start(files, flags, args)
|
239
153
|
# Convert to Things
|
240
|
-
logs = ShellHelper.find_things(
|
154
|
+
logs = ShellHelper.find_things(files, flags).select(&:processed?)
|
241
155
|
|
242
156
|
# Ignore Archive/Host Dividers
|
243
|
-
if
|
157
|
+
if flags[:combine]
|
244
158
|
results = logs.reject(&:blank?).map(&:data).flatten.compact
|
245
|
-
ShellHelper.filter(results,
|
159
|
+
ShellHelper.filter(results, flags, args)
|
246
160
|
else
|
247
161
|
# Iterate and Preserve Archive/Host Index
|
248
162
|
logs.each_with_object({}) do |log, obj|
|
249
163
|
# Ignore Empty Results / No Thing
|
250
164
|
next if log&.blank?
|
251
165
|
|
252
|
-
|
166
|
+
# Include Total Count in Name
|
167
|
+
results = ShellHelper.filter(log.data, flags, args)
|
168
|
+
title = [
|
169
|
+
log.friendly_name,
|
170
|
+
" #{results.count}".pastel(:bright_black)
|
171
|
+
]
|
172
|
+
|
173
|
+
# Save unless empty
|
174
|
+
obj[title.join] = results unless results.count.zero?
|
253
175
|
|
254
176
|
obj
|
255
177
|
end
|
@@ -259,50 +181,55 @@ module GreenHat
|
|
259
181
|
# Filter Logic
|
260
182
|
# TODO: Simplify
|
261
183
|
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
262
|
-
def self.filter(data,
|
184
|
+
def self.filter(data, flags = {}, args = {})
|
263
185
|
results = data.clone.flatten.compact
|
264
186
|
results.select! do |row|
|
265
|
-
|
266
|
-
filter_row_key(row,
|
187
|
+
args.send(flags.logic) do |arg|
|
188
|
+
filter_row_key(row, arg, flags)
|
267
189
|
end
|
268
190
|
end
|
269
191
|
|
192
|
+
# Ensure presecense of a specific field
|
193
|
+
results = filter_exists(results, flags[:exists]) if flags.key?(:exists)
|
194
|
+
|
270
195
|
# Time Filtering
|
271
|
-
results = filter_time(results,
|
196
|
+
results = filter_time(results, flags) if flags.key?(:start) || flags.key?(:end)
|
272
197
|
|
273
198
|
# Strip Results if Slice is defined
|
274
|
-
results = filter_slice(results,
|
199
|
+
results = filter_slice(results, flags[:slice]) if flags.key?(:slice)
|
275
200
|
|
276
201
|
# Strip Results if Except is defined
|
277
|
-
results = filter_except(results,
|
202
|
+
results = filter_except(results, flags[:except]) if flags.key?(:except)
|
278
203
|
|
279
204
|
# Remove Blank from either slice or except
|
280
205
|
results.reject!(&:empty?)
|
281
206
|
|
282
207
|
# Sort
|
283
|
-
results.sort_by! { |x| x.slice(*
|
208
|
+
results.sort_by! { |x| x.slice(*flags[:sort]).values } if flags.key?(:sort)
|
284
209
|
|
285
210
|
# JSON Formatting
|
286
|
-
results = results.map { |x| Oj.dump(x) } if
|
211
|
+
results = results.map { |x| Oj.dump(x) } if flags.key?(:json)
|
287
212
|
|
288
213
|
# Show Unique Only
|
289
|
-
results = filter_uniq(results,
|
214
|
+
results = filter_uniq(results, flags[:uniq]) if flags.key?(:uniq)
|
290
215
|
|
291
216
|
# Reverse
|
292
|
-
results.reverse! if
|
217
|
+
results.reverse! if flags[:reverse]
|
293
218
|
|
294
219
|
# Count occurrences / Skip Results
|
295
|
-
return filter_stats(results,
|
220
|
+
return filter_stats(results, flags[:stats]) if flags.key?(:stats)
|
296
221
|
|
297
222
|
# Pluck
|
298
|
-
results = filter_pluck(results,
|
223
|
+
results = filter_pluck(results, flags[:pluck]) if flags.key?(:pluck)
|
299
224
|
|
300
|
-
#
|
301
|
-
|
225
|
+
# Limit / Ensure Exists and Valid Number
|
226
|
+
if flags.key?(:limit) && flags[:limit]
|
227
|
+
# Old
|
228
|
+
# results[0..flags[:limit].map(&:to_s).join.to_i - 1]
|
229
|
+
|
230
|
+
# New
|
231
|
+
results.shift flags[:limit]
|
302
232
|
|
303
|
-
# Limit
|
304
|
-
if args[:limit]
|
305
|
-
results[0..args[:limit].map(&:to_s).join.to_i - 1]
|
306
233
|
else
|
307
234
|
results
|
308
235
|
end
|
@@ -312,10 +239,10 @@ module GreenHat
|
|
312
239
|
# Filter Start and End Times
|
313
240
|
# rubocop:disable Metrics/MethodLength
|
314
241
|
# TODO: This is a bit icky, simplify/dry
|
315
|
-
def self.filter_time(results,
|
316
|
-
if
|
242
|
+
def self.filter_time(results, flags)
|
243
|
+
if flags.key?(:start)
|
317
244
|
begin
|
318
|
-
time_start = Time.parse(
|
245
|
+
time_start = Time.parse(flags[:start])
|
319
246
|
|
320
247
|
results.select! do |x|
|
321
248
|
if x.time
|
@@ -325,13 +252,13 @@ module GreenHat
|
|
325
252
|
end
|
326
253
|
end
|
327
254
|
rescue StandardError
|
328
|
-
puts 'Unable to Process Start Time Filter'.
|
255
|
+
puts 'Unable to Process Start Time Filter'.pastel(:red)
|
329
256
|
end
|
330
257
|
end
|
331
258
|
|
332
|
-
if
|
259
|
+
if flags.key?(:end)
|
333
260
|
begin
|
334
|
-
time_start = Time.parse(
|
261
|
+
time_start = Time.parse(flags[:end])
|
335
262
|
|
336
263
|
results.select! do |x|
|
337
264
|
if x.time
|
@@ -341,7 +268,7 @@ module GreenHat
|
|
341
268
|
end
|
342
269
|
end
|
343
270
|
rescue StandardError
|
344
|
-
puts 'Unable to Process End Time Filter'.
|
271
|
+
puts 'Unable to Process End Time Filter'.pastel(:red)
|
345
272
|
end
|
346
273
|
end
|
347
274
|
|
@@ -351,14 +278,24 @@ module GreenHat
|
|
351
278
|
|
352
279
|
def self.filter_except(results, except)
|
353
280
|
# Avoid Empty Results
|
354
|
-
if
|
355
|
-
filter_empty_arg('
|
281
|
+
if except.empty?
|
282
|
+
filter_empty_arg('except')
|
356
283
|
return results
|
357
284
|
end
|
358
285
|
|
359
286
|
results.map { |row| row.except(*except) }
|
360
287
|
end
|
361
288
|
|
289
|
+
def self.filter_exists(results, exists)
|
290
|
+
# Avoid Empty Results
|
291
|
+
if exists.empty?
|
292
|
+
filter_empty_arg('exists')
|
293
|
+
return results
|
294
|
+
end
|
295
|
+
|
296
|
+
results.select { |row| (exists - row.keys).empty? }
|
297
|
+
end
|
298
|
+
|
362
299
|
def self.entry_truncate(entry, truncate)
|
363
300
|
# Ignore if Truncation Off
|
364
301
|
return entry if truncate.zero?
|
@@ -367,7 +304,7 @@ module GreenHat
|
|
367
304
|
return entry unless entry.instance_of?(String) && entry.size > truncate
|
368
305
|
|
369
306
|
# Include '...' to indicate truncation
|
370
|
-
"#{entry.to_s[0..truncate]} #{'...'.
|
307
|
+
"#{entry.to_s[0..truncate]} #{'...'.pastel(:bright_blue)}"
|
371
308
|
end
|
372
309
|
|
373
310
|
def self.filter_slice(results, slice)
|
@@ -399,7 +336,7 @@ module GreenHat
|
|
399
336
|
|
400
337
|
unique.map do |field|
|
401
338
|
results.uniq { |x| x[field] }
|
402
|
-
end.
|
339
|
+
end.inject(:&)
|
403
340
|
end
|
404
341
|
|
405
342
|
def self.filter_stats(results, stats)
|
@@ -409,18 +346,45 @@ module GreenHat
|
|
409
346
|
return results
|
410
347
|
end
|
411
348
|
|
412
|
-
|
413
|
-
|
414
|
-
occurrences
|
349
|
+
# Loop through Stats, Separate Hash/Tables
|
350
|
+
stats.map do |field|
|
351
|
+
occurrences = filter_count_occurrences(results, field)
|
352
|
+
|
353
|
+
# Total Occurences
|
354
|
+
total = occurrences.values.sum
|
355
|
+
|
356
|
+
# Percs
|
357
|
+
occurrences.transform_values! do |count|
|
358
|
+
[
|
359
|
+
count,
|
360
|
+
" #{percent(count, total)}%".pastel(:bright_black)
|
361
|
+
]
|
362
|
+
end
|
363
|
+
|
364
|
+
# Sort by total occurances / New Variable for Total
|
365
|
+
output = occurrences.sort_by(&:last).to_h.transform_values!(&:join).to_a
|
366
|
+
|
367
|
+
# Append Header / Total with field name
|
368
|
+
output.unshift([field.to_s.pastel(:bright_black), total])
|
369
|
+
|
370
|
+
# Format
|
371
|
+
output.to_h
|
415
372
|
end
|
416
373
|
end
|
417
374
|
|
375
|
+
# Percent Helper
|
376
|
+
def self.percent(value, total)
|
377
|
+
((value / total.to_f) * 100).round
|
378
|
+
end
|
379
|
+
|
418
380
|
# Helper to Count occurrences
|
419
|
-
def self.filter_count_occurrences(results,
|
381
|
+
def self.filter_count_occurrences(results, field)
|
420
382
|
results.each_with_object(Hash.new(0)) do |entry, counts|
|
421
|
-
|
422
|
-
|
423
|
-
|
383
|
+
if entry.key? field
|
384
|
+
counts[entry[field]] += 1
|
385
|
+
else
|
386
|
+
counts['None'.pastel(:bright_black)] += 1
|
387
|
+
end
|
424
388
|
|
425
389
|
counts
|
426
390
|
end
|
@@ -428,43 +392,67 @@ module GreenHat
|
|
428
392
|
|
429
393
|
def self.filter_empty_arg(arg)
|
430
394
|
puts [
|
431
|
-
'Ignoring'.
|
432
|
-
"--#{arg}".
|
433
|
-
'it requires an argument'.
|
395
|
+
'Ignoring'.pastel(:bright_yellow),
|
396
|
+
"--#{arg}".pastel(:cyan),
|
397
|
+
'it requires an argument'.pastel(:red)
|
434
398
|
].join(' ')
|
435
399
|
end
|
436
400
|
|
437
401
|
# Break out filter row logic into separate method
|
438
|
-
def self.filter_row_key(row, param, args)
|
439
|
-
# Ignore Other Logic if Field isn't even included
|
440
|
-
return false unless row.key? param.field
|
441
402
|
|
442
|
-
|
443
|
-
|
444
|
-
|
403
|
+
def self.filter_row_key(row, arg, flags)
|
404
|
+
# Ignore Other Logic if Field isn't even included / Full Text Searching
|
405
|
+
return false unless row.key?(arg[:field]) || arg[:field] == :text
|
406
|
+
|
407
|
+
# Sensitivity Check / Check for Match / Full Text Searching
|
408
|
+
included = if arg[:field] == :text
|
409
|
+
filter_row_entry(row.to_s, arg, flags)
|
445
410
|
else
|
446
|
-
row[
|
411
|
+
filter_row_entry(row[arg.field].to_s, arg, flags)
|
447
412
|
end
|
448
413
|
|
449
414
|
# Pivot of off include vs exclude
|
450
|
-
if
|
415
|
+
if arg.bang
|
451
416
|
!included
|
452
417
|
else
|
453
418
|
included
|
454
419
|
end
|
455
420
|
end
|
456
421
|
|
422
|
+
# Field Partial / Case / Exact search
|
423
|
+
def self.filter_row_entry(entry, arg, flags)
|
424
|
+
# Exact Matching / Unless doing full text search
|
425
|
+
return entry.to_s == arg.value.to_s if flags.key?(:exact) && arg.field != :text
|
426
|
+
|
427
|
+
if flags.key?(:case)
|
428
|
+
entry.include? arg.value.to_s
|
429
|
+
else
|
430
|
+
entry.downcase.include? arg.value.to_s.downcase
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
457
434
|
# Total Count Helper
|
458
435
|
def self.total_count(results)
|
459
436
|
results.each do |k, v|
|
460
437
|
puts k
|
461
|
-
puts "Total: #{v.count.to_s.
|
438
|
+
puts "Total: #{v.count.to_s.pastel(:blue)}"
|
462
439
|
puts
|
463
440
|
end
|
464
441
|
end
|
465
442
|
|
443
|
+
# Unified Files Interface
|
444
|
+
def self.files(file_list, base_list = nil, flags = {})
|
445
|
+
base_list ||= Thing.all
|
446
|
+
|
447
|
+
# Prepare Log List
|
448
|
+
file_list = prepare_list(file_list, base_list)
|
449
|
+
|
450
|
+
# Convert to Things
|
451
|
+
find_things(file_list, flags)
|
452
|
+
end
|
453
|
+
|
466
454
|
# Total Log List Manipulator
|
467
|
-
def self.prepare_list(log_list, base_list = nil)
|
455
|
+
def self.prepare_list(log_list, base_list = nil, _flags = {})
|
468
456
|
base_list ||= GreenHat::ShellHelper::Log.list
|
469
457
|
|
470
458
|
# Assume all
|
@@ -476,48 +464,62 @@ module GreenHat
|
|
476
464
|
log_list
|
477
465
|
end
|
478
466
|
|
467
|
+
# Fuzzy match for things
|
468
|
+
def self.thing_list
|
469
|
+
@thing_list ||= Thing.all.map(&:name)
|
470
|
+
|
471
|
+
@thing_list
|
472
|
+
end
|
473
|
+
|
479
474
|
# Shortcut find things
|
480
|
-
def self.find_things(
|
481
|
-
things =
|
482
|
-
Thing
|
483
|
-
|
475
|
+
def self.find_things(files, flags = {})
|
476
|
+
things = files.uniq.flat_map do |file|
|
477
|
+
# If Thing, Return Thing
|
478
|
+
return file if file.instance_of?(Thing)
|
479
|
+
|
480
|
+
if flags.fuzzy_file_match
|
481
|
+
Thing.all.select { |x| x.name.include? file }
|
482
|
+
else
|
483
|
+
Thing.where name: file
|
484
|
+
end
|
485
|
+
end.uniq
|
484
486
|
|
485
487
|
# Host / Archive
|
486
|
-
things.select! { |x| x.archive?
|
488
|
+
things.select! { |x| x.archive? flags.archive } if flags.key?(:archive)
|
487
489
|
|
488
490
|
things
|
489
491
|
end
|
490
492
|
|
491
493
|
# Main Entry Point for Searching
|
492
|
-
def self.search_start(log_list, filter_type, args, opts)
|
494
|
+
# def self.search_start(log_list, filter_type, args, opts)
|
495
|
+
def self.search_start(files, flags, args)
|
493
496
|
# Convert to Things
|
494
|
-
logs = ShellHelper.find_things(
|
497
|
+
logs = ShellHelper.find_things(files, flags)
|
495
498
|
|
496
499
|
logs.each_with_object({}) do |log, obj|
|
497
500
|
# Ignore Empty Results / No Thing
|
498
501
|
next if log&.data.blank?
|
499
502
|
|
500
|
-
obj[log.friendly_name] = ShellHelper.search(log.data,
|
503
|
+
obj[log.friendly_name] = ShellHelper.search(log.data, flags, args)
|
501
504
|
|
502
505
|
obj
|
503
506
|
end
|
504
507
|
end
|
505
508
|
|
506
509
|
# Generic Search Helper / String/Regex
|
507
|
-
def self.search(data,
|
510
|
+
def self.search(data, flags = {}, args = {})
|
508
511
|
results = data.clone.flatten.compact
|
509
|
-
|
510
512
|
results.select! do |row|
|
511
|
-
|
512
|
-
search_row(row,
|
513
|
+
args.send(flags.logic) do |arg|
|
514
|
+
search_row(row, arg, flags)
|
513
515
|
end
|
514
516
|
end
|
515
517
|
|
516
518
|
# Strip Results if Slice is defined
|
517
|
-
results.map! { |row| row.slice(*
|
519
|
+
results.map! { |row| row.slice(*flags[:slice]) } if flags[:slice]
|
518
520
|
|
519
521
|
# Strip Results if Except is defined
|
520
|
-
results.map! { |row| row.except(*
|
522
|
+
results.map! { |row| row.except(*flags[:except]) } if flags[:except]
|
521
523
|
|
522
524
|
# Remove Blank from either slice or except
|
523
525
|
results.reject!(&:empty?)
|
@@ -526,21 +528,23 @@ module GreenHat
|
|
526
528
|
end
|
527
529
|
|
528
530
|
# Break out filter row logic into separate method
|
529
|
-
def self.search_row(row,
|
530
|
-
#
|
531
|
-
included = row.to_s
|
531
|
+
def self.search_row(row, arg, flags)
|
532
|
+
# Sensitivity Check / Check for Match
|
533
|
+
included = filter_row_entry(row.to_s, arg, flags)
|
532
534
|
|
533
|
-
|
535
|
+
# Pivot of off include vs exclude
|
536
|
+
if arg.bang
|
534
537
|
!included
|
535
538
|
else
|
536
539
|
included
|
537
540
|
end
|
538
541
|
end
|
539
542
|
|
543
|
+
# TODO: Remove?
|
540
544
|
# Color Reader Helper
|
541
|
-
def self.pastel
|
542
|
-
|
543
|
-
end
|
545
|
+
# def self.pastel
|
546
|
+
# @pastel ||= Pastel.new
|
547
|
+
# end
|
544
548
|
|
545
549
|
# Number Helper
|
546
550
|
# https://gitlab.com/zedtux/human_size_to_number/-/blob/master/lib/human_size_to_number/helper.rb
|
@@ -579,6 +583,19 @@ module GreenHat
|
|
579
583
|
|
580
584
|
result
|
581
585
|
end
|
586
|
+
|
587
|
+
# General Helper for `show`
|
588
|
+
def self.common_opts
|
589
|
+
puts 'Common Options'.pastel(:blue)
|
590
|
+
puts ' --raw'.pastel(:green)
|
591
|
+
puts ' Do not use less/paging'
|
592
|
+
puts
|
593
|
+
|
594
|
+
puts ' --archive'.pastel(:green)
|
595
|
+
puts ' Limit to specific archive name (inclusive). Matching SOS tar.gz name'
|
596
|
+
puts ' Ex: --archive=dev-gitlab_20210622154626, --archive=202106,202107'
|
597
|
+
puts
|
598
|
+
end
|
582
599
|
end
|
583
600
|
# rubocop:enable Metrics/ModuleLength
|
584
601
|
end
|