use std::path::PathBuf;
pub const ENV_SUBCMD: &str = "env";
pub const EXEC_SUBCMD: &str = "exec";
pub const CWD_FLAG: &str = "--cwd";
pub const WS_FLAG: &str = "--ws";
pub fn shell_quote(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('\'');
for ch in s.chars() {
if ch == '\'' {
out.push_str("'\\''");
} else {
out.push(ch);
}
}
out.push('\'');
out
}
pub fn env_script(overrides: &[(String, String)]) -> String {
let mut out = String::new();
for (key, value) in overrides {
out.push_str("export ");
out.push_str(key);
out.push('=');
out.push_str(&shell_quote(value));
out.push('\n');
}
out
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExecPlan {
pub cwd: Option<PathBuf>,
pub workspace: Option<PathBuf>,
pub cmd: String,
pub args: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ExecError {
#[error("--cwd requires a directory argument")]
MissingCwdValue,
#[error("--ws requires a workspace path")]
MissingWsValue,
#[error("no command given (usage: yog exec [--cwd DIR] [--ws WORKSPACE] <cmd> [args...])")]
MissingCommand,
#[error("yog env takes no arguments but [--ws WORKSPACE]; got {0:?}")]
UnexpectedEnvArg(String),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Leading {
pub cwd: Option<PathBuf>,
pub workspace: Option<PathBuf>,
}
fn leading(args: &[String]) -> Result<(Leading, &[String]), ExecError> {
let mut read = Leading::default();
let mut rest = args;
while let Some((flag, tail)) = rest.split_first() {
let slot = match flag.as_str() {
CWD_FLAG => &mut read.cwd,
WS_FLAG => &mut read.workspace,
_ => break,
};
let missing = if flag == CWD_FLAG {
ExecError::MissingCwdValue
} else {
ExecError::MissingWsValue
};
let (value, more) = tail.split_first().ok_or(missing)?;
*slot = Some(PathBuf::from(value));
rest = more;
}
Ok((read, rest))
}
pub fn parse_env(args: &[String]) -> Result<Leading, ExecError> {
let (read, rest) = leading(args)?;
match (rest.first(), read.cwd.is_some()) {
(Some(extra), _) => Err(ExecError::UnexpectedEnvArg(extra.clone())),
(None, true) => Err(ExecError::UnexpectedEnvArg(CWD_FLAG.to_owned())),
(None, false) => Ok(read),
}
}
pub fn overrides_for(
ambient: &crate::xdg::Env,
workspace: Option<&std::path::Path>,
) -> Vec<(String, String)> {
let mut overrides = super::overrides(ambient);
if let Some(ws) = workspace {
overrides.extend(super::wall::pairs(ambient, ws));
}
overrides
}
pub fn parse_exec(args: &[String]) -> Result<ExecPlan, ExecError> {
let (read, rest) = leading(args)?;
let (cmd, cmd_args) = rest.split_first().ok_or(ExecError::MissingCommand)?;
Ok(ExecPlan {
cwd: read.cwd,
workspace: read.workspace,
cmd: cmd.clone(),
args: cmd_args.to_vec(),
})
}
#[cfg(test)]
mod tests;