summaryrefslogtreecommitdiff
path: root/crates/typst-cli/src/query.rs
diff options
context:
space:
mode:
authorBeiri22 <beier1@hs-mittweida.de>2023-08-06 23:49:04 +0200
committerGitHub <noreply@github.com>2023-08-06 23:49:04 +0200
commit357bce56f5ac701780e4fb967acce3f30ffcadf6 (patch)
treef582efe3197de94e67f5b0b356a9963a6a04fd27 /crates/typst-cli/src/query.rs
parent823fc5e5c4cb5c8555d0bb64e6f4ab4f61bce0e2 (diff)
Query-System for metadata (#1812)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
Diffstat (limited to 'crates/typst-cli/src/query.rs')
-rw-r--r--crates/typst-cli/src/query.rs114
1 files changed, 114 insertions, 0 deletions
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}"))
+ }
+ }
+}