module GreenHat::ShellHelper

CLI Helper

Common Helpers rubocop:disable Metrics/ModuleLength

Public Class Methods

common_opts() click to toggle source

General Helper for `show`

# File lib/greenhat/shell/shell_helper.rb, line 588
def self.common_opts
  puts 'Common Options'.pastel(:blue)
  puts '  --raw'.pastel(:green)
  puts '    Do not use less/paging'
  puts

  puts '  --archive'.pastel(:green)
  puts '    Limit to specific archive name (inclusive). Matching SOS tar.gz name'
  puts '    Ex: --archive=dev-gitlab_20210622154626, --archive=202106,202107'
  puts
end
entry_show(flags, entry, key = nil) click to toggle source

Entry Shower / Top Level

# File lib/greenhat/shell/shell_helper.rb, line 69
def self.entry_show(flags, entry, key = nil)
  LogBot.debug('Entry Show', entry.class) if ENV['DEBUG']
  case entry
  when Hash then render_table(entry, flags)
  when Float, Integer, Array
    format_table_entry(flags, entry, key)
  # Ignore Special Formatting for Strings / Usually already formatted
  when String
    entry
  else
    LogBot.warn('Shell Show', "Unknown #{entry.class}")
    nil
  end
end
entry_truncate(entry, truncate) click to toggle source
# File lib/greenhat/shell/shell_helper.rb, line 299
def self.entry_truncate(entry, truncate)
  # Ignore if Truncation Off
  return entry if truncate.zero?

  # Only truncate large strings
  return entry unless entry.instance_of?(String) && entry.size > truncate

  # Include  '...' to indicate truncation
  "#{entry.to_s[0..truncate]} #{'...'.pastel(:bright_blue)}"
end
file_output(files, flags = {}) click to toggle source

Use File Process for Output

# File lib/greenhat/shell/shell_helper.rb, line 6
def self.file_output(files, flags = {})
  results = file_process(files) do |file|
    [
      file.friendly_name,
      file.output(false),
      "\n"
    ]
  end

  ShellHelper.show(results.flatten, flags)
end
file_process(files, &block) click to toggle source
# File lib/greenhat/shell/shell_helper.rb, line 18
def self.file_process(files, &block)
  files.map do |file|
    next if file.output(false).empty?

    block.call(file)
  end.flatten
end
files(file_list, base_list = nil, flags = {}) click to toggle source

Unified Files Interface

# File lib/greenhat/shell/shell_helper.rb, line 444
def self.files(file_list, base_list = nil, flags = {})
  base_list ||= Thing.all

  # Prepare Log List
  file_list = prepare_list(file_list, base_list)

  # Convert to Things
  find_things(file_list, flags)
end
filter(data, flags = {}, args = {}) click to toggle source

Filter Logic TODO: Simplify rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# File lib/greenhat/shell/shell_helper.rb, line 184
def self.filter(data, flags = {}, args = {})
  results = data.clone.flatten.compact
  results.select! do |row|
    args.send(flags.logic) do |arg|
      filter_row_key(row, arg, flags)
    end
  end

  # Ensure presecense of a specific field
  results = filter_exists(results, flags[:exists]) if flags.key?(:exists)

  # Time Filtering
  results = filter_time(results, flags) if flags.key?(:start) || flags.key?(:end)

  # Strip Results if Slice is defined
  results = filter_slice(results, flags[:slice]) if flags.key?(:slice)

  # Strip Results if Except is defined
  results = filter_except(results, flags[:except]) if flags.key?(:except)

  # Remove Blank from either slice or except
  results.reject!(&:empty?)

  # Sort
  results.sort_by! { |x| x.slice(*flags[:sort]).values } if flags.key?(:sort)

  # JSON Formatting
  results = results.map { |x| Oj.dump(x) } if flags.key?(:json)

  # Show Unique Only
  results = filter_uniq(results, flags[:uniq]) if flags.key?(:uniq)

  # Reverse
  results.reverse! if flags[:reverse]

  # Count occurrences / Skip Results
  return filter_stats(results, flags[:stats]) if flags.key?(:stats)

  # Pluck
  results = filter_pluck(results, flags[:pluck]) if flags.key?(:pluck)

  # Limit / Ensure Exists and Valid Number
  if flags.key?(:limit) && flags[:limit]
    # Old
    # results[0..flags[:limit].map(&:to_s).join.to_i - 1]

    # New
    results.shift flags[:limit]

  else
    results
  end
end
filter_and(data, params = {}) click to toggle source

TODO: Needed?

# File lib/greenhat/shell/shell_helper.rb, line 571
def self.filter_and(data, params = {})
  result = data.clone.flatten.compact
  params.each do |k, v|
    result.select! do |row|
      if row.key? k.to_sym
        row[k.to_sym].include? v
      else
        false
      end
    end
    next
  end

  result
end
filter_count_occurrences(results, field) click to toggle source

Helper to Count occurrences

# File lib/greenhat/shell/shell_helper.rb, line 381
def self.filter_count_occurrences(results, field)
  results.each_with_object(Hash.new(0)) do |entry, counts|
    if entry.key? field
      counts[entry[field]] += 1
    else
      counts['None'.pastel(:bright_black)] += 1
    end

    counts
  end
end
filter_empty_arg(arg) click to toggle source
# File lib/greenhat/shell/shell_helper.rb, line 393
def self.filter_empty_arg(arg)
  puts [
    'Ignoring'.pastel(:bright_yellow),
    "--#{arg}".pastel(:cyan),
    'it requires an argument'.pastel(:red)
  ].join(' ')
end
filter_except(results, except) click to toggle source

rubocop:enable Metrics/MethodLength

# File lib/greenhat/shell/shell_helper.rb, line 279
def self.filter_except(results, except)
  # Avoid Empty Results
  if except.empty?
    filter_empty_arg('except')
    return results
  end

  results.map { |row| row.except(*except) }
end
filter_exists(results, exists) click to toggle source
# File lib/greenhat/shell/shell_helper.rb, line 289
def self.filter_exists(results, exists)
  # Avoid Empty Results
  if exists.empty?
    filter_empty_arg('exists')
    return results
  end

  results.select { |row| (exists - row.keys).empty? }
end
filter_pluck(results, pluck) click to toggle source
# File lib/greenhat/shell/shell_helper.rb, line 320
def self.filter_pluck(results, pluck)
  # Avoid Empty Results
  if pluck.empty?
    filter_empty_arg('pluck')
    return results
  end

  results.map { |x| x.slice(*pluck).values }.flatten
end
filter_row_entry(entry, arg, flags) click to toggle source

Field Partial / Case / Exact search

# File lib/greenhat/shell/shell_helper.rb, line 423
def self.filter_row_entry(entry, arg, flags)
  # Exact Matching / Unless doing full text search
  return entry.to_s == arg.value.to_s if flags.key?(:exact) && arg.field != :text

  if flags.key?(:case)
    entry.include? arg.value.to_s
  else
    entry.downcase.include? arg.value.to_s.downcase
  end
end
filter_row_key(row, arg, flags) click to toggle source

Break out filter row logic into separate method

# File lib/greenhat/shell/shell_helper.rb, line 403
def self.filter_row_key(row, arg, flags)
  # Ignore Other Logic if Field isn't even included / Full Text Searching
  return false unless row.key?(arg[:field]) || arg[:field] == :text

  # Sensitivity Check / Check for Match / Full Text Searching
  included = if arg[:field] == :text
               filter_row_entry(row.to_s, arg, flags)
             else
               filter_row_entry(row[arg.field].to_s, arg, flags)
             end

  # Pivot of off include vs exclude
  if arg.bang
    !included
  else
    included
  end
end
filter_slice(results, slice) click to toggle source
# File lib/greenhat/shell/shell_helper.rb, line 310
def self.filter_slice(results, slice)
  # Avoid Empty Results
  if slice.empty?
    filter_empty_arg('slice')
    return results
  end

  results.map { |row| row.slice(*slice) }
end
filter_start(files, flags, args) click to toggle source

Main Entry Point for Filtering

# File lib/greenhat/shell/shell_helper.rb, line 152
def self.filter_start(files, flags, args)
  # Convert to Things
  logs = ShellHelper.find_things(files, flags).select(&:processed?)

  # Ignore Archive/Host Dividers
  if flags[:combine]
    results = logs.reject(&:blank?).map(&:data).flatten.compact
    ShellHelper.filter(results, flags, args)
  else
    # Iterate and Preserve Archive/Host Index
    logs.each_with_object({}) do |log, obj|
      # Ignore Empty Results / No Thing
      next if log&.blank?

      # Include Total Count in Name
      results = ShellHelper.filter(log.data, flags, args)
      title = [
        log.friendly_name,
        " #{results.count}".pastel(:bright_black)
      ]

      # Save unless empty
      obj[title.join] = results unless results.count.zero?

      obj
    end
  end
end
filter_stats(results, stats) click to toggle source
# File lib/greenhat/shell/shell_helper.rb, line 342
def self.filter_stats(results, stats)
  # Avoid Empty Results
  if stats.empty?
    filter_empty_arg('stats')
    return results
  end

  # Loop through Stats, Separate Hash/Tables
  stats.map do |field|
    occurrences = filter_count_occurrences(results, field)

    # Total Occurences
    total = occurrences.values.sum

    # Percs
    occurrences.transform_values! do |count|
      [
        count,
        " #{percent(count, total)}%".pastel(:bright_black)
      ]
    end

    # Sort by total occurances / New Variable for Total
    output = occurrences.sort_by(&:last).to_h.transform_values!(&:join).to_a

    # Append Header / Total with field name
    output.unshift([field.to_s.pastel(:bright_black), total])

    # Format
    output.to_h
  end
end
filter_time(results, flags) click to toggle source

Filter Start and End Times rubocop:disable Metrics/MethodLength TODO: This is a bit icky, simplify/dry

# File lib/greenhat/shell/shell_helper.rb, line 242
def self.filter_time(results, flags)
  if flags.key?(:start)
    begin
      time_start = Time.parse(flags[:start])

      results.select! do |x|
        if x.time
          time_start < x.time
        else
          true
        end
      end
    rescue StandardError
      puts 'Unable to Process Start Time Filter'.pastel(:red)
    end
  end

  if flags.key?(:end)
    begin
      time_start = Time.parse(flags[:end])

      results.select! do |x|
        if x.time
          time_start > x.time
        else
          true
        end
      end
    rescue StandardError
      puts 'Unable to Process End Time Filter'.pastel(:red)
    end
  end

  results
end
filter_uniq(results, unique) click to toggle source
# File lib/greenhat/shell/shell_helper.rb, line 330
def self.filter_uniq(results, unique)
  # Avoid Empty Results
  if unique.empty?
    filter_empty_arg('uniq')
    return results
  end

  unique.map do |field|
    results.uniq { |x| x[field] }
  end.inject(:&)
end
find_things(files, flags = {}) click to toggle source

Shortcut find things

# File lib/greenhat/shell/shell_helper.rb, line 475
def self.find_things(files, flags = {})
  things = files.uniq.flat_map do |file|
    # If Thing, Return Thing
    return file if file.instance_of?(Thing)

    if flags.fuzzy_file_match
      Thing.all.select { |x| x.name.include? file }
    else
      Thing.where name: file
    end
  end.uniq

  # Host / Archive
  things.select! { |x| x.archive? flags.archive } if flags.key?(:archive)

  things
end
format_table_entry(flags, entry, key = nil) click to toggle source

Format Table Entries

# File lib/greenhat/shell/shell_helper.rb, line 85
def self.format_table_entry(flags, entry, key = nil)
  formatted_entry = case entry
                    # Rounding
                    when Float, Integer || entry.numeric?
                      flags.key?(:round) ? entry.to_f.round(flags.round).ai : entry.ai

                      # General Inspecting
                    when Hash then entry.ai(ruby19_syntax: true)

                    # Arrays often contain Hashes. Dangerous Recursive?
                    when Array
                      entry.map { |x| format_table_entry(flags, x) }.join("\n")

                    when Time
                      entry.to_s.pastel(:bright_white)

                    # Default String Formatting
                    else
                      StringColor.do(key, entry)
                    end

  if flags[:truncate]
    entry_truncate(formatted_entry, flags[:truncate])
  else
    formatted_entry
  end
rescue StandardError => e
  if ENV['DEBUG']
    LogBot.warn('Table Format Entry', message: e.message)
    ap e.backtrace
  end
end
human_size_to_number(string) click to toggle source

Number Helper gitlab.com/zedtux/human_size_to_number/-/blob/master/lib/human_size_to_number/helper.rb

# File lib/greenhat/shell/shell_helper.rb, line 551
def self.human_size_to_number(string)
  size, unit = string.scan(/(\d*\.?\d+)\s?(Bytes?|KB|MB|GB|TB)/i).first
  number = size.to_f

  number = case unit.downcase
           when 'byte', 'bytes'
             number
           when 'kb'
             number * 1024
           when 'mb'
             number * 1024 * 1024
           when 'gb'
             number * 1024 * 1024 * 1024
           when 'tb'
             number * 1024 * 1024 * 1024 * 1024
           end
  number.round
end
page(data) click to toggle source

Pagination Helper

# File lib/greenhat/shell/shell_helper.rb, line 27
def self.page(data)
  TTY::Pager.page do |pager|
    data.flatten.each do |output|
      pager.write("\n#{output}") # write line to the pager
    end
  end
end
percent(value, total) click to toggle source

Percent Helper

# File lib/greenhat/shell/shell_helper.rb, line 376
def self.percent(value, total)
  ((value / total.to_f) * 100).round
end
prepare_list(log_list, base_list = nil, _flags = {}) click to toggle source

Total Log List Manipulator

# File lib/greenhat/shell/shell_helper.rb, line 455
def self.prepare_list(log_list, base_list = nil, _flags = {})
  base_list ||= GreenHat::ShellHelper::Log.list

  # Assume all
  log_list.push '*' if log_list.empty?

  # Map for All
  log_list = base_list.map(&:name) if log_list == ['*']

  log_list
end
render_table(entry, flags) click to toggle source

Print the Table in a Nice way

# File lib/greenhat/shell/shell_helper.rb, line 119
def self.render_table(entry, flags)
  entry = entry.map { |k, v| [k, format_table_entry(flags, v, k)] }.to_h
  # Pre-format Entry

  table = TTY::Table.new(header: entry.keys, rows: [entry], orientation: :vertical)

  LogBot.debug('Rendering Entries') if ENV['DEBUG']
  table.render(:unicode, padding: [0, 1, 0, 1], multiline: true) do |renderer|
    renderer.border.style = :cyan
  end

  # LogBot.debug('Finish Render Table') if ENV['DEBUG']
  # Fall Back to Amazing Inspect
rescue StandardError => e
  if ENV['DEBUG']
    LogBot.warn('Table', message: e.message)
    ap e.backtrace
  end

  [
    entry.ai,
    ('_' * (TTY::Screen.width / 3)).pastel(:cyan),
    "\n"
  ].join("\n")
end
render_table_entry(val, col_index, flags) click to toggle source
# File lib/greenhat/shell/shell_helper.rb, line 145
def self.render_table_entry(val, col_index, flags)
  return val.to_s unless col_index == 1

  format_table_entry(flags, val)
end
search_row(row, arg, flags) click to toggle source

Break out filter row logic into separate method

# File lib/greenhat/shell/shell_helper.rb, line 531
def self.search_row(row, arg, flags)
  # Sensitivity Check / Check for Match
  included = filter_row_entry(row.to_s, arg, flags)

  # Pivot of off include vs exclude
  if arg.bang
    !included
  else
    included
  end
end
search_start(files, flags, args) click to toggle source

Main Entry Point for Searching def self.search_start(log_list, filter_type, args, opts)

# File lib/greenhat/shell/shell_helper.rb, line 495
def self.search_start(files, flags, args)
  # Convert to Things
  logs = ShellHelper.find_things(files, flags)

  logs.each_with_object({}) do |log, obj|
    # Ignore Empty Results / No Thing
    next if log&.data.blank?

    obj[log.friendly_name] = ShellHelper.search(log.data, flags, args)

    obj
  end
end
show(data, flags = {}) click to toggle source

Show Data / Auto Paginate Helper

# File lib/greenhat/shell/shell_helper.rb, line 36
def self.show(data, flags = {})
  # If Block of String
  if data.instance_of?(String)
    TTY::Pager.page data
    return true
  end

  # If raw just print out
  if flags[:raw]
    puts data.join("\n")
    return true
  end

  # Check if content needs to paged, or if auto_height is off
  if Page.skip?(flags, data)
    puts data.map { |entry| entry_show(flags, entry) }.compact.join("\n")
    return true
  end

  # Default Pager
  TTY::Pager.page do |pager|
    data.each do |entry|
      output = entry_show(flags, entry)

      # Breaks any intentional spaces
      # next if output.blank?

      pager.write("\n#{output}") # write line to the pager
    end
  end
end
thing_list() click to toggle source

Fuzzy match for things

# File lib/greenhat/shell/shell_helper.rb, line 468
def self.thing_list
  @thing_list ||= Thing.all.map(&:name)

  @thing_list
end
total_count(results) click to toggle source

Total Count Helper

# File lib/greenhat/shell/shell_helper.rb, line 435
def self.total_count(results)
  results.each do |k, v|
    puts k
    puts "Total: #{v.count.to_s.pastel(:blue)}"
    puts
  end
end