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

Code before:
impl OutputTemplate {
    const INDEXABLE: [&'static str; 3] = ["{p}", "{0p}", "{n}"];

    fn has_indexable_template(output: &str) -> bool {
        Self::INDEXABLE.iter().any(|template| output.contains(template))
    }

    fn indexable_template() -> [&'static str; 3] {
        Self::INDEXABLE
    }

    fn format(output: &str, this_page: usize, total_pages: usize) -> StrResult<String> {
        // Find the base 10 width of number `i`
        fn width(i: usize) -> usize {
            1 + i.checked_ilog10().unwrap_or(0) as usize
        }

        let other_templates = ["{t}", "{0t}"];
        Self::INDEXABLE.iter().chain(other_templates.iter()).try_fold(
            output.to_string(),
            |out, template| {
                let replacement = match *template {
                    "{p}" => format!("{this_page}"),
                    "{0p}" | "{n}" => format!("{:01$}", this_page, width(total_pages)),
                    "{t}" | "{0t}" => format!("{total_pages}"),
                    _ => bail!("unhandled template placeholder {template}"),
                };
                Ok(out.replace(template, replacement.as_str()))
            },
        )
    }
}

/// Export to one or multiple images.
fn export_image(
    world: &mut SystemWorld,
    document: &Document,
    command: &CompileCommand,
    watching: bool,
    fmt: ImageExportFormat,
) -> StrResult<()> {
    let output = command.output();
    // Determine whether we have indexable templates in output
    let can_handle_multiple = match output {
        Output::Stdout => false,
        Output::Path(ref output) => {
            OutputTemplate::has_indexable_template(output.to_str().unwrap_or_default())
        }
    };
    if !can_handle_multiple && document.pages.len() > 1 {
        let path_err = format!(

Review comment (suggestion):
could this be `unreachable!()`? it shouldn't ever reach a user.

Code after:
    world: &mut SystemWorld,
    document: &Document,
    command: &CompileCommand,
    watching: bool,
    fmt: ImageExportFormat,
) -> StrResult<()> {
    let output = command.output();
    // Determine whether we have indexable templates in output
    let can_handle_multiple = match output {
        Output::Stdout => false,
        Output::Path(ref output) => {
            output_template::has_indexable_template(output.to_str().unwrap_or_default())
        }
    };
    if !can_handle_multiple && document.pages.len() > 1 {
        let err = match output {
            Output::Stdout => "to stdout",
            Output::Path(_) => {
                "without a page number template ({p}, {0p}) in the output path"
            }
        };
        bail!("cannot export multiple images {err}");
    }

    let cache = world.export_cache();

    // The results are collected in a `Vec<()>` which does not allocate.
    document
        .pages
        .par_iter()
        .enumerate()
        .map(|(i, page)| {
            // Use output with converted path.
            let output = match output {
                Output::Path(ref path) => {
                    let storage;
                    let path = if can_handle_multiple {
                        storage = output_template::format(
                            path.to_str().unwrap_or_default(),
                            i + 1,
                            document.pages.len(),
                        );
                        Path::new(&storage)
                    } else {
                        path
                    };

                    // If we are not watching, don't use the cache.
                    // If the frame is in the cache, skip it.
                    // If the file does not exist, always create it.
                    if watching && cache.is_cached(i, &page.frame) && path.exists() {