greenhat 0.3.1 → 0.3.2

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 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