Module: GreenHat::Cli

Defined in:
lib/greenhat/cli.rb

Overview

CLI Methods rubocop:disable Metrics/ModuleLength

Class Method Summary collapse

Class Method Details

.autoObject

Auto Complete



67
68
69
70
71
72
73
74
75
76
77
# File 'lib/greenhat/cli.rb', line 67

def self.auto
  word = @list.last
  # This is being weird with auto complete
  # word = reader.line.word

  if word.blank?
    help
  else
    auto_match(current_methods + current_submodules + cmd_list, word)
  end
end

.auto_files(matches, word) ⇒ Object

Auto Complete File Names



109
110
111
112
113
114
115
116
117
118
# File 'lib/greenhat/cli.rb', line 109

def self.auto_files(matches, word)
  if matches.count == 1
    auto_update(matches.first, word)

  # Print List of Options
  elsif matches.count > 1
    auto_update(common_substr(matches), word)
    puts matches.join("\t").pastel(:bright_green)
  end
end

.auto_match(matches, word) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/greenhat/cli.rb', line 83

def self.auto_match(matches, word)
  matches.select! { |x| x[/^#{Regexp.escape(word)}/] }

  if submodule?
    submodule!
    reader.breaker = true
  # Only one Match!
  elsif matches.count == 1
    auto_update(matches.first, word)

  # Print List of Options
  elsif matches.count > 1
    puts matches.join("\t").pastel(:bright_green)

  # General Filename Matching
  elsif !file_matches(word).empty?
    auto_files(file_matches(word), word)

  # Submodule Autocompletion
  elsif current_methods.include?('auto_complete')
    update_text = current_location.auto_complete(@list, word)
    auto_update(update_text, word) unless update_text.blank?
  end
end

.auto_update(match, word) ⇒ Object

Handle Updates to Reader



121
122
123
124
# File 'lib/greenhat/cli.rb', line 121

def self.auto_update(match, word)
  add = match.split(word, 2).last
  reader.line.insert add unless add.nil?
end

.available(list) ⇒ Object



169
170
171
# File 'lib/greenhat/cli.rb', line 169

def self.available(list)
  list.map(&:to_s).map(&:downcase)
end

.backObject



307
308
309
# File 'lib/greenhat/cli.rb', line 307

def self.back
  move location[-2] if location.count > 1
end

.bad_file(archive) ⇒ Object



527
528
529
530
# File 'lib/greenhat/cli.rb', line 527

def self.bad_file(archive)
  puts color("Cannot find archive: #{archive}", :red)
  exit 1
end

.clear_screenObject



338
339
340
# File 'lib/greenhat/cli.rb', line 338

def self.clear_screen
  print cursor.clear_screen + cursor.move_to(0, 0)
end

.cli_helpObject



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/greenhat/cli.rb', line 351

def self.cli_help
  Shell.version
  puts
  puts 'Usage'.pastel(:yellow)
  puts '  greenhat  <sos-archive.tgz> <sos-archive2.tgz> '
  puts '  greenhat  <sos-archive.tgz> -q --command=df'
  puts

  puts 'Options'.pastel(:yellow)
  puts '  --report, -r'.pastel(:green)
  puts '    Run `report` against archives and exit'
  puts

  puts '  --quiet, -r'.pastel(:green)
  puts '    Surpress GreenHat logging output'
  puts

  puts '  --load, -l'.pastel(:green)
  puts '    Automatically attempt to read/parse/preload all included files'
  puts

  puts '  --command, -c'.pastel(:green)
  puts '    Run and then exit a GreenHat Shell command'
  puts

  puts '  --version, -v'.pastel(:green)
  puts '    Print version and exit'
  puts
end

.cmdObject

Reader helper



174
175
176
# File 'lib/greenhat/cli.rb', line 174

def self.cmd
  @list.first
end

.cmd?Boolean

Returns:

  • (Boolean)


187
188
189
# File 'lib/greenhat/cli.rb', line 187

def self.cmd?
  cmd_list.any? cmd
end

.cmd_listObject

Special Command Overrides



183
184
185
# File 'lib/greenhat/cli.rb', line 183

def self.cmd_list
  ['help', '~', '..', 'back', 'clear']
end

.cmd_runObject



195
196
197
198
199
200
201
202
203
204
# File 'lib/greenhat/cli.rb', line 195

def self.cmd_run
  case cmd
  when 'help' then help
  when '~' then home
  when '..', 'back' then back
  when 'clear' then clear_screen
  end

  @list.shift
end

.common_substr(strings) ⇒ Object

Complete as much as possible comparison stackoverflow.com/a/2158481/1678507



128
129
130
131
132
133
134
135
136
137
# File 'lib/greenhat/cli.rb', line 128

def self.common_substr(strings)
  shortest = strings.min_by(&:length)
  maxlen = shortest.length
  maxlen.downto(0) do |len|
    0.upto(maxlen - len) do |start|
      substr = shortest[start, len]
      return substr if strings.all? { |str| str.include? substr }
    end
  end
end

.current_commandsObject



249
250
251
# File 'lib/greenhat/cli.rb', line 249

def self.current_commands
  current_location.methods(false).map(&:to_s).sort - %w[auto_complete]
end

.current_locationObject



326
327
328
# File 'lib/greenhat/cli.rb', line 326

def self.current_location
  location.last
end

.current_methodsObject

Reader to Get Last Location’s Available Methods / Keep Helper Methods



312
313
314
315
# File 'lib/greenhat/cli.rb', line 312

def self.current_methods
  current_location.methods(false).map(&:to_s)
  # .reject { |x| x.include? '_help' }
end

.current_submodule?Boolean

Full Commands at the Submodule ‘disk summary` from `disk`

Returns:

  • (Boolean)


322
323
324
# File 'lib/greenhat/cli.rb', line 322

def self.current_submodule?
  current_location.to_s.demodulize.downcase == @list.first
end

.current_submodulesObject



317
318
319
# File 'lib/greenhat/cli.rb', line 317

def self.current_submodules
  current_location.constants.map(&:to_s).map(&:downcase)
end

.cursorObject



6
7
8
# File 'lib/greenhat/cli.rb', line 6

def self.cursor
  @cursor = TTY::Cursor
end

.default?Boolean

Check for ‘default` method and files

Returns:

  • (Boolean)


245
246
247
# File 'lib/greenhat/cli.rb', line 245

def self.default?
  @list.any? { |x| x.include?(cmd) } && current_methods.include?('default')
end

.did_you_meanObject



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/greenhat/cli.rb', line 140

def self.did_you_mean
  dictionary = current_methods + current_submodules + cmd_list

  # all.select! { |x| x.include? cmd }

  all = DidYouMean::SpellChecker.new(dictionary: dictionary).correct(cmd)

  if all.empty?
    puts [
      'Command not found '.pastel(:red),
      cmd.pastel(:bright_yellow),
      ' ('.pastel(:bright_black),
      'help'.pastel(:blue),
      ' to show available commands'.pastel(:bright_black),
      ')'.pastel(:bright_black)
    ].join
  else
    puts "#{'Did you mean?'.pastel(:cyan)}  #{all.join("\t").pastel(:green)}"
  end
end

.file_matches(word) ⇒ Object



79
80
81
# File 'lib/greenhat/cli.rb', line 79

def self.file_matches(word)
  files.select { |x| x[/^#{Regexp.escape(word)}/] }
end

.filesObject



15
16
17
# File 'lib/greenhat/cli.rb', line 15

def self.files
  @files ||= Thing.all.map(&:name).uniq
end

.flags?(list = [], flags = {}) ⇒ Boolean

Helper to Simplify checking flags

Returns:

  • (Boolean)


428
429
430
# File 'lib/greenhat/cli.rb', line 428

def self.flags?(list = [], flags = {})
  list.any? { |x| flags.key? x }
end

.help(long = true) ⇒ Object

General Helper



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/greenhat/cli.rb', line 254

def self.help(long = true)
  if current_location.methods(false).count.zero?
    puts 'No Commands'.pastel(:red)
  else
    puts 'Commands: '
    current_commands.each do |item|
      next if %w[default help].any? { |x| x == item }

      puts "=> #{item.to_s.pastel(:blue)}"
    end

  end

  puts ''

  if current_location.constants.count.zero?
    puts 'No Submodules'.pastel(:red)
  else
    puts 'Submodules'
    current_location.constants.each do |item|
      puts "-> #{item.to_s.demodulize.downcase.pastel(:yellow)}"
    end
  end

  # Execute local help last if exists
  if current_methods.include?('help') && long
    puts
    current_location.send(:help)
  end

  puts ''
end

.homeObject



303
304
305
# File 'lib/greenhat/cli.rb', line 303

def self.home
  move Shell
end

.load_files(files) ⇒ Object



481
482
483
484
485
486
487
488
489
490
491
492
493
# File 'lib/greenhat/cli.rb', line 481

def self.load_files(files)
  # TODO: Web Helpers?
  # suppress_output { GreenHat::Web.start }

  # Don't double up on archives / Only Existing files
  puts 'Loading Archives'.pastel(:blue) unless Cli.quiet
  files.uniq.each do |file|
    next unless File.exist?(file)

    puts "- #{file}".pastel(:magenta) unless Cli.quiet
    ArchiveLoader.load file
  end
end

.locationObject



287
288
289
# File 'lib/greenhat/cli.rb', line 287

def self.location
  @location ||= [Shell]
end

.location_readerObject



330
331
332
# File 'lib/greenhat/cli.rb', line 330

def self.location_reader
  location.map(&:to_s).map(&:downcase).map(&:demodulize).join('/').gsub('shell', '~').pastel(:blue)
end

TODO



519
520
521
522
523
524
525
# File 'lib/greenhat/cli.rb', line 519

def self.menu
  prompt.select('Wat do?') do |menu|
    menu.choice name: 'exit'
    menu.choice name: 'Export Kibana Dashboard', value: 'export'
    menu.choice name: 'console (pry)', value: 'console'
  end
end

.move(spot) ⇒ Object

Replace Location



292
293
294
# File 'lib/greenhat/cli.rb', line 292

def self.move(spot)
  @location = [spot]
end

.outputObject



510
511
512
# File 'lib/greenhat/cli.rb', line 510

def self.output
  'greenhat.html'
end

.post_args(flags) ⇒ Object

Arguments to be handled after general processing



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/greenhat/cli.rb', line 410

def self.post_args(flags)
  # Run report and exit / Don't Clear for reports
  if flags?(%i[report r], flags)
    @quiet = true
    # Don't Use Pagination
    GreenHat::Shell.report(['--raw'])
    exit 0
  else
    clear_screen
  end

  # CTL Tails need to be parsed for new 'things'
  Thing.where(kind: :gitlab_tail)&.map(&:process)

  Thing.all.each(&:process) if flags?(%i[load l], flags)
end

.pre_args(flags, files) ⇒ Object

Arguments before general processing



382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/greenhat/cli.rb', line 382

def self.pre_args(flags, files)
  # Help
  if flags?(%i[help h], flags)
    cli_help
    exit 0
  end

  # Version
  if flags?(%i[version v], flags)
    Shell.version
    exit 0
  end

  # Quiet Flag
  @quiet = true if flags?(%i[quiet q], flags)

  # rubocop:disable Style/GuardClause
  # MIA Files
  if files.empty? || files.count { |x| File.exist? x }.zero?
    puts "No arguments or files don't exist".pastel(:red)
    puts 'Usage: greenhat  <sos-archive.tgz> <sos-archive2.tgz>'
    cli_help
    Shell.version
  end
  # rubocop:enable Style/GuardClause
end

.processObject

Auto/Run Process - Populate and parse: @list



162
163
164
165
166
167
# File 'lib/greenhat/cli.rb', line 162

def self.process
  line = reader.line
  @list = Shellwords.split line.text
rescue StandardError => e
  puts "#{'Invalid Command'.pastel(:red)}: #{e.message.pastel(:green)}"
end

.promptObject



514
515
516
# File 'lib/greenhat/cli.rb', line 514

def self.prompt
  TTY::Prompt.new(active_color: :cyan)
end

.quietObject



342
343
344
# File 'lib/greenhat/cli.rb', line 342

def self.quiet
  @quiet
end

.quiet!Object

Toggle Quiet Settings



347
348
349
# File 'lib/greenhat/cli.rb', line 347

def self.quiet!
  @quiet = !@quiet
end

.readerObject

Input Loop Listener



11
12
13
# File 'lib/greenhat/cli.rb', line 11

def self.reader
  @reader ||= reader_setup
end

.reader_setupObject



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/greenhat/cli.rb', line 19

def self.reader_setup
  reader = TTY::Reader.new(history_duplicates: false, interrupt: -> { back })

  Settings.cmd_history_clean.each do |line|
    reader.add_to_history(line)
  end

  # Blank?
  reader.add_to_history ''

  # Remove Line
  reader.on(:keyctrl_u) do |_event|
    reader.line.remove reader.line.text.size
  end

  # Navigate Word Left
  reader.on(:keyctrl_left) { reader.line.move_word_left }

  # Navigate Word Right
  reader.on(:keyctrl_right) { reader.line.move_word_right }

  # Navigate Beginning
  reader.on(:keyctrl_a) { reader.line.move_to_start }

  # Navigate End
  reader.on(:keyctrl_e) { reader.line.move_to_end }

  reader.on(:keyback_tab) { back }

  reader.on(:keytab) do
    process
    auto
  end

  reader.instance_variable_get(:@history)

  # DEBUG PRY
  reader.on(:keyctrl_p) do |event|
    # rubocop:disable Lint/Debugger
    binding.pry
    # rubocop:enable Lint/Debugger
  end
  reader
end

.readline_notchObject



334
335
336
# File 'lib/greenhat/cli.rb', line 334

def self.readline_notch
  "#{'greenhat'.pastel(:bright_black)} #{location_reader} » "
end

.runObject



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/greenhat/cli.rb', line 216

def self.run
  return true if @list.blank?

  if cmd?
    cmd_run
  elsif run?
    run!

    return true # End Early

  elsif current_submodule?
    @list.shift
  elsif submodule?
    submodule!
  # Prepend Default if exists
  elsif default?
    @list.unshift 'default'
  else
    did_you_mean
    @list.shift
  end

  run # Loop Back
rescue StandardError => e
  LogBot.fatal('CLI Run', e.message)
  puts e.backtrace[0..4].join("\n").pastel(:red)
end

.run!Object

Final Command Execution



207
208
209
210
211
212
213
214
# File 'lib/greenhat/cli.rb', line 207

def self.run!
  # Shift to prevent duplicate Runs / Arity for identifying if Method has params
  if current_location.method(@list.first).arity.zero?
    current_location.send(@list.shift.clone)
  else
    current_location.send(@list.shift.clone, @list.clone)
  end
end

.run?Boolean

Returns:

  • (Boolean)


178
179
180
# File 'lib/greenhat/cli.rb', line 178

def self.run?
  current_methods.include? cmd
end

.run_command(args) ⇒ Object

Run and Exit Command Helper



469
470
471
472
473
474
475
476
477
478
479
# File 'lib/greenhat/cli.rb', line 469

def self.run_command(args)
  args.each do |arg|
    # Quick Validation
    next unless run_command?(arg) && !arg.value.empty?

    reader.line = ''
    @list = Shellwords.split arg.value
    run
  end
  exit 0
end

.run_command?(arg) ⇒ Boolean

Returns:

  • (Boolean)


462
463
464
465
466
# File 'lib/greenhat/cli.rb', line 462

def self.run_command?(arg)
  %i[command c].any? do |x|
    arg[:field] == x
  end
end

.start(raw) ⇒ Object

If no arguments Supplied Print and quit - rather than nasty exception



433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/greenhat/cli.rb', line 433

def self.start(raw)
  Settings.start
  files, flags, args = Args.parse(raw)
  pre_args(flags, files)
  load_files files
  run_command(args) if args.any? { |arg| run_command?(arg) }
  post_args(flags)

  value ||= '' # Empty Start

  loop do
    line = reader.read_line(readline_notch, value: value)
    value = '' # Remove Afterwards

    if reader.breaker
      value = line
      puts ''
      next
    end

    break if line =~ /^exit/i

    Settings.cmd_add(line) unless line.blank?

    process
    run
  end
end

.submodule!Object

Append to Location



297
298
299
300
301
# File 'lib/greenhat/cli.rb', line 297

def self.submodule!
  spot = @list.shift
  reader.line.replace @list.join(' ')
  @location << current_location.const_get(spot.capitalize.to_sym)
end

.submodule?Boolean

Returns:

  • (Boolean)


191
192
193
# File 'lib/greenhat/cli.rb', line 191

def self.submodule?
  current_submodules.include? @list.first
end

.suppress_outputObject



495
496
497
498
499
500
501
502
503
504
# File 'lib/greenhat/cli.rb', line 495

def self.suppress_output
  original_stderr = $stderr.clone
  original_stdout = $stdout.clone
  $stderr.reopen(File.new('/dev/null', 'w'))
  $stdout.reopen(File.new('/dev/null', 'w'))
  yield
ensure
  $stdout.reopen(original_stdout)
  $stderr.reopen(original_stderr)
end

.templateObject



506
507
508
# File 'lib/greenhat/cli.rb', line 506

def self.template
  "#{__dir__}/views/index.slim"
end