module GreenHat::ShellHelper
CLI Helper
Common Helpers rubocop:disable Metrics/ModuleLength
Public Class Methods
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 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
# 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
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 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
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
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
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
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
# 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
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
# 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
# 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
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
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
# 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
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
# 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
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
# 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
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 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
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
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 Helper
# File lib/greenhat/shell/shell_helper.rb, line 376 def self.percent(value, total) ((value / total.to_f) * 100).round end
# 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
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
# 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
Generic Search Helper / String/Regex
# File lib/greenhat/shell/shell_helper.rb, line 510 def self.search(data, flags = {}, args = {}) results = data.clone.flatten.compact results.select! do |row| args.send(flags.logic) do |arg| search_row(row, arg, flags) end end # Strip Results if Slice is defined results.map! { |row| row.slice(*flags[:slice]) } if flags[:slice] # Strip Results if Except is defined results.map! { |row| row.except(*flags[:except]) } if flags[:except] # Remove Blank from either slice or except results.reject!(&:empty?) results end
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
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 / 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
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 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