tinymist_task/
model.rs

1//! Project task models.
2
3use std::{hash::Hash, path::PathBuf};
4
5use serde::{Deserialize, Serialize};
6
7use super::{Id, Pages, PathPattern, PdfStandard, Scalar, TaskWhen};
8
9/// A project task application specifier. This is used for specifying tasks to
10/// run in a project. When the language service notifies an update event of the
11/// project, it will check whether any associated tasks need to be run.
12///
13/// Each task can have different timing and conditions for running. See
14/// [`TaskWhen`] for more information.
15///
16/// The available task types listed in the [`ProjectTask`] only represent the
17/// direct formats supported by the typst compiler. More task types can be
18/// customized by the [`ExportTransform`].
19///
20/// ## Examples
21///
22/// Export a JSON file with the pdfpc notes of the document:
23///
24/// ```bash
25/// tinymist project query main.typ --format json --selector "<pdfpc-notes>" --field value --one
26/// ```
27///
28/// Export a PDF file and then runs a ghostscript command to compress it:
29///
30/// ```bash
31/// tinymist project compile main.typ --pipe 'import "@local/postprocess:0.0.1": ghostscript; ghostscript(output.path)'
32/// ```
33#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
34#[serde(rename_all = "kebab-case", tag = "type")]
35pub struct ApplyProjectTask {
36    /// The task's ID.
37    pub id: Id,
38    /// The document's ID.
39    pub document: Id,
40    /// The task to run.
41    #[serde(flatten)]
42    pub task: ProjectTask,
43}
44
45impl ApplyProjectTask {
46    /// Returns the document's ID.
47    pub fn doc_id(&self) -> &Id {
48        &self.document
49    }
50
51    /// Returns the task's ID.
52    pub fn id(&self) -> &Id {
53        &self.id
54    }
55}
56
57/// A project task specifier. This structure specifies the arguments for a task.
58#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
59#[serde(rename_all = "kebab-case", tag = "type")]
60pub enum ProjectTask {
61    /// A preview task.
62    Preview(PreviewTask),
63    /// An export PDF task.
64    ExportPdf(ExportPdfTask),
65    /// An export PNG task.
66    ExportPng(ExportPngTask),
67    /// An export SVG task.
68    ExportSvg(ExportSvgTask),
69    /// An export HTML task.
70    ExportHtml(ExportHtmlTask),
71    /// An export HTML task.
72    ExportSvgHtml(ExportHtmlTask),
73    /// An export Markdown task.
74    ExportMd(ExportMarkdownTask),
75    /// An export TeX task.
76    ExportTeX(ExportTeXTask),
77    /// An export Text task.
78    ExportText(ExportTextTask),
79    /// An query task.
80    Query(QueryTask),
81    // todo: compatibility
82    // An export task of another type.
83    // Other(serde_json::Value),
84}
85
86impl ProjectTask {
87    /// Returns the timing of executing the task.
88    pub fn when(&self) -> Option<&TaskWhen> {
89        Some(match self {
90            Self::Preview(task) => &task.when,
91            Self::ExportPdf(..)
92            | Self::ExportPng(..)
93            | Self::ExportSvg(..)
94            | Self::ExportHtml(..)
95            | Self::ExportSvgHtml(..)
96            | Self::ExportMd(..)
97            | Self::ExportTeX(..)
98            | Self::ExportText(..)
99            | Self::Query(..) => &self.as_export()?.when,
100        })
101    }
102
103    /// Returns the export configuration of a task.
104    pub fn as_export(&self) -> Option<&ExportTask> {
105        Some(match self {
106            Self::Preview(..) => return None,
107            Self::ExportPdf(task) => &task.export,
108            Self::ExportPng(task) => &task.export,
109            Self::ExportSvg(task) => &task.export,
110            Self::ExportHtml(task) => &task.export,
111            Self::ExportSvgHtml(task) => &task.export,
112            Self::ExportTeX(task) => &task.export,
113            Self::ExportMd(task) => &task.export,
114            Self::ExportText(task) => &task.export,
115            Self::Query(task) => &task.export,
116        })
117    }
118
119    /// Returns the export configuration of a task.
120    pub fn as_export_mut(&mut self) -> Option<&mut ExportTask> {
121        Some(match self {
122            Self::Preview(..) => return None,
123            Self::ExportPdf(task) => &mut task.export,
124            Self::ExportPng(task) => &mut task.export,
125            Self::ExportSvg(task) => &mut task.export,
126            Self::ExportHtml(task) => &mut task.export,
127            Self::ExportSvgHtml(task) => &mut task.export,
128            Self::ExportTeX(task) => &mut task.export,
129            Self::ExportMd(task) => &mut task.export,
130            Self::ExportText(task) => &mut task.export,
131            Self::Query(task) => &mut task.export,
132        })
133    }
134
135    /// Returns extension of the artifact.
136    pub fn extension(&self) -> &str {
137        match self {
138            Self::ExportPdf { .. } => "pdf",
139            Self::Preview(..) | Self::ExportSvgHtml { .. } | Self::ExportHtml { .. } => "html",
140            Self::ExportMd { .. } => "md",
141            Self::ExportTeX { .. } => "tex",
142            Self::ExportText { .. } => "txt",
143            Self::ExportSvg { .. } => "svg",
144            Self::ExportPng { .. } => "png",
145            Self::Query(QueryTask {
146                format,
147                output_extension,
148                ..
149            }) => output_extension.as_deref().unwrap_or(format),
150        }
151    }
152}
153
154/// A preview task specifier.
155#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
156#[serde(rename_all = "kebab-case")]
157pub struct PreviewTask {
158    /// When to run the task. See [`TaskWhen`] for more
159    /// information.
160    pub when: TaskWhen,
161}
162
163/// An export task specifier.
164#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
165#[serde(rename_all = "kebab-case")]
166pub struct ExportTask {
167    /// When to run the task
168    pub when: TaskWhen,
169    /// The output path pattern.
170    pub output: Option<PathPattern>,
171    /// The task's transforms.
172    #[serde(skip_serializing_if = "Vec::is_empty", default)]
173    pub transform: Vec<ExportTransform>,
174}
175
176impl ExportTask {
177    /// Creates a new unmounted export task.
178    pub fn new(when: TaskWhen) -> Self {
179        Self {
180            when,
181            output: None,
182            transform: Vec::new(),
183        }
184    }
185
186    /// Pretty prints the output whenever possible.
187    pub fn apply_pretty(&mut self) {
188        self.transform
189            .push(ExportTransform::Pretty { script: None });
190    }
191}
192
193/// The legacy page selection specifier.
194#[derive(Default, Debug, Clone, Serialize, Deserialize)]
195#[serde(rename_all = "camelCase")]
196pub enum PageSelection {
197    /// Selects the first page.
198    #[default]
199    First,
200    /// Merges all pages into a single page.
201    Merged {
202        /// The gap between pages (in pt).
203        gap: Option<String>,
204    },
205}
206
207/// A project export transform specifier.
208#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
209#[serde(rename_all = "kebab-case")]
210pub enum ExportTransform {
211    /// Only pick a subset of pages.
212    Pages {
213        /// The page ranges to export.
214        ranges: Vec<Pages>,
215    },
216    /// Merge pages into a single page.
217    Merge {
218        /// The gap between pages (typst code expression, e.g. `1pt`).
219        gap: Option<String>,
220    },
221    /// Execute a transform script.
222    Script {
223        /// The postprocess script (typst script) to run.
224        #[serde(skip_serializing_if = "Option::is_none", default)]
225        script: Option<String>,
226    },
227    /// Uses a pretty printer to format the output.
228    Pretty {
229        /// The pretty command (typst script) to run.
230        ///
231        /// If not provided, the default pretty printer will be used.
232        /// Note: the builtin one may be only effective for json outputs.
233        #[serde(skip_serializing_if = "Option::is_none", default)]
234        script: Option<String>,
235    },
236}
237
238/// An export pdf task specifier.
239#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
240#[serde(rename_all = "kebab-case")]
241pub struct ExportPdfTask {
242    /// The shared export arguments.
243    #[serde(flatten)]
244    pub export: ExportTask,
245    /// One (or multiple comma-separated) PDF standards that Typst will enforce
246    /// conformance with.
247    #[serde(skip_serializing_if = "Vec::is_empty", default)]
248    pub pdf_standards: Vec<PdfStandard>,
249    /// The document's creation date formatted as a UNIX timestamp (in seconds).
250    ///
251    /// For more information, see <https://reproducible-builds.org/specs/source-date-epoch/>.
252    #[serde(skip_serializing_if = "Option::is_none", default)]
253    pub creation_timestamp: Option<i64>,
254}
255
256/// An export png task specifier.
257#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
258#[serde(rename_all = "kebab-case")]
259pub struct ExportPngTask {
260    /// The shared export arguments.
261    #[serde(flatten)]
262    pub export: ExportTask,
263    /// The PPI (pixels per inch) to use for PNG export.
264    pub ppi: Scalar,
265    /// The expression constructing background fill color (in typst script).
266    /// e.g. `#ffffff`, `#000000`, `rgba(255, 255, 255, 0.5)`.
267    ///
268    /// If not provided, the default background color specified in the document
269    /// will be used.
270    #[serde(skip_serializing_if = "Option::is_none", default)]
271    pub fill: Option<String>,
272}
273
274/// An export svg task specifier.
275#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
276#[serde(rename_all = "kebab-case")]
277pub struct ExportSvgTask {
278    /// The shared export arguments.
279    #[serde(flatten)]
280    pub export: ExportTask,
281}
282
283/// An export html task specifier.
284#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
285#[serde(rename_all = "kebab-case")]
286pub struct ExportHtmlTask {
287    /// The shared export arguments.
288    #[serde(flatten)]
289    pub export: ExportTask,
290}
291
292/// An export markdown task specifier.
293#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
294#[serde(rename_all = "kebab-case")]
295pub struct ExportMarkdownTask {
296    /// The processor to use for the markdown export.
297    pub processor: Option<String>,
298    /// The path of external assets directory.
299    pub assets_path: Option<PathBuf>,
300    /// The shared export arguments.
301    #[serde(flatten)]
302    pub export: ExportTask,
303}
304
305/// An export TeX task specifier.
306#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
307#[serde(rename_all = "kebab-case")]
308pub struct ExportTeXTask {
309    /// The processor to use for the TeX export.
310    pub processor: Option<String>,
311    /// The path of external assets directory.
312    pub assets_path: Option<PathBuf>,
313    /// The shared export arguments.
314    #[serde(flatten)]
315    pub export: ExportTask,
316}
317
318/// An export text task specifier.
319#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
320#[serde(rename_all = "kebab-case")]
321pub struct ExportTextTask {
322    /// The shared export arguments.
323    #[serde(flatten)]
324    pub export: ExportTask,
325}
326
327/// An export query task specifier.
328#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
329#[serde(rename_all = "kebab-case")]
330pub struct QueryTask {
331    /// The shared export arguments.
332    #[serde(flatten)]
333    pub export: ExportTask,
334    /// The format to serialize in. Can be `json`, `yaml`, or `txt`,
335    pub format: String,
336    /// Uses a different output extension from the one inferring from the
337    /// [`Self::format`].
338    pub output_extension: Option<String>,
339    /// Defines which elements to retrieve.
340    pub selector: String,
341    /// Extracts just one field from all retrieved elements.
342    pub field: Option<String>,
343    /// Expects and retrieves exactly one element.
344    pub one: bool,
345}