summaryrefslogtreecommitdiff
path: root/crates/typst-cli/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-cli/src')
-rw-r--r--crates/typst-cli/src/args.rs89
-rw-r--r--crates/typst-cli/src/compile.rs19
-rw-r--r--crates/typst-cli/src/main.rs2
-rw-r--r--crates/typst-cli/src/query.rs114
-rw-r--r--crates/typst-cli/src/watch.rs4
-rw-r--r--crates/typst-cli/src/world.rs4
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);