tinymist_task/
model.rs

1//! Project task models.
2
3use std::hash::Hash;
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 Text task.
76    ExportText(ExportTextTask),
77    /// An query task.
78    Query(QueryTask),
79    // todo: compatibility
80    // An export task of another type.
81    // Other(serde_json::Value),
82}
83
84impl ProjectTask {
85    /// Returns the timing of executing the task.
86    pub fn when(&self) -> Option<TaskWhen> {
87        Some(match self {
88            Self::Preview(task) => task.when,
89            Self::ExportPdf(..)
90            | Self::ExportPng(..)
91            | Self::ExportSvg(..)
92            | Self::ExportHtml(..)
93            | Self::ExportSvgHtml(..)
94            | Self::ExportMd(..)
95            | Self::ExportText(..)
96            | Self::Query(..) => self.as_export()?.when,
97        })
98    }
99
100    /// Returns the export configuration of a task.
101    pub fn as_export(&self) -> Option<&ExportTask> {
102        Some(match self {
103            Self::Preview(..) => return None,
104            Self::ExportPdf(task) => &task.export,
105            Self::ExportPng(task) => &task.export,
106            Self::ExportSvg(task) => &task.export,
107            Self::ExportHtml(task) => &task.export,
108            Self::ExportSvgHtml(task) => &task.export,
109            Self::ExportMd(task) => &task.export,
110            Self::ExportText(task) => &task.export,
111            Self::Query(task) => &task.export,
112        })
113    }
114
115    /// Returns extension of the artifact.
116    pub fn extension(&self) -> &str {
117        match self {
118            Self::ExportPdf { .. } => "pdf",
119            Self::Preview(..) | Self::ExportSvgHtml { .. } | Self::ExportHtml { .. } => "html",
120            Self::ExportMd { .. } => "md",
121            Self::ExportText { .. } => "txt",
122            Self::ExportSvg { .. } => "svg",
123            Self::ExportPng { .. } => "png",
124            Self::Query(QueryTask {
125                format,
126                output_extension,
127                ..
128            }) => output_extension.as_deref().unwrap_or(format),
129        }
130    }
131}
132
133/// A preview task specifier.
134#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
135#[serde(rename_all = "kebab-case")]
136pub struct PreviewTask {
137    /// When to run the task. See [`TaskWhen`] for more
138    /// information.
139    pub when: TaskWhen,
140}
141
142/// An export task specifier.
143#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
144#[serde(rename_all = "kebab-case")]
145pub struct ExportTask {
146    /// When to run the task
147    pub when: TaskWhen,
148    /// The output path pattern.
149    pub output: Option<PathPattern>,
150    /// The task's transforms.
151    #[serde(skip_serializing_if = "Vec::is_empty", default)]
152    pub transform: Vec<ExportTransform>,
153}
154
155impl ExportTask {
156    /// Creates a new unmounted export task.
157    pub fn new(when: TaskWhen) -> Self {
158        Self {
159            when,
160            output: None,
161            transform: Vec::new(),
162        }
163    }
164
165    /// Pretty prints the output whenever possible.
166    pub fn apply_pretty(&mut self) {
167        self.transform
168            .push(ExportTransform::Pretty { script: None });
169    }
170}
171
172/// The legacy page selection specifier.
173#[derive(Default, Debug, Clone, Serialize, Deserialize)]
174#[serde(rename_all = "camelCase")]
175pub enum PageSelection {
176    /// Selects the first page.
177    #[default]
178    First,
179    /// Merges all pages into a single page.
180    Merged {
181        /// The gap between pages (in pt).
182        gap: Option<String>,
183    },
184}
185
186/// A project export transform specifier.
187#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
188#[serde(rename_all = "kebab-case")]
189pub enum ExportTransform {
190    /// Only pick a subset of pages.
191    Pages {
192        /// The page ranges to export.
193        ranges: Vec<Pages>,
194    },
195    /// Merge pages into a single page.
196    Merge {
197        /// The gap between pages (typst code expression, e.g. `1pt`).
198        gap: Option<String>,
199    },
200    /// Execute a transform script.
201    Script {
202        /// The postprocess script (typst script) to run.
203        #[serde(skip_serializing_if = "Option::is_none", default)]
204        script: Option<String>,
205    },
206    /// Uses a pretty printer to format the output.
207    Pretty {
208        /// The pretty command (typst script) to run.
209        ///
210        /// If not provided, the default pretty printer will be used.
211        /// Note: the builtin one may be only effective for json outputs.
212        #[serde(skip_serializing_if = "Option::is_none", default)]
213        script: Option<String>,
214    },
215}
216
217/// An export pdf task specifier.
218#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
219#[serde(rename_all = "kebab-case")]
220pub struct ExportPdfTask {
221    /// The shared export arguments.
222    #[serde(flatten)]
223    pub export: ExportTask,
224    /// One (or multiple comma-separated) PDF standards that Typst will enforce
225    /// conformance with.
226    #[serde(skip_serializing_if = "Vec::is_empty", default)]
227    pub pdf_standards: Vec<PdfStandard>,
228    /// The document's creation date formatted as a UNIX timestamp (in seconds).
229    ///
230    /// For more information, see <https://reproducible-builds.org/specs/source-date-epoch/>.
231    #[serde(skip_serializing_if = "Option::is_none", default)]
232    pub creation_timestamp: Option<i64>,
233}
234
235/// An export png task specifier.
236#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
237#[serde(rename_all = "kebab-case")]
238pub struct ExportPngTask {
239    /// The shared export arguments.
240    #[serde(flatten)]
241    pub export: ExportTask,
242    /// The PPI (pixels per inch) to use for PNG export.
243    pub ppi: Scalar,
244    /// The expression constructing background fill color (in typst script).
245    /// e.g. `#ffffff`, `#000000`, `rgba(255, 255, 255, 0.5)`.
246    ///
247    /// If not provided, the default background color specified in the document
248    /// will be used.
249    #[serde(skip_serializing_if = "Option::is_none", default)]
250    pub fill: Option<String>,
251}
252
253/// An export svg task specifier.
254#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
255#[serde(rename_all = "kebab-case")]
256pub struct ExportSvgTask {
257    /// The shared export arguments.
258    #[serde(flatten)]
259    pub export: ExportTask,
260}
261
262/// An export html task specifier.
263#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
264#[serde(rename_all = "kebab-case")]
265pub struct ExportHtmlTask {
266    /// The shared export arguments.
267    #[serde(flatten)]
268    pub export: ExportTask,
269}
270
271/// An export markdown task specifier.
272#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
273#[serde(rename_all = "kebab-case")]
274pub struct ExportMarkdownTask {
275    /// The shared export arguments.
276    #[serde(flatten)]
277    pub export: ExportTask,
278}
279
280/// An export text task specifier.
281#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
282#[serde(rename_all = "kebab-case")]
283pub struct ExportTextTask {
284    /// The shared export arguments.
285    #[serde(flatten)]
286    pub export: ExportTask,
287}
288
289/// An export query task specifier.
290#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
291#[serde(rename_all = "kebab-case")]
292pub struct QueryTask {
293    /// The shared export arguments.
294    #[serde(flatten)]
295    pub export: ExportTask,
296    /// The format to serialize in. Can be `json`, `yaml`, or `txt`,
297    pub format: String,
298    /// Uses a different output extension from the one inferring from the
299    /// [`Self::format`].
300    pub output_extension: Option<String>,
301    /// Defines which elements to retrieve.
302    pub selector: String,
303    /// Extracts just one field from all retrieved elements.
304    pub field: Option<String>,
305    /// Expects and retrieves exactly one element.
306    pub one: bool,
307}