Repository: typst/typst
File: crates/typst-cli/src/args.rs (Rust)

Code before:
pub struct SharedArgs {
    /// Path to input Typst file
    pub input_file: PathBuf,

    /// Configures the project root (for absolute paths)
    #[clap(long = "root", env = "TYPST_ROOT", value_name = "DIR")]
    pub root: Option<PathBuf>,

    /// Adds additional directories to search for fonts
    #[clap(
        long = "font-path",
        env = "TYPST_FONT_PATHS",
        value_name = "DIR",
        value_delimiter = ENV_PATH_SEP,
    )]
    pub font_paths: Vec<PathBuf>,

    /// The format to emit diagnostics in
    #[clap(
        long,
        default_value_t = DiagnosticFormat::Human,
        value_parser = clap::value_parser!(DiagnosticFormat)
    )]
    pub diagnostic_format: DiagnosticFormat,

    /// Add an input value visible through sys.inputs
    #[clap(
        long = "input",
        short = 'i',
        value_name = "KEY=VALUE",
        action = ArgAction::Append,
        value_parser = ValueParser::new(parse_input_pair),
    )]
    pub plain_inputs: Vec<(String, String)>,
}

fn parse_input_pair(raw: &str) -> Result<(String, String), InputPairError> {
    let (key, val) = raw.split_once('=').ok_or(InputPairError::NoEqual)?;
    let key = key.trim().to_owned();
    if key.is_empty() {
        return Err(InputPairError::MissingKey);
    }
    let val = val.trim().to_owned();
    Ok((key, val))
}

/// An error that occurred while parsing an input argument
#[derive(Clone, Debug)]
enum InputPairError {
    /// Missing equal sign in the value
    NoEqual,

Review comment (suggestion):
It should probably clarify that this will be a string value, i.e, something like `-i var=int(10)` will be `"int(10)"`, not `10`.

Code after:
pub struct SharedArgs {
    /// Path to input Typst file
    pub input: PathBuf,

    /// Configures the project root (for absolute paths)
    #[clap(long = "root", env = "TYPST_ROOT", value_name = "DIR")]
    pub root: Option<PathBuf>,

    /// Add a string key-value pair visible through `sys.inputs`
    #[clap(
        long = "input",
        value_name = "key=value",
        action = ArgAction::Append,
        value_parser = ValueParser::new(parse_input_pair),
    )]
    pub inputs: Vec<(String, String)>,

    /// Adds additional directories to search for fonts
    #[clap(
        long = "font-path",
        env = "TYPST_FONT_PATHS",
        value_name = "DIR",
        value_delimiter = ENV_PATH_SEP,
    )]
    pub font_paths: Vec<PathBuf>,

    /// The format to emit diagnostics in
    #[clap(
        long,
        default_value_t = DiagnosticFormat::Human,
        value_parser = clap::value_parser!(DiagnosticFormat)
    )]
    pub diagnostic_format: DiagnosticFormat,
}

/// Parses key/value pairs split by the first equal sign.
///
/// This function will return an error if the argument contains no equals sign
/// or contains the key (before the equals sign) is empty.
fn parse_input_pair(raw: &str) -> Result<(String, String), String> {
    let (key, val) = raw
        .split_once('=')
        .ok_or("input must be a key and a value separated by an equal sign")?;
    let key = key.trim().to_owned();
    if key.is_empty() {
        return Err("the key was missing or empty".to_owned());
    }
    let val = val.trim().to_owned();
    Ok((key, val))
}