runcc 2.0.3

run commands concurrently with rust and cargo
Documentation
use serde::{Deserialize, Serialize};

use crate::env::match_program_with_envs;

#[non_exhaustive]
#[derive(Deserialize, Serialize, Debug)]
pub struct CommandConfig {
    pub program: String,
    pub args: Option<Vec<String>>,
    pub label: Option<String>,
    pub envs: Option<Vec<(String, String)>>,
    pub cwd: Option<String>,
}

#[non_exhaustive]
#[derive(Debug, Default)]
pub struct CommandConfigFromScriptOptions {
    pub windows_call_cmd_with_env: super::WindowsCallCmdWithEnv,
}

macro_rules! def_into_command_and_label {
    ($name:ident -> $cmd_type:ty) => {
        pub fn $name<I, K, V>(self, inherited_envs: Option<I>) -> ($cmd_type, String)
        where
            I: IntoIterator<Item = (K, V)>,
            K: AsRef<std::ffi::OsStr>,
            V: AsRef<std::ffi::OsStr>,
        {
            let Self {
                program,
                args,
                label,
                envs,
                cwd,
            } = self;

            let mut command = <$cmd_type>::new(&program);

            if let Some(cwd) = cwd {
                command.current_dir(cwd);
            }

            if let Some(args) = &args {
                command.args(args);
            }

            if let Some(envs) = inherited_envs {
                command.envs(envs);
            }

            if let Some(envs) = envs {
                command.envs(envs);
            }

            let label = label.unwrap_or_else(move || {
                if let Some(args) = args {
                    format!("{} {}", program, args.join(" "))
                } else {
                    program
                }
            });

            (command, label)
        }
    };
}

impl CommandConfig {
    pub fn from_script(script: &str, options: &CommandConfigFromScriptOptions) -> CommandConfig {
        let script = script.trim();

        let (program, envs) = match_program_with_envs(script);

        let program = program.to_string();
        let envs = if let Some(envs) = envs {
            Some(
                envs.into_iter()
                    .map(|(k, v)| (k.to_string(), v.to_string()))
                    .collect(),
            )
        } else {
            None
        };

        if cfg!(target_os = "windows") {
            let with_env = &options.windows_call_cmd_with_env;

            let env_name = with_env.clone().try_into_env_name();

            let (arg, env) = match env_name {
                Some(env_name) => (format!("%{}%", env_name), Some((env_name, program.clone()))),
                None => (program.clone(), None),
            };

            let mut cmd = CommandConfig {
                program: "cmd".to_string(),
                args: Some(vec!["/C".to_string(), arg]),
                label: Some(program.clone()),
                envs,
                cwd: None,
            };

            if let Some(env) = env {
                cmd.env(env);
            }

            cmd
        } else {
            CommandConfig {
                program: "sh".to_string(),
                args: Some(vec!["-c".to_string(), program.clone()]),
                label: Some(program),
                envs,
                cwd: None,
            }
        }
    }

    pub fn from_program_args(program: String, args: Option<Vec<String>>) -> CommandConfig {
        CommandConfig {
            program,
            args,
            label: None,
            envs: None,
            cwd: None,
        }
    }

    pub fn label_length(&self) -> usize {
        match &self.label {
            None => {
                self.program.len()
                    + self
                        .args
                        .as_ref()
                        .map_or(0, |args| args.iter().map(|arg| arg.len() + 1).sum())
            }
            Some(label) => label.len(),
        }
    }

    pub fn env(&mut self, env: (String, String)) -> &mut Self {
        self.envs.get_or_insert_with(|| vec![]).push(env);
        self
    }

    def_into_command_and_label! {into_command_and_label->std::process::Command}

    def_into_command_and_label! {into_tokio_command_and_label->tokio::process::Command}
}