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

Code before:
    /// 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
    /// whose type is always treated as a string
    /// which you may enclose with single or double quotes
    #[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

Review comment (suggestion):
Two things:
- I'm not sure how longer comments render in clap. If it looks okay, it's fine, but this is what has stopped me so far from adding too much docs here.
- The sentence doesn't read well with the "whose" and "which". If went want to say all of this, it should be multiple sentences. 
- While I see the benefit of saying that everything is a string, I don't think we need to mention the quotes thing, that's just how shell works after all 

Overall, I feel like this could just be:
```
/// Add an input string visible through `sys.inputs`
```



Code after:
    /// 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))
}

/// Lists all discovered fonts in system and custom font paths