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