greenhat 0.2.0 → 0.3.0
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 +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
|