diff options
Diffstat (limited to 'crates/typst-cli/src')
| -rw-r--r-- | crates/typst-cli/src/args.rs | 89 | ||||
| -rw-r--r-- | crates/typst-cli/src/compile.rs | 19 | ||||
| -rw-r--r-- | crates/typst-cli/src/main.rs | 2 | ||||
| -rw-r--r-- | crates/typst-cli/src/query.rs | 114 | ||||
| -rw-r--r-- | crates/typst-cli/src/watch.rs | 4 | ||||
| -rw-r--r-- | crates/typst-cli/src/world.rs | 4 |
6 files changed, 196 insertions, 36 deletions
diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index 55cfe8ad..0d88df9b 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Display, Formatter}; use std::path::PathBuf; -use clap::{ArgAction, Parser, Subcommand, ValueEnum}; +use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum}; /// The Typst compiler. #[derive(Debug, Clone, Parser)] @@ -29,6 +29,9 @@ pub enum Command { #[command(visible_alias = "w")] Watch(CompileCommand), + /// Processes an input file to extract provided metadata + Query(QueryCommand), + /// Lists all discovered fonts in system and custom font paths Fonts(FontsCommand), } @@ -36,12 +39,71 @@ pub enum Command { /// Compiles the input file into a PDF file #[derive(Debug, Clone, Parser)] pub struct CompileCommand { - /// Path to input Typst file - pub input: PathBuf, + /// Shared arguments. + #[clap(flatten)] + pub common: SharedArgs, /// Path to output PDF file or PNG file(s) pub output: Option<PathBuf>, + /// Opens the output file using the default viewer after compilation + #[arg(long = "open")] + pub open: Option<Option<String>>, + + /// The PPI (pixels per inch) to use for PNG export + #[arg(long = "ppi", default_value_t = 144.0)] + pub ppi: f32, + + /// Produces a flamegraph of the compilation process + #[arg(long = "flamegraph", value_name = "OUTPUT_SVG")] + pub flamegraph: Option<Option<PathBuf>>, +} + +impl CompileCommand { + /// The output path. + pub fn output(&self) -> PathBuf { + self.output + .clone() + .unwrap_or_else(|| self.common.input.with_extension("pdf")) + } +} + +/// Processes an input file to extract provided metadata +#[derive(Debug, Clone, Parser)] +pub struct QueryCommand { + /// Shared arguments. + #[clap(flatten)] + pub common: SharedArgs, + + /// Define what elements to retrieve + pub selector: String, + + /// Extract just one field from all retrieved elements + #[clap(long = "field")] + pub field: Option<String>, + + /// Expect and retrieve exactly one element + #[clap(long = "one", default_value = "false")] + pub one: bool, + + /// The format to serialization in + #[clap(long = "format", default_value = "json")] + pub format: SerializationFormat, +} + +// Output file format for query command +#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)] +pub enum SerializationFormat { + Json, + Yaml, +} + +/// Common arguments of compile, watch, and query. +#[derive(Debug, Clone, Args)] +pub struct SharedArgs { + /// Path to input Typst file + pub input: PathBuf, + /// Configures the project root #[clap(long = "root", env = "TYPST_ROOT", value_name = "DIR")] pub root: Option<PathBuf>, @@ -55,14 +117,6 @@ pub struct CompileCommand { )] pub font_paths: Vec<PathBuf>, - /// Opens the output file using the default viewer after compilation - #[arg(long = "open")] - pub open: Option<Option<String>>, - - /// The PPI (pixels per inch) to use for PNG export - #[arg(long = "ppi", default_value_t = 144.0)] - pub ppi: f32, - /// In which format to emit diagnostics #[clap( long, @@ -70,19 +124,6 @@ pub struct CompileCommand { value_parser = clap::value_parser!(DiagnosticFormat) )] pub diagnostic_format: DiagnosticFormat, - - /// Produces a flamegraph of the compilation process - #[arg(long = "flamegraph", value_name = "OUTPUT_SVG")] - pub flamegraph: Option<Option<PathBuf>>, -} - -impl CompileCommand { - /// The output path. - pub fn output(&self) -> PathBuf { - self.output - .clone() - .unwrap_or_else(|| self.input.with_extension("pdf")) - } } /// Lists all discovered fonts in system and custom font paths diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index 2cce13e1..ca088a76 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -21,7 +21,7 @@ type CodespanError = codespan_reporting::files::Error; /// Execute a compilation command. pub fn compile(mut command: CompileCommand) -> StrResult<()> { - let mut world = SystemWorld::new(&command)?; + let mut world = SystemWorld::new(&command.common)?; compile_once(&mut world, &mut command, false)?; Ok(()) } @@ -42,14 +42,12 @@ pub fn compile_once( Status::Compiling.print(command).unwrap(); } - // Reset everything and ensure that the main file is still present. + // Reset everything and ensure that the main file is present. world.reset(); world.source(world.main()).map_err(|err| err.to_string())?; let mut tracer = Tracer::default(); - let result = typst::compile(world, &mut tracer); - let warnings = tracer.warnings(); match result { @@ -67,7 +65,7 @@ pub fn compile_once( } } - print_diagnostics(world, &[], &warnings, command.diagnostic_format) + print_diagnostics(world, &[], &warnings, command.common.diagnostic_format) .map_err(|_| "failed to print diagnostics")?; if let Some(open) = command.open.take() { @@ -84,8 +82,13 @@ pub fn compile_once( Status::Error.print(command).unwrap(); } - print_diagnostics(world, &errors, &warnings, command.diagnostic_format) - .map_err(|_| "failed to print diagnostics")?; + print_diagnostics( + world, + &errors, + &warnings, + command.common.diagnostic_format, + ) + .map_err(|_| "failed to print diagnostics")?; } } @@ -152,7 +155,7 @@ fn open_file(open: Option<&str>, path: &Path) -> StrResult<()> { } /// Print diagnostic messages to the terminal. -fn print_diagnostics( +pub fn print_diagnostics( world: &SystemWorld, errors: &[SourceDiagnostic], warnings: &[SourceDiagnostic], diff --git a/crates/typst-cli/src/main.rs b/crates/typst-cli/src/main.rs index 425d05fd..62f14566 100644 --- a/crates/typst-cli/src/main.rs +++ b/crates/typst-cli/src/main.rs @@ -2,6 +2,7 @@ mod args; mod compile; mod fonts; mod package; +mod query; mod tracing; mod watch; mod world; @@ -36,6 +37,7 @@ fn main() -> ExitCode { let res = match arguments.command { Command::Compile(command) => crate::compile::compile(command), Command::Watch(command) => crate::watch::watch(command), + Command::Query(command) => crate::query::query(command), Command::Fonts(command) => crate::fonts::fonts(command), }; diff --git a/crates/typst-cli/src/query.rs b/crates/typst-cli/src/query.rs new file mode 100644 index 00000000..faf4e01b --- /dev/null +++ b/crates/typst-cli/src/query.rs @@ -0,0 +1,114 @@ +use comemo::Track; +use serde::Serialize; +use typst::diag::{bail, StrResult}; +use typst::eval::{eval_string, EvalMode, Tracer}; +use typst::model::Introspector; +use typst::World; +use typst_library::prelude::*; + +use crate::args::{QueryCommand, SerializationFormat}; +use crate::compile::print_diagnostics; +use crate::set_failed; +use crate::world::SystemWorld; + +/// Execute a query command. +pub fn query(command: QueryCommand) -> StrResult<()> { + let mut world = SystemWorld::new(&command.common)?; + tracing::info!("Starting querying"); + + // Reset everything and ensure that the main file is present. + world.reset(); + world.source(world.main()).map_err(|err| err.to_string())?; + + let mut tracer = Tracer::default(); + let result = typst::compile(&world, &mut tracer); + let warnings = tracer.warnings(); + + match result { + // Retrieve and print query results. + Ok(document) => { + let data = retrieve(&world, &command, &document)?; + let serialized = format(data, &command)?; + println!("{serialized}"); + print_diagnostics(&world, &[], &warnings, command.common.diagnostic_format) + .map_err(|_| "failed to print diagnostics")?; + } + + // Print diagnostics. + Err(errors) => { + set_failed(); + print_diagnostics( + &world, + &errors, + &warnings, + command.common.diagnostic_format, + ) + .map_err(|_| "failed to print diagnostics")?; + } + } + + Ok(()) +} + +/// Retrieve the matches for the selector. +fn retrieve( + world: &dyn World, + command: &QueryCommand, + document: &Document, +) -> StrResult<Vec<Content>> { + let selector = eval_string( + world.track(), + &command.selector, + Span::detached(), + EvalMode::Code, + Scope::default(), + ) + .map_err(|errors| { + let mut message = EcoString::from("failed to evaluate selector"); + for (i, error) in errors.into_iter().enumerate() { + message.push_str(if i == 0 { ": " } else { ", " }); + message.push_str(&error.message); + } + message + })? + .cast::<LocatableSelector>()?; + + Ok(Introspector::new(&document.pages) + .query(&selector.0) + .into_iter() + .map(|x| x.into_inner()) + .collect::<Vec<_>>()) +} + +/// Format the query result in the output format. +fn format(elements: Vec<Content>, command: &QueryCommand) -> StrResult<String> { + if command.one && elements.len() != 1 { + bail!("expected exactly one element, found {}", elements.len()) + } + + let mapped: Vec<_> = elements + .into_iter() + .filter_map(|c| match &command.field { + Some(field) => c.field(field), + _ => Some(c.into_value()), + }) + .collect(); + + if command.one { + serialize(&mapped[0], command.format) + } else { + serialize(&mapped, command.format) + } +} + +/// Serialize data to the output format. +fn serialize(data: &impl Serialize, format: SerializationFormat) -> StrResult<String> { + match format { + SerializationFormat::Json => { + serde_json::to_string_pretty(data).map_err(|e| eco_format!("{e}")) + } + SerializationFormat::Yaml => { + serde_yaml::to_string(&data).map_err(|e| eco_format!("{e}")) + } + } +} diff --git a/crates/typst-cli/src/watch.rs b/crates/typst-cli/src/watch.rs index 759d27ec..b320c651 100644 --- a/crates/typst-cli/src/watch.rs +++ b/crates/typst-cli/src/watch.rs @@ -17,7 +17,7 @@ use crate::world::SystemWorld; /// Execute a watching compilation command. pub fn watch(mut command: CompileCommand) -> StrResult<()> { // Create the world that serves sources, files, and fonts. - let mut world = SystemWorld::new(&command)?; + let mut world = SystemWorld::new(&command.common)?; // Perform initial compilation. compile_once(&mut world, &mut command, true)?; @@ -159,7 +159,7 @@ impl Status { w.set_color(&color)?; write!(w, "watching")?; w.reset()?; - writeln!(w, " {}", command.input.display())?; + writeln!(w, " {}", command.common.input.display())?; w.set_color(&color)?; write!(w, "writing to")?; diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs index 2c0ee7d0..fb8fc0c7 100644 --- a/crates/typst-cli/src/world.rs +++ b/crates/typst-cli/src/world.rs @@ -15,7 +15,7 @@ use typst::syntax::{FileId, Source}; use typst::util::PathExt; use typst::World; -use crate::args::CompileCommand; +use crate::args::SharedArgs; use crate::fonts::{FontSearcher, FontSlot}; use crate::package::prepare_package; @@ -44,7 +44,7 @@ pub struct SystemWorld { impl SystemWorld { /// Create a new system world. - pub fn new(command: &CompileCommand) -> StrResult<Self> { + pub fn new(command: &SharedArgs) -> StrResult<Self> { let mut searcher = FontSearcher::new(); searcher.search(&command.font_paths); |
