runtasktic 1.2.1

Command-line task management tool for execution of regular long sequential or parallel tasks.
Documentation
use anyhow::{Context, Result};
use std::fs::{File, OpenOptions};
use std::path::Path;
use std::process::{Command, Stdio};

pub trait CommandConfig {
  fn working_dir(&mut self, dir: &Option<String>) -> Result<&mut Self>;
  fn stdout_opt(&mut self, stdout: &Option<String>, inherit: bool) -> Result<&mut Self>;
  fn stderr_opt(&mut self, stdout: &Option<String>, inherit: bool) -> Result<&mut Self>;
}

impl CommandConfig for Command {
  fn working_dir(&mut self, dir: &Option<String>) -> Result<&mut Self> {
    if let Some(d) = dir {
      ensure!(
        Path::new(d).is_dir(),
        "Invalid working directory: `{}` is not a directory",
        d
      );
      Ok(self.current_dir(d))
    } else {
      Ok(self)
    }
  }

  fn stdout_opt(&mut self, stdout: &Option<String>, inherit: bool) -> Result<&mut Self> {
    let stdio = process_stdio(stdout, inherit, "runtasktic.out")?;

    Ok(self.stdout(stdio))
  }

  fn stderr_opt(&mut self, stderr: &Option<String>, inherit: bool) -> Result<&mut Self> {
    let stdio = process_stdio(stderr, inherit, "runtasktic.err")?;

    Ok(self.stderr(stdio))
  }
}

fn process_stdio(
  file: &Option<String>,
  inherit: bool,
  default_file: &'static str,
) -> Result<Stdio> {
  let res = if let Some(stdio) = file {
    match stdio.as_str() {
      "none" | "/dev/null" => Stdio::null(),
      _ => Stdio::from(open_options(stdio)?),
    }
  } else if !inherit {
    Stdio::from(open_options(default_file)?)
  } else {
    Stdio::inherit()
  };

  Ok(res)
}

fn open_options<P: AsRef<Path>>(file: P) -> Result<File> {
  OpenOptions::new()
    .create(true)
    .append(true)
    .open(file.as_ref())
    .with_context(|| format!("Can't open output file {}", file.as_ref().display()))
}