#![warn(clippy::pedantic, missing_docs, clippy::cargo)]
#![allow(clippy::missing_errors_doc)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::io::Result;
use std::path::{Path, PathBuf};
use std::process::{Child, Command as StdCommand, ExitStatus, Output, Stdio as StdStdio};
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Stdio {
Piped,
Inherit,
Null,
}
impl Stdio {
#[must_use]
pub fn to_std(&self) -> StdStdio {
self.into()
}
}
impl From<Stdio> for StdStdio {
fn from(value: Stdio) -> Self {
match value {
Stdio::Inherit => StdStdio::inherit(),
Stdio::Piped => StdStdio::piped(),
Stdio::Null => StdStdio::null(),
}
}
}
impl From<&Stdio> for StdStdio {
fn from(value: &Stdio) -> Self {
match value {
Stdio::Inherit => StdStdio::inherit(),
Stdio::Piped => StdStdio::piped(),
Stdio::Null => StdStdio::null(),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Command {
pub name: OsString,
pub arguments: Vec<OsString>,
pub inherit_environment: bool,
pub environment: HashMap<OsString, Option<OsString>>,
pub current_dir: Option<PathBuf>,
pub stdin: Option<Stdio>,
pub stdout: Option<Stdio>,
pub stderr: Option<Stdio>,
}
impl Command {
#[must_use]
pub fn new(name: impl AsRef<OsStr>) -> Self {
Self {
name: name.as_ref().to_owned(),
arguments: Vec::new(),
inherit_environment: true,
environment: HashMap::new(),
current_dir: None,
stdin: None,
stdout: None,
stderr: None,
}
}
#[must_use]
pub fn arg(mut self, arg: impl AsRef<OsStr>) -> Self {
self.add_arg(arg);
self
}
#[must_use]
pub fn args(mut self, args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Self {
self.add_args(args);
self
}
#[must_use]
pub fn env(mut self, key: impl AsRef<OsStr>, val: impl AsRef<OsStr>) -> Self {
self.set_env(key, val);
self
}
#[must_use]
pub fn envs(
mut self,
vars: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
) -> Self {
self.set_envs(vars);
self
}
#[must_use]
pub fn env_remove(mut self, key: impl AsRef<OsStr>) -> Self {
self.remove_env(key);
self
}
#[must_use]
pub fn env_clear(mut self) -> Self {
self.environment.clear();
self.inherit_environment = false;
self
}
#[must_use]
pub fn env_no_inherit(mut self) -> Self {
self.inherit_environment = false;
self
}
#[must_use]
pub fn current_dir(mut self, key: impl AsRef<Path>) -> Self {
self.set_current_dir(key);
self
}
#[must_use]
pub fn stdin(mut self, stdin: Stdio) -> Self {
self.stdin = Some(stdin);
self
}
#[must_use]
pub fn stdout(mut self, stdout: Stdio) -> Self {
self.stdout = Some(stdout);
self
}
#[must_use]
pub fn stderr(mut self, stderr: Stdio) -> Self {
self.stderr = Some(stderr);
self
}
}
impl Command {
pub fn add_arg(&mut self, arg: impl AsRef<OsStr>) {
self.arguments.push(arg.as_ref().to_owned());
}
pub fn add_args(&mut self, args: impl IntoIterator<Item = impl AsRef<OsStr>>) {
self.arguments
.extend(args.into_iter().map(|i| i.as_ref().to_owned()));
}
pub fn set_env(&mut self, key: impl AsRef<OsStr>, val: impl AsRef<OsStr>) {
self.environment
.insert(key.as_ref().to_owned(), Some(val.as_ref().to_owned()));
}
pub fn set_envs(
&mut self,
vars: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
) {
self.environment.extend(
vars.into_iter()
.map(|(k, v)| (k.as_ref().to_owned(), Some(v.as_ref().to_owned()))),
);
}
pub fn remove_env(&mut self, key: impl AsRef<OsStr>) {
self.environment.remove(key.as_ref());
}
pub fn set_current_dir(&mut self, path: impl AsRef<Path>) {
self.current_dir = Some(path.as_ref().to_owned());
}
}
impl From<&Command> for StdCommand {
fn from(
Command {
name,
arguments: args,
inherit_environment: inherit_env,
environment: env,
current_dir,
stdin,
stdout,
stderr,
}: &Command,
) -> Self {
let mut command = StdCommand::new(name);
if *inherit_env {
for (removed, _) in env.iter().filter(|i| i.1.is_none()) {
command.env_remove(removed);
}
} else {
command.env_clear();
}
command
.args(args)
.envs(env.iter().filter_map(|(k, v)| v.as_ref().map(|v| (k, v))));
current_dir
.as_ref()
.map(|current_dir| command.current_dir(current_dir));
stdin.map(|stdin| command.stdin(stdin));
stdout.map(|stdout| command.stdout(stdout));
stderr.map(|stderr| command.stderr(stderr));
command
}
}
impl Command {
pub fn spawn(&self) -> Result<Child> {
StdCommand::from(self).spawn()
}
pub fn output(&self) -> Result<Output> {
StdCommand::from(self).output()
}
pub fn status(&self) -> Result<ExitStatus> {
StdCommand::from(self).status()
}
#[must_use]
pub fn to_std(&self) -> StdCommand {
self.into()
}
}