use { crate::{ config::{ Exec, context::Context, extractor::{Extractor, ExtractorError, arr, bol, opt, recover, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::{ StringParser, StringParserError, env::{EnvParser, EnvParserError}, }, }, toml::{ toml_span::{DespanExt, Span, Spanned, SpannedExt}, toml_value::Value, }, }, indexmap::IndexMap, std::sync::LazyLock, thiserror::Error, }; #[derive(Debug, Error)] pub enum ExecParserError { #[error(transparent)] Expected(#[from] UnexpectedDataType), #[error(transparent)] Extractor(#[from] ExtractorError), #[error(transparent)] String(#[from] StringParserError), #[error(transparent)] Env(#[from] EnvParserError), #[error("Array cannot be empty")] Empty, #[error("Exactly one of the `prog` or `shell` fields must be specified")] ProgXorShell, #[error("Could not read $SHELL")] ShellNotDefined, #[error("The `args` field cannot be used for shell commands")] ArgsForShell, } pub struct ExecParser<'a>(pub &'a Context<'a>); impl Parser for ExecParser<'_> { type Value = Exec; type Error = ExecParserError; const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Array, DataType::Table]; fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult { Ok(Exec { prog: string.to_string(), args: vec![], envs: vec![], privileged: false, tag: None, }) } fn parse_array(&mut self, span: Span, array: &[Spanned]) -> ParseResult { if array.is_empty() { return Err(ExecParserError::Empty.spanned(span)); } let prog = array[0].parse_map(&mut StringParser)?; let mut args = vec![]; for v in &array[1..] { args.push(v.parse_map(&mut StringParser)?); } Ok(Exec { prog, args, envs: vec![], privileged: false, tag: None, }) } fn parse_table( &mut self, span: Span, table: &IndexMap, Spanned>, ) -> ParseResult { let mut ext = Extractor::new(self.0, span, table); let (prog_opt, shell_opt, args_val, envs_val, privileged, tag) = ext.extract(( opt(str("prog")), opt(str("shell")), opt(arr("args")), opt(val("env")), recover(opt(bol("privileged"))), opt(str("tag")), ))?; let prog; let mut args = vec![]; match (prog_opt, shell_opt) { (None, None) | (Some(_), Some(_)) => { return Err(ExecParserError::ProgXorShell.spanned(span)); } (Some(v), _) => { prog = v.value.to_string(); if let Some(args_val) = args_val { for arg in args_val.value { args.push(arg.parse_map(&mut StringParser)?); } } } (_, Some(v)) => { prog = shell(v.span)?; args = vec!["-c".to_string(), v.value.to_string()]; if let Some(v) = args_val { return Err(ExecParserError::ArgsForShell.spanned(v.span)); } } } let envs = match envs_val { None => vec![], Some(e) => e.parse_map(&mut EnvParser)?, }; if let Some(privileged) = privileged && privileged.value && tag.is_some() { log::warn!( "Exec is privileged and tagged but tagged execs are always unprivileged: {}", self.0.error3(privileged.span), ); } Ok(Exec { prog, args, envs, privileged: privileged.despan().unwrap_or(false), tag: tag.despan_into(), }) } } fn shell(span: Span) -> Result> { static SHELL: LazyLock> = LazyLock::new(|| std::env::var("SHELL").ok()); SHELL .clone() .ok_or(ExecParserError::ShellNotDefined.spanned(span)) }