greenhat 0.2.0 → 0.3.3
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 +147 -58
- 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 +103 -56
- data/lib/greenhat/shell/field_helper.rb +75 -0
- data/lib/greenhat/shell/filter_help.rb +162 -0
- data/lib/greenhat/shell/gitlab.rb +61 -2
- data/lib/greenhat/shell/help.rb +98 -15
- data/lib/greenhat/shell/list.rb +46 -0
- data/lib/greenhat/shell/log.rb +118 -104
- data/lib/greenhat/shell/page.rb +11 -5
- data/lib/greenhat/shell/process.rb +29 -17
- data/lib/greenhat/shell/report.rb +37 -47
- data/lib/greenhat/shell/shell_helper.rb +661 -0
- data/lib/greenhat/shell.rb +23 -9
- data/lib/greenhat/thing/file_types.rb +31 -5
- data/lib/greenhat/thing/formatters/json_shellwords.rb +0 -3
- data/lib/greenhat/thing/formatters/nginx.rb +44 -0
- 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 +25 -3
- data/lib/greenhat/tty/columns.rb +4 -0
- data/lib/greenhat/version.rb +1 -1
- data/lib/greenhat.rb +15 -14
- metadata +38 -18
- data/lib/greenhat/shell/filter.rb +0 -128
- data/lib/greenhat/shell/helper.rb +0 -584
@@ -0,0 +1,661 @@
|
|
1
|
+
module GreenHat
|
2
|
+
# Common Helpers
|
3
|
+
# rubocop:disable Metrics/ModuleLength
|
4
|
+
module ShellHelper
|
5
|
+
# Use File Process for Output
|
6
|
+
def self.file_output(files, flags = {})
|
7
|
+
results = file_process(files) do |file|
|
8
|
+
[
|
9
|
+
file.friendly_name,
|
10
|
+
file.output(false),
|
11
|
+
"\n"
|
12
|
+
]
|
13
|
+
end
|
14
|
+
|
15
|
+
ShellHelper.show(results.flatten, flags)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.file_process(files, &block)
|
19
|
+
files.map do |file|
|
20
|
+
next if file.output(false).empty?
|
21
|
+
|
22
|
+
block.call(file)
|
23
|
+
end.flatten
|
24
|
+
end
|
25
|
+
|
26
|
+
# Pagination Helper
|
27
|
+
def self.page(data)
|
28
|
+
TTY::Pager.page do |pager|
|
29
|
+
data.flatten.each do |output|
|
30
|
+
pager.write("\n#{output}") # write line to the pager
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Show Data / Auto Paginate Helper
|
36
|
+
def self.show(data, flags = {})
|
37
|
+
# If Block of String
|
38
|
+
if data.instance_of?(String)
|
39
|
+
TTY::Pager.page data
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
|
43
|
+
# If raw just print out
|
44
|
+
if flags[:raw]
|
45
|
+
puts data.join("\n")
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
|
49
|
+
# Check if content needs to paged, or if auto_height is off
|
50
|
+
if Page.skip?(flags, data)
|
51
|
+
puts data.map { |entry| entry_show(flags, entry) }.compact.join("\n")
|
52
|
+
return true
|
53
|
+
end
|
54
|
+
|
55
|
+
# Default Pager
|
56
|
+
TTY::Pager.page do |pager|
|
57
|
+
data.each do |entry|
|
58
|
+
output = entry_show(flags, entry)
|
59
|
+
|
60
|
+
# Breaks any intentional spaces
|
61
|
+
# next if output.blank?
|
62
|
+
|
63
|
+
pager.write("\n#{output}") # write line to the pager
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Entry Shower / Top Level
|
69
|
+
def self.entry_show(flags, entry, key = nil)
|
70
|
+
LogBot.debug('Entry Show', entry.class) if ENV['DEBUG']
|
71
|
+
case entry
|
72
|
+
when Hash then render_table(entry, flags)
|
73
|
+
when Float, Integer, Array
|
74
|
+
format_table_entry(flags, entry, key)
|
75
|
+
# Ignore Special Formatting for Strings / Usually already formatted
|
76
|
+
when String
|
77
|
+
entry
|
78
|
+
else
|
79
|
+
LogBot.warn('Shell Show', "Unknown #{entry.class}")
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Format Table Entries
|
85
|
+
def self.format_table_entry(flags, entry, key = nil)
|
86
|
+
formatted_entry = case entry
|
87
|
+
# Rounding
|
88
|
+
when Float, Integer || entry.numeric?
|
89
|
+
flags.key?(:round) ? entry.to_f.round(flags.round).ai : entry.ai
|
90
|
+
|
91
|
+
# General Inspecting
|
92
|
+
when Hash then entry.ai(ruby19_syntax: true)
|
93
|
+
|
94
|
+
# Arrays often contain Hashes. Dangerous Recursive?
|
95
|
+
when Array
|
96
|
+
entry.map { |x| format_table_entry(flags, x) }.join("\n")
|
97
|
+
|
98
|
+
when Time
|
99
|
+
entry.to_s.pastel(:bright_white)
|
100
|
+
|
101
|
+
# Default String Formatting
|
102
|
+
else
|
103
|
+
StringColor.do(key, entry)
|
104
|
+
end
|
105
|
+
|
106
|
+
if flags[:truncate]
|
107
|
+
entry_truncate(formatted_entry, flags[:truncate])
|
108
|
+
else
|
109
|
+
formatted_entry
|
110
|
+
end
|
111
|
+
rescue StandardError => e
|
112
|
+
if ENV['DEBUG']
|
113
|
+
LogBot.warn('Table Format Entry', message: e.message)
|
114
|
+
ap e.backtrace
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Print the Table in a Nice way
|
119
|
+
def self.render_table(entry, flags)
|
120
|
+
entry = entry.map { |k, v| [k, format_table_entry(flags, v, k)] }.to_h
|
121
|
+
# Pre-format Entry
|
122
|
+
|
123
|
+
table_style = flags[:table_style]&.to_sym || :unicode
|
124
|
+
|
125
|
+
table = TTY::Table.new(header: entry.keys, rows: [entry], orientation: :vertical)
|
126
|
+
|
127
|
+
LogBot.debug('Rendering Entries') if ENV['DEBUG']
|
128
|
+
table.render(table_style, padding: [0, 1, 0, 1], multiline: true) do |renderer|
|
129
|
+
renderer.border.style = :cyan
|
130
|
+
end
|
131
|
+
|
132
|
+
# LogBot.debug('Finish Render Table') if ENV['DEBUG']
|
133
|
+
# Fall Back to Amazing Inspect
|
134
|
+
rescue StandardError => e
|
135
|
+
if ENV['DEBUG']
|
136
|
+
LogBot.warn('Table', message: e.message)
|
137
|
+
ap e.backtrace
|
138
|
+
end
|
139
|
+
|
140
|
+
[
|
141
|
+
entry.ai,
|
142
|
+
('_' * (TTY::Screen.width / 3)).pastel(:cyan),
|
143
|
+
"\n"
|
144
|
+
].join("\n")
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.render_table_entry(val, col_index, flags)
|
148
|
+
return val.to_s unless col_index == 1
|
149
|
+
|
150
|
+
format_table_entry(flags, val)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Main Entry Point for Filtering
|
154
|
+
def self.filter_start(files, flags, args)
|
155
|
+
# Convert to Things
|
156
|
+
logs = ShellHelper.find_things(files, flags).select(&:processed?)
|
157
|
+
|
158
|
+
# Ignore Archive/Host Dividers
|
159
|
+
if flags[:combine]
|
160
|
+
results = logs.reject(&:blank?).map(&:data).flatten.compact
|
161
|
+
ShellHelper.filter(results, flags, args)
|
162
|
+
else
|
163
|
+
# Iterate and Preserve Archive/Host Index
|
164
|
+
logs.each_with_object({}) do |log, obj|
|
165
|
+
# Ignore Empty Results / No Thing
|
166
|
+
next if log&.blank?
|
167
|
+
|
168
|
+
# Include Total Count in Name
|
169
|
+
results = ShellHelper.filter(log.data, flags, args)
|
170
|
+
title = [
|
171
|
+
log.friendly_name,
|
172
|
+
" #{results.count}".pastel(:bright_black)
|
173
|
+
]
|
174
|
+
|
175
|
+
# Save unless empty
|
176
|
+
obj[title.join] = results unless results.count.zero?
|
177
|
+
|
178
|
+
obj
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Filter Logic
|
184
|
+
# TODO: Simplify
|
185
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
186
|
+
def self.filter(data, flags = {}, args = {})
|
187
|
+
# results = data.clone.flatten.compact
|
188
|
+
|
189
|
+
# Experimenting with deep clone
|
190
|
+
results = Marshal.load(Marshal.dump(data))
|
191
|
+
results.select! do |row|
|
192
|
+
args.send(flags.logic) do |arg|
|
193
|
+
filter_row_key(row, arg, flags)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Ensure presecense of a specific field
|
198
|
+
results = filter_exists(results, flags[:exists]) if flags.key?(:exists)
|
199
|
+
|
200
|
+
# Time Zone
|
201
|
+
results = filter_modify_timezone(results, flags[:time_zone]) if flags.key?(:time_zone)
|
202
|
+
|
203
|
+
# Time Filtering
|
204
|
+
results = filter_time(results, flags) if flags.key?(:start) || flags.key?(:end)
|
205
|
+
|
206
|
+
# Strip Results if Slice is defined
|
207
|
+
results = filter_slice(results, flags[:slice]) if flags.key?(:slice)
|
208
|
+
|
209
|
+
# Strip Results if Except is defined
|
210
|
+
results = filter_except(results, flags[:except]) if flags.key?(:except)
|
211
|
+
|
212
|
+
# Remove Blank from either slice or except
|
213
|
+
results.reject!(&:empty?)
|
214
|
+
|
215
|
+
# Sort
|
216
|
+
results.sort_by! { |x| x.slice(*flags[:sort]).values } if flags.key?(:sort)
|
217
|
+
|
218
|
+
# JSON Formatting
|
219
|
+
results = results.map { |x| Oj.dump(x) } if flags.key?(:json)
|
220
|
+
|
221
|
+
# Show Unique Only
|
222
|
+
results = filter_uniq(results, flags[:uniq]) if flags.key?(:uniq)
|
223
|
+
|
224
|
+
# Reverse
|
225
|
+
results.reverse! if flags[:reverse]
|
226
|
+
|
227
|
+
# Count occurrences / Skip Results
|
228
|
+
return filter_stats(results, flags) if flags.key?(:stats)
|
229
|
+
|
230
|
+
# Limit before Pluck / Flattening
|
231
|
+
results = filter_limit(results, flags[:limit]) if flags.key?(:limit)
|
232
|
+
|
233
|
+
# Pluck
|
234
|
+
results = filter_pluck(results, flags[:pluck]) if flags.key?(:pluck)
|
235
|
+
|
236
|
+
results
|
237
|
+
end
|
238
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
239
|
+
|
240
|
+
# Limit / Ensure Exists and Valid Number
|
241
|
+
def self.filter_limit(results, limit)
|
242
|
+
return results unless limit.integer? && limit.positive?
|
243
|
+
|
244
|
+
results.shift limit
|
245
|
+
end
|
246
|
+
|
247
|
+
def self.filter_modify_timezone(results, time_zone)
|
248
|
+
results.each do |x|
|
249
|
+
next unless x.key? :time
|
250
|
+
|
251
|
+
x[:time] = x[:time].in_time_zone time_zone
|
252
|
+
end
|
253
|
+
|
254
|
+
results
|
255
|
+
end
|
256
|
+
|
257
|
+
# Filter Start and End Times
|
258
|
+
# rubocop:disable Metrics/MethodLength
|
259
|
+
# TODO: This is a bit icky, simplify/dry
|
260
|
+
def self.filter_time(results, flags)
|
261
|
+
if flags.key?(:start)
|
262
|
+
begin
|
263
|
+
time_start = Time.parse(flags[:start])
|
264
|
+
|
265
|
+
results.select! do |x|
|
266
|
+
if x.time
|
267
|
+
time_start < x.time
|
268
|
+
else
|
269
|
+
true
|
270
|
+
end
|
271
|
+
end
|
272
|
+
rescue StandardError
|
273
|
+
puts 'Unable to Process Start Time Filter'.pastel(:red)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
if flags.key?(:end)
|
278
|
+
begin
|
279
|
+
time_start = Time.parse(flags[:end])
|
280
|
+
|
281
|
+
results.select! do |x|
|
282
|
+
if x.time
|
283
|
+
time_start > x.time
|
284
|
+
else
|
285
|
+
true
|
286
|
+
end
|
287
|
+
end
|
288
|
+
rescue StandardError
|
289
|
+
puts 'Unable to Process End Time Filter'.pastel(:red)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
results
|
294
|
+
end
|
295
|
+
# rubocop:enable Metrics/MethodLength
|
296
|
+
|
297
|
+
def self.filter_except(results, except)
|
298
|
+
# Avoid Empty Results
|
299
|
+
if except.empty?
|
300
|
+
filter_empty_arg('except')
|
301
|
+
return results
|
302
|
+
end
|
303
|
+
|
304
|
+
results.map { |row| row.except(*except) }
|
305
|
+
end
|
306
|
+
|
307
|
+
def self.filter_exists(results, exists)
|
308
|
+
# Avoid Empty Results
|
309
|
+
if exists.empty?
|
310
|
+
filter_empty_arg('exists')
|
311
|
+
return results
|
312
|
+
end
|
313
|
+
|
314
|
+
results.select { |row| (exists - row.keys).empty? }
|
315
|
+
end
|
316
|
+
|
317
|
+
def self.entry_truncate(entry, truncate)
|
318
|
+
# Ignore if Truncation Off
|
319
|
+
return entry if truncate.zero?
|
320
|
+
|
321
|
+
# Only truncate large strings
|
322
|
+
return entry unless entry.instance_of?(String) && entry.size > truncate
|
323
|
+
|
324
|
+
# Include '...' to indicate truncation
|
325
|
+
"#{entry.to_s[0..truncate]} #{'...'.pastel(:bright_blue)}"
|
326
|
+
end
|
327
|
+
|
328
|
+
def self.filter_slice(results, slice)
|
329
|
+
# Avoid Empty Results
|
330
|
+
if slice.empty?
|
331
|
+
filter_empty_arg('slice')
|
332
|
+
return results
|
333
|
+
end
|
334
|
+
|
335
|
+
results.map { |row| row.slice(*slice) }
|
336
|
+
end
|
337
|
+
|
338
|
+
def self.filter_pluck(results, pluck)
|
339
|
+
# Avoid Empty Results
|
340
|
+
if pluck.empty?
|
341
|
+
filter_empty_arg('pluck')
|
342
|
+
return results
|
343
|
+
end
|
344
|
+
|
345
|
+
results.map { |x| x.slice(*pluck).values }.flatten
|
346
|
+
end
|
347
|
+
|
348
|
+
def self.filter_uniq(results, unique)
|
349
|
+
# Avoid Empty Results
|
350
|
+
if unique.empty?
|
351
|
+
filter_empty_arg('uniq')
|
352
|
+
return results
|
353
|
+
end
|
354
|
+
|
355
|
+
unique.map do |field|
|
356
|
+
results.uniq { |x| x[field] }
|
357
|
+
end.inject(:&)
|
358
|
+
end
|
359
|
+
|
360
|
+
def self.filter_stats(results, flags)
|
361
|
+
stats = flags[:stats]
|
362
|
+
|
363
|
+
# Avoid Empty Results
|
364
|
+
if stats.empty?
|
365
|
+
filter_empty_arg('stats')
|
366
|
+
return results
|
367
|
+
end
|
368
|
+
|
369
|
+
# Loop through Stats, Separate Hash/Tables
|
370
|
+
stats.map do |field|
|
371
|
+
occurrences = filter_count_occurrences(results, field, flags)
|
372
|
+
|
373
|
+
# Total Occurences
|
374
|
+
total = occurrences.values.sum
|
375
|
+
|
376
|
+
# Percs
|
377
|
+
occurrences.transform_values! do |count|
|
378
|
+
[
|
379
|
+
count,
|
380
|
+
" #{percent(count, total)}%".pastel(:bright_black)
|
381
|
+
]
|
382
|
+
end
|
383
|
+
|
384
|
+
# Sort by total occurances / New Variable for Total
|
385
|
+
output = occurrences.sort_by(&:last).to_h.transform_values!(&:join).to_a
|
386
|
+
|
387
|
+
# Append Header / Total with field name
|
388
|
+
output.unshift([field.to_s.pastel(:bright_black), total])
|
389
|
+
|
390
|
+
# Use Truncate For Long Keys
|
391
|
+
if flags[:truncate]
|
392
|
+
output.map! do |key, value|
|
393
|
+
[key.to_s[0..flags[:truncate]], value]
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
# Format
|
398
|
+
output.to_h
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# Percent Helper
|
403
|
+
def self.percent(value, total)
|
404
|
+
((value / total.to_f) * 100).round
|
405
|
+
end
|
406
|
+
|
407
|
+
# Helper to Count occurrences
|
408
|
+
def self.filter_count_occurrences(results, field, flags = {})
|
409
|
+
results.each_with_object(Hash.new(0)) do |entry, counts|
|
410
|
+
if entry.key? field
|
411
|
+
# Rounding in pagination breaks stats
|
412
|
+
key = if flags.key?(:round) && entry[field].numeric?
|
413
|
+
entry[field].to_f.round(flags.round)
|
414
|
+
else
|
415
|
+
entry[field]
|
416
|
+
end
|
417
|
+
|
418
|
+
counts[key] += 1
|
419
|
+
else
|
420
|
+
counts['None'.pastel(:bright_black)] += 1
|
421
|
+
end
|
422
|
+
|
423
|
+
counts
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def self.filter_empty_arg(arg)
|
428
|
+
puts [
|
429
|
+
'Ignoring'.pastel(:bright_yellow),
|
430
|
+
"--#{arg}".pastel(:cyan),
|
431
|
+
'it requires an argument'.pastel(:red)
|
432
|
+
].join(' ')
|
433
|
+
end
|
434
|
+
|
435
|
+
# Break out filter row logic into separate method
|
436
|
+
|
437
|
+
def self.filter_row_key(row, arg, flags)
|
438
|
+
# Ignore Other Logic if Field isn't even included / Full Text Searching
|
439
|
+
return false unless row.key?(arg[:field]) || arg[:field] == :text
|
440
|
+
|
441
|
+
# Sensitivity Check / Check for Match / Full Text Searching
|
442
|
+
included = if arg[:field] == :text
|
443
|
+
filter_row_entry(row.to_s, arg, flags)
|
444
|
+
else
|
445
|
+
filter_row_entry(row[arg.field].to_s, arg, flags)
|
446
|
+
end
|
447
|
+
|
448
|
+
# Pivot of off include vs exclude
|
449
|
+
if arg.bang
|
450
|
+
!included
|
451
|
+
else
|
452
|
+
included
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
# Field Partial / Case / Exact search
|
457
|
+
def self.filter_row_entry(entry, arg, flags)
|
458
|
+
# Exact Matching / Unless doing full text search
|
459
|
+
return entry.to_s == arg.value.to_s if flags.key?(:exact) && arg.field != :text
|
460
|
+
|
461
|
+
if flags.key?(:case)
|
462
|
+
entry.include? arg.value.to_s
|
463
|
+
else
|
464
|
+
entry.downcase.include? arg.value.to_s.downcase
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
# Total Count Helper
|
469
|
+
def self.total_count(results)
|
470
|
+
results.each do |k, v|
|
471
|
+
puts k
|
472
|
+
puts "Total: #{v.count.to_s.pastel(:blue)}"
|
473
|
+
puts
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
# Total Count Helper
|
478
|
+
def self.fields_print(results)
|
479
|
+
results.each do |k, v|
|
480
|
+
puts k
|
481
|
+
puts field_table(v.map(&:keys).flatten.uniq.sort)
|
482
|
+
puts
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def self.field_table(list, columns = 4)
|
487
|
+
return nil if list.size.zero?
|
488
|
+
|
489
|
+
# Keep Alphabetical Sort
|
490
|
+
groups = list.each_slice((list.size / columns.to_f).round).to_a
|
491
|
+
|
492
|
+
table = TTY::Table.new do |t|
|
493
|
+
loop do
|
494
|
+
break if groups.all?(&:empty?)
|
495
|
+
|
496
|
+
t << groups.map(&:shift)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
table.render(:unicode, padding: [0, 1, 0, 1])
|
501
|
+
end
|
502
|
+
|
503
|
+
# Unified Files Interface
|
504
|
+
def self.files(file_list, base_list = nil, flags = {})
|
505
|
+
base_list ||= Thing.all
|
506
|
+
|
507
|
+
# Prepare Log List
|
508
|
+
file_list = prepare_list(file_list, base_list)
|
509
|
+
|
510
|
+
# Convert to Things
|
511
|
+
find_things(file_list, flags)
|
512
|
+
end
|
513
|
+
|
514
|
+
# Total Log List Manipulator
|
515
|
+
def self.prepare_list(log_list, base_list = nil, _flags = {})
|
516
|
+
base_list ||= GreenHat::ShellHelper::Log.list
|
517
|
+
|
518
|
+
# Assume all
|
519
|
+
log_list.push '*' if log_list.empty?
|
520
|
+
|
521
|
+
# Map for All
|
522
|
+
log_list = base_list.map(&:name) if log_list == ['*']
|
523
|
+
|
524
|
+
log_list
|
525
|
+
end
|
526
|
+
|
527
|
+
# Fuzzy match for things
|
528
|
+
def self.thing_list
|
529
|
+
@thing_list ||= Thing.all.map(&:name)
|
530
|
+
|
531
|
+
@thing_list
|
532
|
+
end
|
533
|
+
|
534
|
+
# Shortcut find things
|
535
|
+
def self.find_things(files, flags = {})
|
536
|
+
things = files.uniq.flat_map do |file|
|
537
|
+
# If Thing, Return Thing
|
538
|
+
return file if file.instance_of?(Thing)
|
539
|
+
|
540
|
+
if flags.fuzzy_file_match
|
541
|
+
Thing.all.select { |x| x.name.include? file }
|
542
|
+
else
|
543
|
+
Thing.where name: file
|
544
|
+
end
|
545
|
+
end.uniq
|
546
|
+
|
547
|
+
# Host / Archive
|
548
|
+
things.select! { |x| x.archive? flags.archive } if flags.key?(:archive)
|
549
|
+
|
550
|
+
things
|
551
|
+
end
|
552
|
+
|
553
|
+
# Main Entry Point for Searching
|
554
|
+
# def self.search_start(log_list, filter_type, args, opts)
|
555
|
+
def self.search_start(files, flags, args)
|
556
|
+
# Convert to Things
|
557
|
+
logs = ShellHelper.find_things(files, flags)
|
558
|
+
|
559
|
+
logs.each_with_object({}) do |log, obj|
|
560
|
+
# Ignore Empty Results / No Thing
|
561
|
+
next if log&.data.blank?
|
562
|
+
|
563
|
+
obj[log.friendly_name] = ShellHelper.search(log.data, flags, args)
|
564
|
+
|
565
|
+
obj
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
# Generic Search Helper / String/Regex
|
570
|
+
def self.search(data, flags = {}, args = {})
|
571
|
+
results = data.clone.flatten.compact
|
572
|
+
results.select! do |row|
|
573
|
+
args.send(flags.logic) do |arg|
|
574
|
+
search_row(row, arg, flags)
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
# Strip Results if Slice is defined
|
579
|
+
results.map! { |row| row.slice(*flags[:slice]) } if flags[:slice]
|
580
|
+
|
581
|
+
# Strip Results if Except is defined
|
582
|
+
results.map! { |row| row.except(*flags[:except]) } if flags[:except]
|
583
|
+
|
584
|
+
# Remove Blank from either slice or except
|
585
|
+
results.reject!(&:empty?)
|
586
|
+
|
587
|
+
results
|
588
|
+
end
|
589
|
+
|
590
|
+
# Break out filter row logic into separate method
|
591
|
+
def self.search_row(row, arg, flags)
|
592
|
+
# Sensitivity Check / Check for Match
|
593
|
+
included = filter_row_entry(row.to_s, arg, flags)
|
594
|
+
|
595
|
+
# Pivot of off include vs exclude
|
596
|
+
if arg.bang
|
597
|
+
!included
|
598
|
+
else
|
599
|
+
included
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
# TODO: Remove?
|
604
|
+
# Color Reader Helper
|
605
|
+
# def self.pastel
|
606
|
+
# @pastel ||= Pastel.new
|
607
|
+
# end
|
608
|
+
|
609
|
+
# Number Helper
|
610
|
+
# https://gitlab.com/zedtux/human_size_to_number/-/blob/master/lib/human_size_to_number/helper.rb
|
611
|
+
def self.human_size_to_number(string)
|
612
|
+
size, unit = string.scan(/(\d*\.?\d+)\s?(Bytes?|KB|MB|GB|TB)/i).first
|
613
|
+
number = size.to_f
|
614
|
+
|
615
|
+
number = case unit.downcase
|
616
|
+
when 'byte', 'bytes'
|
617
|
+
number
|
618
|
+
when 'kb'
|
619
|
+
number * 1024
|
620
|
+
when 'mb'
|
621
|
+
number * 1024 * 1024
|
622
|
+
when 'gb'
|
623
|
+
number * 1024 * 1024 * 1024
|
624
|
+
when 'tb'
|
625
|
+
number * 1024 * 1024 * 1024 * 1024
|
626
|
+
end
|
627
|
+
number.round
|
628
|
+
end
|
629
|
+
|
630
|
+
# TODO: Needed?
|
631
|
+
def self.filter_and(data, params = {})
|
632
|
+
result = data.clone.flatten.compact
|
633
|
+
params.each do |k, v|
|
634
|
+
result.select! do |row|
|
635
|
+
if row.key? k.to_sym
|
636
|
+
row[k.to_sym].include? v
|
637
|
+
else
|
638
|
+
false
|
639
|
+
end
|
640
|
+
end
|
641
|
+
next
|
642
|
+
end
|
643
|
+
|
644
|
+
result
|
645
|
+
end
|
646
|
+
|
647
|
+
# General Helper for `show`
|
648
|
+
def self.common_opts
|
649
|
+
puts 'Common Options'.pastel(:blue)
|
650
|
+
puts ' --raw'.pastel(:green)
|
651
|
+
puts ' Do not use less/paging'
|
652
|
+
puts
|
653
|
+
|
654
|
+
puts ' --archive'.pastel(:green)
|
655
|
+
puts ' Limit to specific archive name (inclusive). Matching SOS tar.gz name'
|
656
|
+
puts ' Ex: --archive=dev-gitlab_20210622154626, --archive=202106,202107'
|
657
|
+
puts
|
658
|
+
end
|
659
|
+
end
|
660
|
+
# rubocop:enable Metrics/ModuleLength
|
661
|
+
end
|