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