/-
Copyright (c) 2022 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Gabriel Ebner, Sebastian Ullrich, Mac Malone
-/
import Lake.Util.Log

namespace Lake

@[specialize] def logProcWith [Monad m]
(args : IO.Process.SpawnArgs) (out : IO.Process.Output)  (log : String → m PUnit) : m Unit := do
  let envStr := String.join <| args.env.toList.filterMap fun (k, v) =>
    if k == "PATH" then none else some s!"{k}={v.getD ""} " -- PATH too big
  let cmdStr := " ".intercalate (args.cmd :: args.args.toList)
  log <| "> " ++ envStr ++
    match args.cwd with
    | some cwd => s!"{cmdStr}    # in directory {cwd}"
    | none     => cmdStr
  unless out.stdout.isEmpty do
    log s!"stdout:\n{out.stdout}"
  unless out.stderr.isEmpty do
    log s!"stderr:\n{out.stderr}"

def proc (args : IO.Process.SpawnArgs) : LogIO Unit := do
  match (← IO.Process.output args |>.toBaseIO) with
  | .ok out =>
    if out.exitCode = 0 then
      logProcWith args out logVerbose
    else
      logProcWith args out logError
      error s!"external command `{args.cmd}` exited with code {out.exitCode}"
  | .error err =>
    error s!"failed to execute `{args.cmd}`: {err}"

def captureProc (args : IO.Process.SpawnArgs) : LogIO String := do
  match (← IO.Process.output args |>.toBaseIO) with
  | .ok out =>
    if out.exitCode = 0 then
      return out.stdout.trim -- remove, e.g., newline at end
    else
      logProcWith args out logError
      error s!"external command `{args.cmd}` exited with code {out.exitCode}"
  | .error err =>
    error s!"failed to execute `{args.cmd}`: {err}"

def captureProc? (args : IO.Process.SpawnArgs) : BaseIO (Option String) := do
  EIO.catchExceptions (h := fun _ => pure none) do
    let out ← IO.Process.output args
    if out.exitCode = 0 then
      return some out.stdout.trim -- remove, e.g., newline at end
    else
      return none

def testProc (args : IO.Process.SpawnArgs) : BaseIO Bool :=
  EIO.catchExceptions (h := fun _ => pure false) do
    let child ← IO.Process.spawn {
      args with
      stdout := IO.Process.Stdio.null
      stderr := IO.Process.Stdio.null
    }
    return (← child.wait) == 0
