tinymist_task/
model.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
//! Project task models.

use std::hash::Hash;

use serde::{Deserialize, Serialize};

use super::{Id, Pages, PathPattern, PdfStandard, Scalar, TaskWhen};

/// A project task application specifier. This is used for specifying tasks to
/// run in a project. When the language service notifies an update event of the
/// project, it will check whether any associated tasks need to be run.
///
/// Each task can have different timing and conditions for running. See
/// [`TaskWhen`] for more information.
///
/// The available task types listed in the [`ProjectTask`] only represent the
/// direct formats supported by the typst compiler. More task types can be
/// customized by the [`ExportTransform`].
///
/// ## Examples
///
/// Export a JSON file with the pdfpc notes of the document:
///
/// ```bash
/// tinymist project query main.typ --format json --selector "<pdfpc-notes>" --field value --one
/// ```
///
/// Export a PDF file and then runs a ghostscript command to compress it:
///
/// ```bash
/// tinymist project compile main.typ --pipe 'import "@local/postprocess:0.0.1": ghostscript; ghostscript(output.path)'
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", tag = "type")]
pub struct ApplyProjectTask {
    /// The task's ID.
    pub id: Id,
    /// The document's ID.
    pub document: Id,
    /// The task to run.
    #[serde(flatten)]
    pub task: ProjectTask,
}

impl ApplyProjectTask {
    /// Returns the document's ID.
    pub fn doc_id(&self) -> &Id {
        &self.document
    }

    /// Returns the task's ID.
    pub fn id(&self) -> &Id {
        &self.id
    }
}

/// A project task specifier. This structure specifies the arguments for a task.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", tag = "type")]
pub enum ProjectTask {
    /// A preview task.
    Preview(PreviewTask),
    /// An export PDF task.
    ExportPdf(ExportPdfTask),
    /// An export PNG task.
    ExportPng(ExportPngTask),
    /// An export SVG task.
    ExportSvg(ExportSvgTask),
    /// An export HTML task.
    ExportHtml(ExportHtmlTask),
    /// An export HTML task.
    ExportSvgHtml(ExportHtmlTask),
    /// An export Markdown task.
    ExportMd(ExportMarkdownTask),
    /// An export Text task.
    ExportText(ExportTextTask),
    /// An query task.
    Query(QueryTask),
    // todo: compatibility
    // An export task of another type.
    // Other(serde_json::Value),
}

impl ProjectTask {
    /// Returns the timing of executing the task.
    pub fn when(&self) -> Option<TaskWhen> {
        Some(match self {
            Self::Preview(task) => task.when,
            Self::ExportPdf(..)
            | Self::ExportPng(..)
            | Self::ExportSvg(..)
            | Self::ExportHtml(..)
            | Self::ExportSvgHtml(..)
            | Self::ExportMd(..)
            | Self::ExportText(..)
            | Self::Query(..) => self.as_export()?.when,
        })
    }

    /// Returns the export configuration of a task.
    pub fn as_export(&self) -> Option<&ExportTask> {
        Some(match self {
            Self::Preview(..) => return None,
            Self::ExportPdf(task) => &task.export,
            Self::ExportPng(task) => &task.export,
            Self::ExportSvg(task) => &task.export,
            Self::ExportHtml(task) => &task.export,
            Self::ExportSvgHtml(task) => &task.export,
            Self::ExportMd(task) => &task.export,
            Self::ExportText(task) => &task.export,
            Self::Query(task) => &task.export,
        })
    }

    /// Returns extension of the artifact.
    pub fn extension(&self) -> &str {
        match self {
            Self::ExportPdf { .. } => "pdf",
            Self::Preview(..) | Self::ExportSvgHtml { .. } | Self::ExportHtml { .. } => "html",
            Self::ExportMd { .. } => "md",
            Self::ExportText { .. } => "txt",
            Self::ExportSvg { .. } => "svg",
            Self::ExportPng { .. } => "png",
            Self::Query(QueryTask {
                format,
                output_extension,
                ..
            }) => output_extension.as_deref().unwrap_or(format),
        }
    }
}

/// A preview task specifier.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct PreviewTask {
    /// When to run the task. See [`TaskWhen`] for more
    /// information.
    pub when: TaskWhen,
}

/// An export task specifier.
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ExportTask {
    /// When to run the task
    pub when: TaskWhen,
    /// The output path pattern.
    pub output: Option<PathPattern>,
    /// The task's transforms.
    #[serde(skip_serializing_if = "Vec::is_empty", default)]
    pub transform: Vec<ExportTransform>,
}

impl ExportTask {
    /// Creates a new unmounted export task.
    pub fn new(when: TaskWhen) -> Self {
        Self {
            when,
            output: None,
            transform: Vec::new(),
        }
    }

    /// Pretty prints the output whenever possible.
    pub fn apply_pretty(&mut self) {
        self.transform
            .push(ExportTransform::Pretty { script: None });
    }
}

/// The legacy page selection specifier.
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PageSelection {
    /// Selects the first page.
    #[default]
    First,
    /// Merges all pages into a single page.
    Merged {
        /// The gap between pages (in pt).
        gap: Option<String>,
    },
}

/// A project export transform specifier.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ExportTransform {
    /// Only pick a subset of pages.
    Pages {
        /// The page ranges to export.
        ranges: Vec<Pages>,
    },
    /// Merge pages into a single page.
    Merge {
        /// The gap between pages (typst code expression, e.g. `1pt`).
        gap: Option<String>,
    },
    /// Execute a transform script.
    Script {
        /// The postprocess script (typst script) to run.
        #[serde(skip_serializing_if = "Option::is_none", default)]
        script: Option<String>,
    },
    /// Uses a pretty printer to format the output.
    Pretty {
        /// The pretty command (typst script) to run.
        ///
        /// If not provided, the default pretty printer will be used.
        /// Note: the builtin one may be only effective for json outputs.
        #[serde(skip_serializing_if = "Option::is_none", default)]
        script: Option<String>,
    },
}

/// An export pdf task specifier.
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ExportPdfTask {
    /// The shared export arguments.
    #[serde(flatten)]
    pub export: ExportTask,
    /// One (or multiple comma-separated) PDF standards that Typst will enforce
    /// conformance with.
    #[serde(skip_serializing_if = "Vec::is_empty", default)]
    pub pdf_standards: Vec<PdfStandard>,
    /// The document's creation date formatted as a UNIX timestamp (in seconds).
    ///
    /// For more information, see <https://reproducible-builds.org/specs/source-date-epoch/>.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub creation_timestamp: Option<i64>,
}

/// An export png task specifier.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ExportPngTask {
    /// The shared export arguments.
    #[serde(flatten)]
    pub export: ExportTask,
    /// The PPI (pixels per inch) to use for PNG export.
    pub ppi: Scalar,
    /// The expression constructing background fill color (in typst script).
    /// e.g. `#ffffff`, `#000000`, `rgba(255, 255, 255, 0.5)`.
    ///
    /// If not provided, the default background color specified in the document
    /// will be used.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub fill: Option<String>,
}

/// An export svg task specifier.
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ExportSvgTask {
    /// The shared export arguments.
    #[serde(flatten)]
    pub export: ExportTask,
}

/// An export html task specifier.
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ExportHtmlTask {
    /// The shared export arguments.
    #[serde(flatten)]
    pub export: ExportTask,
}

/// An export markdown task specifier.
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ExportMarkdownTask {
    /// The shared export arguments.
    #[serde(flatten)]
    pub export: ExportTask,
}

/// An export text task specifier.
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ExportTextTask {
    /// The shared export arguments.
    #[serde(flatten)]
    pub export: ExportTask,
}

/// An export query task specifier.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct QueryTask {
    /// The shared export arguments.
    #[serde(flatten)]
    pub export: ExportTask,
    /// The format to serialize in. Can be `json`, `yaml`, or `txt`,
    pub format: String,
    /// Uses a different output extension from the one inferring from the
    /// [`Self::format`].
    pub output_extension: Option<String>,
    /// Defines which elements to retrieve.
    pub selector: String,
    /// Extracts just one field from all retrieved elements.
    pub field: Option<String>,
    /// Expects and retrieves exactly one element.
    pub one: bool,
}