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/// A page merge specifier.
194#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
195#[serde(default)]
196pub struct PageMerge {
197    /// The gap between pages (in pt).
198    pub gap: Option<String>,
199}
200
201/// A project export transform specifier.
202#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
203#[serde(rename_all = "kebab-case")]
204pub enum ExportTransform {
205    /// Only pick a subset of pages.
206    Pages {
207        /// The page ranges to export.
208        ranges: Vec<Pages>,
209    },
210    /// Merge pages into a single page.
211    Merge {
212        /// The gap between pages (typst code expression, e.g. `1pt`).
213        gap: Option<String>,
214    },
215    /// Execute a transform script.
216    Script {
217        /// The postprocess script (typst script) to run.
218        #[serde(skip_serializing_if = "Option::is_none", default)]
219        script: Option<String>,
220    },
221    /// Uses a pretty printer to format the output.
222    Pretty {
223        /// The pretty command (typst script) to run.
224        ///
225        /// If not provided, the default pretty printer will be used.
226        /// Note: the builtin one may be only effective for json outputs.
227        #[serde(skip_serializing_if = "Option::is_none", default)]
228        script: Option<String>,
229    },
230}
231
232/// An export pdf task specifier.
233#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
234#[serde(rename_all = "kebab-case")]
235pub struct ExportPdfTask {
236    /// The shared export arguments.
237    #[serde(flatten)]
238    pub export: ExportTask,
239    /// Which pages to export. When unspecified, all pages are exported.
240    #[serde(skip_serializing_if = "Option::is_none", default)]
241    pub pages: Option<Vec<Pages>>,
242    /// One (or multiple comma-separated) PDF standards that Typst will enforce
243    /// conformance with.
244    #[serde(skip_serializing_if = "Vec::is_empty", default)]
245    pub pdf_standards: Vec<PdfStandard>,
246    /// By default, even when not producing a `PDF/UA-1` document, a tagged PDF
247    /// document is written to provide a baseline of accessibility. In some
248    /// circumstances (for example when trying to reduce the size of a document)
249    /// it can be desirable to disable tagged PDF.
250    #[serde(skip_serializing_if = "std::ops::Not::not", default)]
251    pub no_pdf_tags: bool,
252    /// The document's creation date formatted as a UNIX timestamp (in seconds).
253    ///
254    /// For more information, see <https://reproducible-builds.org/specs/source-date-epoch/>.
255    #[serde(skip_serializing_if = "Option::is_none", default)]
256    pub creation_timestamp: Option<i64>,
257}
258
259/// An export png task specifier.
260#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
261#[serde(rename_all = "kebab-case")]
262pub struct ExportPngTask {
263    /// The shared export arguments.
264    #[serde(flatten)]
265    pub export: ExportTask,
266    /// Which pages to export. When unspecified, all pages are exported.
267    #[serde(skip_serializing_if = "Option::is_none", default)]
268    pub pages: Option<Vec<Pages>>,
269    /// The page template to use for multiple pages.
270    #[serde(skip_serializing_if = "Option::is_none", default)]
271    pub page_number_template: Option<String>,
272    /// The page merge specifier.
273    #[serde(skip_serializing_if = "Option::is_none", default)]
274    pub merge: Option<PageMerge>,
275    /// The PPI (pixels per inch) to use for PNG export.
276    pub ppi: Scalar,
277    /// The expression constructing background fill color (in typst script).
278    /// e.g. `#ffffff`, `#000000`, `rgba(255, 255, 255, 0.5)`.
279    ///
280    /// If not provided, the default background color specified in the document
281    /// will be used.
282    #[serde(skip_serializing_if = "Option::is_none", default)]
283    pub fill: Option<String>,
284}
285
286/// An export svg task specifier.
287#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
288#[serde(rename_all = "kebab-case")]
289pub struct ExportSvgTask {
290    /// The shared export arguments.
291    #[serde(flatten)]
292    pub export: ExportTask,
293    /// The page template to use for multiple pages.
294    #[serde(skip_serializing_if = "Option::is_none", default)]
295    pub page_number_template: Option<String>,
296    /// Which pages to export. When unspecified, all pages are exported.
297    #[serde(skip_serializing_if = "Option::is_none", default)]
298    pub pages: Option<Vec<Pages>>,
299    /// The page merge specifier.
300    #[serde(skip_serializing_if = "Option::is_none", default)]
301    pub merge: Option<PageMerge>,
302}
303
304/// An export html task specifier.
305#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
306#[serde(rename_all = "kebab-case")]
307pub struct ExportHtmlTask {
308    /// The shared export arguments.
309    #[serde(flatten)]
310    pub export: ExportTask,
311}
312
313/// An export markdown task specifier.
314#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
315#[serde(rename_all = "kebab-case")]
316pub struct ExportMarkdownTask {
317    /// The processor to use for the markdown export.
318    pub processor: Option<String>,
319    /// The path of external assets directory.
320    pub assets_path: Option<PathBuf>,
321    /// The shared export arguments.
322    #[serde(flatten)]
323    pub export: ExportTask,
324}
325
326/// An export TeX task specifier.
327#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
328#[serde(rename_all = "kebab-case")]
329pub struct ExportTeXTask {
330    /// The processor to use for the TeX export.
331    pub processor: Option<String>,
332    /// The path of external assets directory.
333    pub assets_path: Option<PathBuf>,
334    /// The shared export arguments.
335    #[serde(flatten)]
336    pub export: ExportTask,
337}
338
339/// An export text task specifier.
340#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
341#[serde(rename_all = "kebab-case")]
342pub struct ExportTextTask {
343    /// The shared export arguments.
344    #[serde(flatten)]
345    pub export: ExportTask,
346}
347
348/// An export query task specifier.
349#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
350#[serde(rename_all = "kebab-case")]
351pub struct QueryTask {
352    /// The shared export arguments.
353    #[serde(flatten)]
354    pub export: ExportTask,
355    /// The format to serialize in. Can be `json`, `yaml`, or `txt`,
356    pub format: String,
357    /// Uses a different output extension from the one inferring from the
358    /// [`Self::format`].
359    pub output_extension: Option<String>,
360    /// Defines which elements to retrieve.
361    pub selector: String,
362    /// Extracts just one field from all retrieved elements.
363    pub field: Option<String>,
364    /// Expects and retrieves exactly one element.
365    pub one: bool,
366}