use std::path::PathBuf;
use std::process::ExitCode;
use clap::{Parser, Subcommand};
use typstgen::{CompileOptions, Config, PageRange, PdfStandard};
#[derive(Parser)]
#[command(name = "typstgen")]
#[command(version)]
#[command(about = "Compile .typ files to PDF with an embedded Typst engine")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Compile {
input: PathBuf,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(short, long)]
config: Option<PathBuf>,
#[arg(long = "input", value_name = "KEY=VALUE", value_parser = parse_input)]
inputs: Vec<(String, String)>,
#[arg(long = "pdf-standard", value_name = "STANDARD", value_delimiter = ',')]
pdf_standards: Vec<PdfStandard>,
#[arg(long, value_name = "UNIX_SECONDS", env = "SOURCE_DATE_EPOCH")]
creation_timestamp: Option<i64>,
#[arg(
long,
value_name = "PAGES",
value_delimiter = ',',
allow_hyphen_values = true
)]
pages: Vec<PageRange>,
#[arg(long)]
no_pdf_tags: bool,
},
}
fn main() -> ExitCode {
let cli = Cli::parse();
match cli.command {
Commands::Compile {
input,
output,
config,
inputs,
pdf_standards,
creation_timestamp,
pages,
no_pdf_tags,
} => {
let options = CompileOptions {
inputs: inputs.into_iter().collect(),
pdf_standards,
creation_timestamp,
pages,
pdf_tags: !no_pdf_tags,
};
let config = match Config::load(config.as_deref()) {
Ok(config) => config,
Err(err) => {
eprintln!("error: {err}");
return ExitCode::FAILURE;
}
};
match typstgen::compile_with_options(&input, &config, &options) {
Ok(compiled) => {
for warning in &compiled.warnings {
eprintln!("{warning}");
}
let output = output.unwrap_or_else(|| input.with_extension("pdf"));
if let Err(err) = std::fs::write(&output, compiled.pdf) {
eprintln!("error: failed to write {}: {err}", output.display());
return ExitCode::FAILURE;
}
println!("{}", output.display());
ExitCode::SUCCESS
}
Err(err) => {
eprintln!("error: {err}");
ExitCode::FAILURE
}
}
}
}
}
fn parse_input(raw: &str) -> Result<(String, String), String> {
match raw.split_once('=') {
Some((key, value)) if !key.is_empty() => Ok((key.to_owned(), value.to_owned())),
_ => Err(format!("expected KEY=VALUE, got {raw:?}")),
}
}