class Raykit::Command

Functionality to support executing and logging system commands

def self.get_script_commands(hash)

def self.get_script_commands(hash)
  commands = []
  if hash.key?("script")
    hash["script"].each do |cmd|
      commands << cmd
    end
  end
  hash.each do |_k, v|
    next unless v.is_a?(Hash)
    subcommands = get_script_commands(v)
    subcommands.each do |c|
      commands << c
    end
  end
  commands
end

def self.parse(json)

def self.parse(json)
  cmd = Command.new("")
  cmd.from_hash(JSON.parse(json))
  cmd
end

def self.parse_yaml_commands(yaml)

def self.parse_yaml_commands(yaml)
  # commands=Array.new()

  data = YAML.safe_load(yaml)
  commands = get_script_commands(data)
end

def details

def details
  puts "  exit_code: " + @exitstatus.to_s
  if @output.length > 0
    @output.lines.each do |line|
      puts "  " + line
    end
  end
  if @error.length > 0
    @error.lines.each do |line|
      puts "  " + line
    end
  end
  self
end

def details_on_failure

def details_on_failure
  if @exitstatus != 0
    details
  end
  self
end

def elapsed_str

def elapsed_str
  if elapsed < 1.0
  end
  "#{format("%.0f", elapsed)}s"
end

def from_hash(hash)

def from_hash(hash)
  @command = hash["command"]
  @directory = hash["directory"]
  @timeout = hash["timeout"]
  @start_time = hash["start_time"]
  @elapsed = hash["elapsed"]
  @output = hash["output"]
  @error = hash["error"]
  @exitstatus = hash["exitstatus"]
  @user = hash["user"] if hash.include?("user")
  @machine = hash["machine"] if hash.include?("machine")
end

def init_defaults

def init_defaults
  @timeout = 0
  @directory = Dir.pwd
  @output = +""
  @error = +""
  @exitstatus = 0
  @user = Environment.user
  @machine = Environment.machine
  @logging_enabled = true
end

def initialize(command)

def initialize(command)
  #def initialize(command, timeout = 0, success_log_level = Logger::DEBUG, logging_enabled = true)

  timeout = 0
  success_log_level = nil
  logging_enabled = false
  @@commands = []
  init_defaults
  @success_log_level = success_log_level
  @logging_enabled = logging_enabled
  @command = command
  @timeout = timeout
  #t = Time.now

  @elapsed = 0
  #run if @command.length.positive?

  self
end

def log

def log
  # --- Rolling File JSON -----

  log = Logger.new("#{Raykit::Environment.log_dir}/Raykit.Commands.txt", "daily")
  log.formatter = proc do |_severity, _datetime, _progname, msg|
    "#{msg}\n"
  end
  secrets = Secrets.new
  msg = secrets.hide(@command) # "?"# self.summary(false)

  level = "Verbose"
  level = "Warning" if @exitstatus != 0
  event = Raykit::LogEvent.new(level, msg, {
    "Timeout" => @timeout,
    "Directory" => @directory,
    "Output" => @output,
    "Error" => @error,
    "ExitStatus" => @exitstatus,
    "Elapsed" => elapsed_str,
  })
  log.info event.to_json
  # ---------------------------

  begin
    json = JSON.generate(to_hash)
    log_filename = "#{Environment.get_dev_dir("log")}/Raykit.Command/#{SecureRandom.uuid}.json"
    log_dir = File.dirname(log_filename)
    FileUtils.mkdir_p(log_dir) unless Dir.exist?(log_dir)
    File.open(log_filename, "w") { |f| f.write(json) }
    if !@exitstatus.nil? && @exitstatus.zero?
      LOG.log("Raykit.Command", Logger::Severity::INFO, json)
    else
      LOG.log("Raykit.Command", Logger::Severity::ERROR, json)
    end
  rescue JSON::GeneratorError => e
    puts to_hash.to_s
    puts e.to_s
    json = JSON.generate(to_hash)
  end
end

def run

def run
  # puts '---running---'

  @start_time = Time.now
  @elapsed = 0
  timer = Timer.new
  if @timeout.zero?
    @output, @error, process_status = Open3.capture3(@command)
    @exitstatus = process_status.exitstatus
  else
    # =================================================

    #puts "@timeout is #{@timeout}"

    Open3.popen3(@command, chdir: @directory) do |_stdin, stdout, stderr, thread|
      tick = 1
      pid = thread.pid
      start = Time.now
      while ((Time.now - start) < @timeout) && thread.alive?
        Kernel.select([stdout, stderr], nil, nil, tick)
        begin
          @output << stdout.read_nonblock(BUFFER_SIZE)
          @error << stderr.read_nonblock(BUFFER_SIZE)
        rescue IO::WaitReadable
        rescue EOFError
          @exitstatus = thread.value.exitstatus
          until stdout.eof?
            @output << stdout.read_nonblock(BUFFER_SIZE)
          end
          until stderr.eof?
            @error << stderr.read_nonblock(BUFFER_SIZE)
          end
          break
        end
      end
      sleep 1
      if thread.alive?
        if Gem.win_platform?
          `taskkill /f /pid #{pid}`
        else
          Process.kill("TERM", pid)
        end
        @exitstatus = 5
        @error = +"timed out"
      else
        @exitstatus = thread.value.exitstatus
      end
    end
    # =================================================

  end
  @elapsed = timer.elapsed
  if @logging_enabled
    log
    if @exitstatus != 0
      to_log_event.to_seq
    else
      # puts '---logging---'

      unless @success_log_level.nil?
        e = to_log_event
        e["Level"] = "Verbose" if @success_log_level == "Verbose"
        e["Level"] = "Debug" if @success_log_level == Logger::DEBUG
        e["Level"] = "Information" if @success_log_level == Logger::INFO
        e["Level"] = "Warning" if @elapsed > 60 * 2
        e.to_seq
      end
    end
  end
  self
end

def save

def save
  filename = "#{Environment.get_dev_dir("log")}/Commands/#{SecureRandom.uuid}"
  log_dir = File.dirname(filename)
  FileUtils.mkdir_p(log_dir) unless Dir.exist?(log_dir)
  File.open(filename, "w") do |f|
    f.write(JSON.pretty_generate(to_hash))
  end
  self
end

def set_timeout(timeout)

def set_timeout(timeout)
  @timeout = timeout
  self
end

def summary(show_directory = true)

def summary(show_directory = true)
  checkmark = "\u2713"
  # warning="\u26A0"

  error = "\u0058"
  symbol = Rainbow(checkmark.encode("utf-8")).green
  symbol = Rainbow(error.encode("utf-8")).red if @exitstatus != 0
  cmd = "#{Rainbow(SECRETS.hide(@command)).yellow}"
  if show_directory
    puts "#{symbol} #{cmd} " + Rainbow("#{elapsed_str}").cyan
    puts Rainbow("  #{@directory}").cyan
  else
    puts "#{symbol} #{Rainbow(SECRETS.hide(@command)).yellow} (#{elapsed_str})"
  end
  self
end

def to_hash

def to_hash
  hash = {}
  hash[:command] = @command
  hash[:directory] = @directory
  hash[:timeout] = @timeout
  hash[:start_time] = @start_time
  hash[:elapsed] = @elapsed
  hash[:output] = @output.force_encoding("ISO-8859-1").encode("UTF-8")
  hash[:error] = @error.force_encoding("ISO-8859-1").encode("UTF-8")
  hash[:exitstatus] = @exitstatus
  hash[:user] = @user
  hash[:machine] = @machine
  hash[:context] = "Raykit.Command"
  hash
end

def to_log_event

def to_log_event
  secrets = Secrets.new
  msg = secrets.hide(@command)
  level = "Verbose"
  level = "Warning" if @exitstatus != 0
  output = @output
  error = @error
  output = @output[-1000..-1] if @output.length > 1200
  error = @error[-1000..-1] if @error.length > 1200
  Raykit::LogEvent.new(level, msg, {
    "SourceContext" => "Raykit::Command",
    "Category" => "Command",
    "Timeout" => @timeout,
    "Directory" => @directory,
    "Output" => output,
    "Error" => error,
    "ExitStatus" => @exitstatus,
    "Elapsed" => elapsed_str,
    "ElapsedSeconds" => @elapsed,
  })
end

def to_markdown

def to_markdown
  checkmark = "\u2713"
  error = "\u0058"
  symbol = checkmark.encode("utf-8")
  symbol = error.encode("utf-8") if @exitstatus != 0
  cmd = "#{SECRETS.hide(@command)}"
  md = "#{symbol} #{SECRETS.hide(@command)} (#{elapsed_str})"
  md
end