tinymist_task/compute/
query.rs1use std::sync::Arc;
4
5use comemo::Track;
6use ecow::EcoString;
7use tinymist_std::error::prelude::*;
8use tinymist_std::typst::TypstDocument;
9use tinymist_world::{CompilerFeat, ExportComputation, WorldComputeGraph};
10use typst::World;
11use typst::diag::{SourceResult, StrResult};
12use typst::engine::Sink;
13use typst::foundations::{Content, IntoValue, LocatableSelector, Scope, Value};
14use typst::syntax::Span;
15use typst::syntax::SyntaxMode;
16use typst_eval::eval_string;
17
18use crate::QueryTask;
19
20pub struct DocumentQuery;
22
23impl DocumentQuery {
24 pub fn retrieve<D: typst::Document>(
27 world: &dyn World,
28 selector: &str,
29 document: &D,
30 ) -> StrResult<Vec<Content>> {
31 let selector = eval_string(
32 &typst::ROUTINES,
33 world.track(),
34 Sink::new().track_mut(),
35 selector,
36 Span::detached(),
37 SyntaxMode::Code,
38 Scope::default(),
39 )
40 .map_err(|errors| {
41 let mut message = EcoString::from("failed to evaluate selector");
42 for (i, error) in errors.into_iter().enumerate() {
43 message.push_str(if i == 0 { ": " } else { ", " });
44 message.push_str(&error.message);
45 }
46 message
47 })?
48 .cast::<LocatableSelector>()
49 .map_err(|e| EcoString::from(format!("failed to cast: {}", e.message())))?;
50
51 Ok(document
52 .introspector()
53 .query(&selector.0)
54 .into_iter()
55 .collect::<Vec<_>>())
56 }
57
58 fn run_inner<F: CompilerFeat, D: typst::Document>(
59 g: &Arc<WorldComputeGraph<F>>,
60 doc: &Arc<D>,
61 config: &QueryTask,
62 ) -> Result<Vec<Value>> {
63 let selector = &config.selector;
64 let elements = Self::retrieve(&g.snap.world, selector, doc.as_ref())
65 .map_err(|e| anyhow::anyhow!("failed to retrieve: {e}"))?;
66 if config.one && elements.len() != 1 {
67 bail!("expected exactly one element, found {}", elements.len());
68 }
69
70 Ok(elements
71 .into_iter()
72 .filter_map(|c| match &config.field {
73 Some(field) => c.get_by_name(field).ok(),
74 _ => Some(c.into_value()),
75 })
76 .collect())
77 }
78
79 pub fn doc_get_as_value<F: CompilerFeat>(
81 g: &Arc<WorldComputeGraph<F>>,
82 doc: &TypstDocument,
83 config: &QueryTask,
84 ) -> Result<serde_json::Value> {
85 match doc {
86 TypstDocument::Paged(doc) => Self::get_as_value(g, doc, config),
87 TypstDocument::Html(doc) => Self::get_as_value(g, doc, config),
88 }
89 }
90
91 pub fn get_as_value<F: CompilerFeat, D: typst::Document>(
93 g: &Arc<WorldComputeGraph<F>>,
94 doc: &Arc<D>,
95 config: &QueryTask,
96 ) -> Result<serde_json::Value> {
97 let mapped = Self::run_inner(g, doc, config)?;
98
99 let res = if config.one {
100 let Some(value) = mapped.first() else {
101 bail!("no such field found for element");
102 };
103 serde_json::to_value(value)
104 } else {
105 serde_json::to_value(&mapped)
106 };
107
108 res.context("failed to serialize")
109 }
110}
111
112impl<F: CompilerFeat, D: typst::Document> ExportComputation<F, D> for DocumentQuery {
113 type Output = SourceResult<String>;
114 type Config = QueryTask;
115
116 fn run(
117 g: &Arc<WorldComputeGraph<F>>,
118 doc: &Arc<D>,
119 config: &QueryTask,
120 ) -> Result<SourceResult<String>> {
121 let pretty = false;
122 let mapped = Self::run_inner(g, doc, config)?;
123
124 let res = if config.one {
125 let Some(value) = mapped.first() else {
126 bail!("no such field found for element");
127 };
128 serialize(value, &config.format, pretty)
129 } else {
130 serialize(&mapped, &config.format, pretty)
131 };
132
133 res.map(Ok)
134 }
135}
136
137fn serialize(data: &impl serde::Serialize, format: &str, pretty: bool) -> Result<String> {
139 Ok(match format {
140 "json" if pretty => serde_json::to_string_pretty(data).context("serialize query")?,
141 "json" => serde_json::to_string(data).context("serialize query")?,
142 "yaml" => serde_yaml::to_string(&data).context_ut("serialize query")?,
143 "txt" => {
144 use serde_json::Value::*;
145 let value = serde_json::to_value(data).context("serialize query")?;
146 match value {
147 String(s) => s,
148 _ => {
149 let kind = match value {
150 Null => "null",
151 Bool(_) => "boolean",
152 Number(_) => "number",
153 String(_) => "string",
154 Array(_) => "array",
155 Object(_) => "object",
156 };
157 bail!("expected a string value for format: {format}, got {kind}")
158 }
159 }
160 }
161 _ => bail!("unsupported format for query: {format}"),
162 })
163}