greenhat 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf89eef7afeef6861dde013e8cc7992a92d1c9f4dca79ca0a438913f6d9a7a89
4
- data.tar.gz: eed033653f4723f19962f5643ac02c71d9f950f97aa4a68375cc3174152e5513
3
+ metadata.gz: db336e98cd2a446c371d866d068eb9b485f9782b2ea7182f4b4da651011e2083
4
+ data.tar.gz: 90b1858171c14ebe93e3a0ff42dffe2965602835f8f2cc98178f6a81ec816352
5
5
  SHA512:
6
- metadata.gz: 90c1a58ef0334bd864480ad422957758850ecacd39bc85db1fd47233192e09bda768e15331dc179d8860f4842aed58fafb439f4f9499ff22d1c25e3ebf8bff74
7
- data.tar.gz: aea2b938e6b1a5956d7811084b1ab725cf41f2d0766affffed696755ac497cc41a67ebfc562ce516ce448ebb983e6e504a493e772348fd536f1e5c711954f612
6
+ metadata.gz: 94d78c52d3e4b800ac90798d8030dfa9962c7e7c628bd8b636f5471fcf2c35044aa044a91edbd412827db2daf36117ab47cfecc4c6a85e49875e3e6d7756e1d0
7
+ data.tar.gz: fad9f87be4735b5d028ccc7e251fce423de9cf0d440253b65dbaddbc36b268234ffedc7277f9207131692bcf8ea74cd9a4b70c433f55e158b6cd30aae73edf1d
data/lib/greenhat/cli.rb CHANGED
@@ -65,7 +65,9 @@ module GreenHat
65
65
  # Auto Complete
66
66
  # =========================================================
67
67
  def self.auto
68
- word = reader.line.word
68
+ word = @list.last
69
+ # This is being weird with auto complete
70
+ # word = reader.line.word
69
71
 
70
72
  if word.blank?
71
73
  help
@@ -74,6 +76,10 @@ module GreenHat
74
76
  end
75
77
  end
76
78
 
79
+ def self.file_matches(word)
80
+ files.select { |x| x[/^#{Regexp.escape(word)}/] }
81
+ end
82
+
77
83
  def self.auto_match(matches, word)
78
84
  matches.select! { |x| x[/^#{Regexp.escape(word)}/] }
79
85
 
@@ -88,11 +94,14 @@ module GreenHat
88
94
  elsif matches.count > 1
89
95
  puts matches.join("\t").pastel(:bright_green)
90
96
 
91
- # No other Matches
92
- else
93
- file_matches = files.select { |x| x[/^#{Regexp.escape(word)}/] }
94
- auto_files(file_matches, word) unless file_matches.empty?
97
+ # General Filename Matching
98
+ elsif !file_matches(word).empty?
99
+ auto_files(file_matches(word), word)
95
100
 
101
+ # Submodule Autocompletion
102
+ elsif current_methods.include?('auto_complete')
103
+ update_text = current_location.auto_complete(@list, word)
104
+ auto_update(update_text, word) unless update_text.blank?
96
105
  end
97
106
  end
98
107
 
@@ -237,13 +246,17 @@ module GreenHat
237
246
  @list.any? { |x| x.include?(cmd) } && current_methods.include?('default')
238
247
  end
239
248
 
249
+ def self.current_commands
250
+ current_location.methods(false).map(&:to_s).sort - %w[auto_complete]
251
+ end
252
+
240
253
  # General Helper
241
254
  def self.help(long = true)
242
255
  if current_location.methods(false).count.zero?
243
256
  puts 'No Commands'.pastel(:red)
244
257
  else
245
258
  puts 'Commands: '
246
- current_location.methods(false).map(&:to_s).sort.each do |item|
259
+ current_commands.each do |item|
247
260
  next if %w[default help].any? { |x| x == item }
248
261
 
249
262
  puts "=> #{item.to_s.pastel(:blue)}"
@@ -340,6 +353,7 @@ module GreenHat
340
353
  puts
341
354
  puts 'Usage'.pastel(:yellow)
342
355
  puts ' greenhat <sos-archive.tgz> <sos-archive2.tgz> '
356
+ puts ' greenhat <sos-archive.tgz> -q --command=df'
343
357
  puts
344
358
 
345
359
  puts 'Options'.pastel(:yellow)
@@ -70,8 +70,8 @@ module GreenHat
70
70
  # Flags Anything that isn't sent as a key/filter
71
71
  def self.arg_to_flag_list
72
72
  %i[
73
- archive end except exists json limit pluck reverse round slice sort start stats
74
- truncate uniq page
73
+ archive end except exists json limit pluck reverse round slice sort start
74
+ stats truncate uniq page time_zone table_style
75
75
  ]
76
76
  end
77
77
 
@@ -67,8 +67,20 @@ module GreenHat
67
67
  end
68
68
  # rubocop:enable Metrics/MethodLength)
69
69
 
70
+ def self.faststats_installation
71
+ puts "#{'Unable to find'.pastel(:red)} #{'fast-stats'.pastel(:blue)}"
72
+ puts ' Release Downloads here'
73
+ puts ' - https://gitlab.com/gitlab-com/support/toolbox/fast-stats/-/releases'.pastel(:yellow)
74
+ puts ''
75
+ end
76
+
70
77
  # List Files Helpers
71
78
  def self.list(args = [])
79
+ unless TTY::Which.exist? 'fast-stats'
80
+ faststats_installation
81
+ return false
82
+ end
83
+
72
84
  all = false
73
85
  all = true if args.include?('-a') || args.include?('--all')
74
86
 
@@ -96,6 +108,11 @@ module GreenHat
96
108
 
97
109
  # Vanilla Fast Stats
98
110
  def self.default(raw, _other = nil)
111
+ unless TTY::Which.exist? 'fast-stats'
112
+ faststats_installation
113
+ return false
114
+ end
115
+
99
116
  files, flags, cmd = ShellHelper::Faststats.parse(raw)
100
117
 
101
118
  LogBot.debug('FastStats CMD', cmd) if ENV['DEBUG']
@@ -121,6 +138,11 @@ module GreenHat
121
138
  # --sort=count,fail,max,median,min,p95,p99,rps,score
122
139
  # ========================================================================
123
140
  def self.top(raw)
141
+ unless TTY::Which.exist? 'fast-stats'
142
+ faststats_installation
143
+ return false
144
+ end
145
+
124
146
  files, flags, cmd = ShellHelper::Faststats.parse(raw)
125
147
 
126
148
  LogBot.debug('FastStats CMD', cmd) if ENV['DEBUG']
@@ -141,6 +163,11 @@ module GreenHat
141
163
  # ===== [ Fast Stats - Errors ] ====================
142
164
  # ========================================================================
143
165
  def self.errors(raw)
166
+ unless TTY::Which.exist? 'fast-stats'
167
+ faststats_installation
168
+ return false
169
+ end
170
+
144
171
  # Add Color Output
145
172
  raw.push '--color-output'
146
173
  files, flags, cmd = ShellHelper::Faststats.parse(raw)
@@ -0,0 +1,75 @@
1
+ module GreenHat
2
+ # Common Helpers
3
+ module FieldHelper
4
+ def self.fields_find(files, word, flags = {})
5
+ fields = ShellHelper.find_things(files, flags).map(&:fields).flatten.uniq
6
+
7
+ if word.blank?
8
+ puts 'Possible Fields:'.pastel(:bright_blue)
9
+ puts ShellHelper.field_table(fields)
10
+
11
+ return [] # Empty Result
12
+ end
13
+
14
+ list_select(fields, word)
15
+ end
16
+
17
+ def self.list_select(list, word)
18
+ list.select! { |x| x[/^#{Regexp.escape(word)}/] }
19
+ end
20
+
21
+ def self.filter_flags(word)
22
+ if word.blank?
23
+ puts 'Filter Options:'.pastel(:bright_blue)
24
+ puts ShellHelper.field_table(filter_opts, 6)
25
+ puts
26
+
27
+ return []
28
+ end
29
+
30
+ list_select(filter_opts, word)
31
+ end
32
+
33
+ def self.filter_opts
34
+ %w[
35
+ archive case combine end exact except exists json limit or page pluck
36
+ raw reverse round slice sort start stats table_style text time_zone
37
+ total truncate uniq
38
+ ]
39
+ end
40
+
41
+ def self.filter_auto_completes
42
+ %w[
43
+ except exists pluck slice sort stats uniq
44
+ ]
45
+ end
46
+
47
+ def self.field_auto_complete?(word)
48
+ return false if word.blank?
49
+
50
+ filter_auto_completes.include? word.split('=', 2).first
51
+ end
52
+
53
+ def self.field_auto_complete(word, files, flags = {})
54
+ # Prevent weird dupes
55
+ return nil if word[-1] == ','
56
+
57
+ # Command Manipulation
58
+ cmd, fields = word.split('=', 2)
59
+ complete = fields.split(',')[0..-2]
60
+ auto = fields.split(',').last
61
+
62
+ # Field Finder
63
+ matches = fields_find(files, auto, flags)
64
+
65
+ if matches.count == 1
66
+ "--#{cmd}=#{(complete + matches).join(',')}"
67
+ elsif matches.count > 1
68
+ puts "#{'Field Options:'.pastel(:bright_blue)} #{matches.join(' ').pastel(:bright_green)}"
69
+
70
+ list = [Cli.common_substr(matches.map(&:to_s))]
71
+ "--#{cmd}=#{(complete + list).join(',')}"
72
+ end
73
+ end
74
+ end
75
+ end
@@ -11,7 +11,7 @@ module GreenHat
11
11
 
12
12
  puts 'Options'.pastel(:blue)
13
13
  puts '--raw'.pastel(:green)
14
- puts ' Do not use less/paging'
14
+ puts ' Disable formatting and page/less'
15
15
  puts
16
16
 
17
17
  puts '--page'.pastel(:green)
@@ -41,6 +41,10 @@ module GreenHat
41
41
  puts ' Print only total count of matching entries'
42
42
  puts
43
43
 
44
+ puts '--fields'.pastel(:green)
45
+ puts ' Print only Available fields for selected files'
46
+ puts
47
+
44
48
  puts '--slice'.pastel(:green)
45
49
  puts ' Extract specific fields from entries (slice multiple with comma)'
46
50
  puts ' Ex: --slice=path or --slice=path,params'
@@ -57,7 +61,7 @@ module GreenHat
57
61
  puts
58
62
 
59
63
  puts '--stats'.pastel(:green)
60
- puts ' Order/Count occurrances by field'
64
+ puts ' Order/Count occurrances by field. Combine with `truncate` for key names'
61
65
  puts ' Ex: --stats=params --except=params,path'
62
66
  puts
63
67
 
@@ -113,6 +117,21 @@ module GreenHat
113
117
  puts ' Ex: log filter --end="2021-06-22"'
114
118
  puts
115
119
 
120
+ puts '--time_zone'.pastel(:green)
121
+ puts ' Manipulate the `time` field into a specific timezone'
122
+ puts ' Ex: log filter --time_zone=EDT'
123
+ puts
124
+
125
+ puts '--text'.pastel(:green)
126
+ puts ' Full entry text searching (slow)'
127
+ puts ' --text="anything here"'
128
+ puts
129
+
130
+ puts '--table_style'.pastel(:green)
131
+ puts ' Renderer used for formatted output. basic, ascii, or unicode(default)'
132
+ puts ' Ex: log filter --table_style=base'
133
+ puts
134
+
116
135
  puts '--truncate'.pastel(:green)
117
136
  puts ' Truncate field length. On by default (4 rows). Performance issues!'
118
137
  puts ' Disable with --truncate=0'.pastel(:bright_red)
@@ -3,6 +3,41 @@ module GreenHat
3
3
  module Shell
4
4
  # Logs
5
5
  module Log
6
+ def self.auto_complete(list, word)
7
+ # Argument Parsing
8
+ files, flags, _args = Args.parse(list)
9
+
10
+ # Don't try to autocomplete anything else
11
+ return nil unless word =~ /^-/
12
+
13
+ # Clean Up
14
+ word.delete!('-')
15
+ matches = FieldHelper.filter_flags(word)
16
+
17
+ if matches.count == 1
18
+ "--#{matches.first}"
19
+ elsif matches.count > 1
20
+ puts "#{'Filter Options:'.pastel(:bright_blue)} #{matches.join(' ').pastel(:bright_green)}"
21
+ "--#{Cli.common_substr(matches)}"
22
+ # -----------------------------------
23
+ # TODO: Fix Icky Double Nesting
24
+ elsif files.count.nonzero?
25
+ matches = FieldHelper.fields_find(files, word, flags)
26
+
27
+ return nil if matches.nil?
28
+
29
+ if matches.count == 1
30
+ "--#{matches.first}"
31
+ elsif matches.count > 1
32
+ puts "#{'Field Options:'.pastel(:bright_blue)} #{matches.join(' ').pastel(:bright_green)}"
33
+ "--#{Cli.common_substr(matches.map(&:to_s))}"
34
+ elsif FieldHelper.field_auto_complete?(word)
35
+ FieldHelper.field_auto_complete(word, files, flags)
36
+ end
37
+ # -----------------------------------
38
+ end
39
+ end
40
+
6
41
  def self.help
7
42
  puts "\u2500".pastel(:cyan) * 20
8
43
  puts "#{'Logs'.pastel(:yellow)} find stuff"
@@ -73,6 +108,12 @@ module GreenHat
73
108
  return true
74
109
  end
75
110
 
111
+ # Skip and Print Total if set
112
+ if flags[:fields]
113
+ ShellHelper.fields_print(results)
114
+ return true
115
+ end
116
+
76
117
  # Check Search Results
77
118
  if results.instance_of?(Hash) && results.values.flatten.empty?
78
119
  puts 'No results'.pastel(:red)
@@ -120,10 +120,12 @@ module GreenHat
120
120
  entry = entry.map { |k, v| [k, format_table_entry(flags, v, k)] }.to_h
121
121
  # Pre-format Entry
122
122
 
123
+ table_style = flags[:table_style]&.to_sym || :unicode
124
+
123
125
  table = TTY::Table.new(header: entry.keys, rows: [entry], orientation: :vertical)
124
126
 
125
127
  LogBot.debug('Rendering Entries') if ENV['DEBUG']
126
- table.render(:unicode, padding: [0, 1, 0, 1], multiline: true) do |renderer|
128
+ table.render(table_style, padding: [0, 1, 0, 1], multiline: true) do |renderer|
127
129
  renderer.border.style = :cyan
128
130
  end
129
131
 
@@ -182,7 +184,10 @@ module GreenHat
182
184
  # TODO: Simplify
183
185
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
184
186
  def self.filter(data, flags = {}, args = {})
185
- results = data.clone.flatten.compact
187
+ # results = data.clone.flatten.compact
188
+
189
+ # Experimenting with deep clone
190
+ results = Marshal.load(Marshal.dump(data))
186
191
  results.select! do |row|
187
192
  args.send(flags.logic) do |arg|
188
193
  filter_row_key(row, arg, flags)
@@ -192,6 +197,9 @@ module GreenHat
192
197
  # Ensure presecense of a specific field
193
198
  results = filter_exists(results, flags[:exists]) if flags.key?(:exists)
194
199
 
200
+ # Time Zone
201
+ results = filter_modify_timezone(results, flags[:time_zone]) if flags.key?(:time_zone)
202
+
195
203
  # Time Filtering
196
204
  results = filter_time(results, flags) if flags.key?(:start) || flags.key?(:end)
197
205
 
@@ -217,24 +225,34 @@ module GreenHat
217
225
  results.reverse! if flags[:reverse]
218
226
 
219
227
  # Count occurrences / Skip Results
220
- return filter_stats(results, flags[:stats]) if flags.key?(:stats)
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)
221
232
 
222
233
  # Pluck
223
234
  results = filter_pluck(results, flags[:pluck]) if flags.key?(:pluck)
224
235
 
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]
236
+ results
237
+ end
238
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
229
239
 
230
- # New
231
- results.shift flags[:limit]
240
+ # Limit / Ensure Exists and Valid Number
241
+ def self.filter_limit(results, limit)
242
+ return results unless limit.integer? && limit.positive?
232
243
 
233
- else
234
- results
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
235
252
  end
253
+
254
+ results
236
255
  end
237
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
238
256
 
239
257
  # Filter Start and End Times
240
258
  # rubocop:disable Metrics/MethodLength
@@ -339,7 +357,9 @@ module GreenHat
339
357
  end.inject(:&)
340
358
  end
341
359
 
342
- def self.filter_stats(results, stats)
360
+ def self.filter_stats(results, flags)
361
+ stats = flags[:stats]
362
+
343
363
  # Avoid Empty Results
344
364
  if stats.empty?
345
365
  filter_empty_arg('stats')
@@ -367,6 +387,13 @@ module GreenHat
367
387
  # Append Header / Total with field name
368
388
  output.unshift([field.to_s.pastel(:bright_black), total])
369
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
+
370
397
  # Format
371
398
  output.to_h
372
399
  end
@@ -440,6 +467,32 @@ module GreenHat
440
467
  end
441
468
  end
442
469
 
470
+ # Total Count Helper
471
+ def self.fields_print(results)
472
+ results.each do |k, v|
473
+ puts k
474
+ puts field_table(v.map(&:keys).flatten.uniq.sort)
475
+ puts
476
+ end
477
+ end
478
+
479
+ def self.field_table(list, columns = 4)
480
+ return nil if list.size.zero?
481
+
482
+ # Keep Alphabetical Sort
483
+ groups = list.each_slice((list.size / columns.to_f).round).to_a
484
+
485
+ table = TTY::Table.new do |t|
486
+ loop do
487
+ break if groups.all?(&:empty?)
488
+
489
+ t << groups.map(&:shift)
490
+ end
491
+ end
492
+
493
+ table.render(:unicode, padding: [0, 1, 0, 1])
494
+ end
495
+
443
496
  # Unified Files Interface
444
497
  def self.files(file_list, base_list = nil, flags = {})
445
498
  base_list ||= Thing.all
@@ -34,10 +34,10 @@ module GreenHat
34
34
  :ok
35
35
  end
36
36
 
37
- # rubocop:disable Metrics/LineLength
37
+ # rubocop:disable Layout/LineLength
38
38
  # NGINX Conf: /var/opt/gitlab/nginx/conf/nginx.conf
39
39
  # log_format gitlab_access '$remote_addr - $remote_user [$time_local] "$request_method $filtered_request_uri $server_protocol" $status $body_bytes_sent "$filtered_http_referer" "$http_user_agent" $gzip_ratio';
40
- # rubocop:enable Metrics/LineLength
40
+ # rubocop:enable Layout/LineLength
41
41
 
42
42
  # ==========================================================================
43
43
  end
@@ -26,6 +26,7 @@ class Thing < Teron
26
26
  field :parsed # Flag for Parsing
27
27
  field :result # Processed Data
28
28
  field :raw_result # Flag for reading raw data
29
+ field :result_fields # All available fields
29
30
 
30
31
  def friendly_name
31
32
  "#{archive.friendly_name.pastel(:blue)} #{name.pastel(:green)}"
@@ -49,6 +50,13 @@ class Thing < Teron
49
50
  result
50
51
  end
51
52
 
53
+ # Processor
54
+ def fields
55
+ process unless parsed
56
+
57
+ result_fields
58
+ end
59
+
52
60
  def raw
53
61
  raw_read if raw_result.nil?
54
62
 
@@ -116,6 +124,20 @@ class Thing < Teron
116
124
 
117
125
  self.parsed = true
118
126
 
127
+ self.result_fields = field_processing
128
+
119
129
  save!
120
130
  end
131
+
132
+ def field_processing
133
+ if data.instance_of?(Array)
134
+ data.select { |x| x.instance_of?(Hash) }.map(&:keys).flatten.uniq
135
+ else
136
+ []
137
+ end
138
+ rescue StandardError => e
139
+ LogBot.fatal('Process', message: e.message, backtrace: e.backtrace.first)
140
+
141
+ []
142
+ end
121
143
  end
@@ -1,3 +1,3 @@
1
1
  module GreenHat
2
- VERSION = '0.3.1'.freeze
2
+ VERSION = '0.3.2'.freeze
3
3
  end
data/lib/greenhat.rb CHANGED
@@ -21,6 +21,7 @@ require 'tty-prompt'
21
21
  require 'tty-reader'
22
22
  require 'tty-spinner'
23
23
  require 'tty-table'
24
+ require 'tty-which'
24
25
  require 'warning'
25
26
 
26
27
  # Custom Gem
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: greenhat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Davin Walker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-14 00:00:00.000000000 Z
11
+ date: 2021-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: amazing_print
@@ -360,6 +360,20 @@ dependencies:
360
360
  - - "~>"
361
361
  - !ruby/object:Gem::Version
362
362
  version: '0.12'
363
+ - !ruby/object:Gem::Dependency
364
+ name: tty-which
365
+ requirement: !ruby/object:Gem::Requirement
366
+ requirements:
367
+ - - "~>"
368
+ - !ruby/object:Gem::Version
369
+ version: '0.5'
370
+ type: :runtime
371
+ prerelease: false
372
+ version_requirements: !ruby/object:Gem::Requirement
373
+ requirements:
374
+ - - "~>"
375
+ - !ruby/object:Gem::Version
376
+ version: '0.5'
363
377
  - !ruby/object:Gem::Dependency
364
378
  name: warning
365
379
  requirement: !ruby/object:Gem::Requirement
@@ -405,6 +419,7 @@ files:
405
419
  - lib/greenhat/shell/color_string.rb
406
420
  - lib/greenhat/shell/disk.rb
407
421
  - lib/greenhat/shell/faststats.rb
422
+ - lib/greenhat/shell/field_helper.rb
408
423
  - lib/greenhat/shell/filter_help.rb
409
424
  - lib/greenhat/shell/gitlab.rb
410
425
  - lib/greenhat/shell/help.rb