pdfboss-cli 2.4.0

PDF command line: PDF to text, Markdown and PNG, image extraction, PDF creation, and the json/hex/q/tui explorer
//! `pdfboss meta`: update document metadata, by appending an incremental
//! update that preserves the original PDF bytes, or, with `--rewrite`, by
//! writing the whole document fresh.

use std::path::Path;

use pdfboss_core::Document;
use pdfboss_write::{rewrite_with_metadata, Metadata, Update, WriteOptions};

use crate::assemble::reject_encrypted;

/// Runs `pdfboss meta`: sets `set`'s assignments on `file`'s metadata and
/// writes the result to `out`. Appends an incremental update by default;
/// `rewrite` writes the whole document fresh instead, through
/// [`rewrite_with_metadata`]. Refuses an encrypted `file` outright, the same
/// way every other assembly command does, whether or not `password` opened
/// it: `decrypt` is the one command that deliberately strips encryption.
pub fn cmd_meta(
    file: &Path,
    out: &Path,
    set: &[String],
    rewrite: bool,
    password: &str,
) -> Result<(), String> {
    let meta = parse_assignments(set)?;
    let doc = Document::open_with_password(file, password).map_err(|e| format!("parse: {e}"))?;
    reject_encrypted(&doc, file)?;
    if rewrite {
        let bytes = rewrite_with_metadata(&doc, meta, WriteOptions::default())
            .map_err(|e| e.to_string())?;
        return std::fs::write(out, bytes).map_err(|e| format!("{}: {e}", out.display()));
    }
    let mut update = Update::new(&doc).map_err(|e| e.to_string())?;
    update.set_metadata(meta).map_err(|e| e.to_string())?;
    update.save(out).map_err(|e| e.to_string())
}

fn parse_assignments(set: &[String]) -> Result<Metadata, String> {
    let mut meta = Metadata::default();
    for pair in set {
        let (key, value) = pair
            .split_once('=')
            .ok_or_else(|| format!("expected KEY=VALUE, got {pair:?}"))?;
        let slot = match key {
            "title" => &mut meta.title,
            "author" => &mut meta.author,
            "subject" => &mut meta.subject,
            "keywords" => &mut meta.keywords,
            "creator" => &mut meta.creator,
            "producer" => &mut meta.producer,
            other => {
                return Err(format!(
                    "unknown metadata key {other:?}: valid keys are title, author, subject, keywords, creator, producer"
                ))
            }
        };
        *slot = Some(value.to_string());
    }
    Ok(meta)
}