Skip to main content

wdl_format/v1/
task.rs

1//! Formatting for tasks.
2
3use wdl_ast::SyntaxKind;
4use wdl_ast::v1::StrippedCommandPart;
5
6use crate::PreToken;
7use crate::TokenStream;
8use crate::Trivia;
9use crate::Writable as _;
10use crate::element::FormatElement;
11
12/// Formats a [`TaskDefinition`](wdl_ast::v1::TaskDefinition).
13///
14/// # Panics
15///
16/// This will panic if the element does not have the expected children.
17pub fn format_task_definition(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
18    let mut children = element.children().expect("task definition children");
19
20    stream.ignore_trailing_blank_lines();
21
22    let task_keyword = children.next().expect("task keyword");
23    assert!(task_keyword.element().kind() == SyntaxKind::TaskKeyword);
24    (&task_keyword).write(stream);
25    stream.end_word();
26
27    let name = children.next().expect("task name");
28    assert!(name.element().kind() == SyntaxKind::Ident);
29    (&name).write(stream);
30    stream.end_word();
31
32    let open_brace = children.next().expect("open brace");
33    assert!(open_brace.element().kind() == SyntaxKind::OpenBrace);
34    (&open_brace).write(stream);
35    stream.end_line();
36    stream.increment_indent();
37
38    let mut meta = None;
39    let mut parameter_meta = None;
40    let mut input = None;
41    let mut body = Vec::new();
42    let mut command = None;
43    let mut output = None;
44    let mut requirements = None;
45    let mut runtime = None;
46    let mut hints = None;
47    let mut close_brace = None;
48
49    for child in children {
50        match child.element().kind() {
51            SyntaxKind::InputSectionNode => {
52                input = Some(child.clone());
53            }
54            SyntaxKind::MetadataSectionNode => {
55                meta = Some(child.clone());
56            }
57            SyntaxKind::ParameterMetadataSectionNode => {
58                parameter_meta = Some(child.clone());
59            }
60            SyntaxKind::BoundDeclNode => {
61                body.push(child.clone());
62            }
63            SyntaxKind::CommandSectionNode => {
64                command = Some(child.clone());
65            }
66            SyntaxKind::OutputSectionNode => {
67                output = Some(child.clone());
68            }
69            SyntaxKind::RequirementsSectionNode => {
70                requirements = Some(child.clone());
71            }
72            SyntaxKind::RuntimeSectionNode => {
73                runtime = Some(child.clone());
74            }
75            SyntaxKind::TaskHintsSectionNode => {
76                hints = Some(child.clone());
77            }
78            SyntaxKind::CloseBrace => {
79                close_brace = Some(child.clone());
80            }
81            _ => {
82                unreachable!(
83                    "unexpected child in task definition: {:?}",
84                    child.element().kind()
85                );
86            }
87        }
88    }
89
90    if let Some(meta) = meta {
91        (&meta).write(stream);
92        stream.blank_line();
93    }
94
95    if let Some(parameter_meta) = parameter_meta {
96        (&parameter_meta).write(stream);
97        stream.blank_line();
98    }
99
100    if let Some(input) = input {
101        (&input).write(stream);
102        stream.blank_line();
103    }
104
105    stream.allow_blank_lines();
106    let body_empty = body.is_empty();
107    for child in body {
108        (&child).write(stream);
109    }
110    stream.ignore_trailing_blank_lines();
111    if !body_empty {
112        stream.blank_line();
113    }
114
115    if let Some(command) = command {
116        (&command).write(stream);
117        stream.blank_line();
118    }
119
120    if let Some(output) = output {
121        (&output).write(stream);
122        stream.blank_line();
123    }
124
125    match requirements {
126        Some(requirements) => {
127            (&requirements).write(stream);
128            stream.blank_line();
129        }
130        _ => {
131            if let Some(runtime) = runtime {
132                (&runtime).write(stream);
133                stream.blank_line();
134            }
135        }
136    }
137
138    if let Some(hints) = hints {
139        (&hints).write(stream);
140        stream.blank_line();
141    }
142
143    stream.trim_while(|t| matches!(t, PreToken::BlankLine | PreToken::Trivia(Trivia::BlankLine)));
144
145    stream.decrement_indent();
146    (&close_brace.expect("task close brace")).write(stream);
147    stream.end_line();
148}
149
150/// Formats a [`CommandSection`](wdl_ast::v1::CommandSection).
151///
152/// # Panics
153///
154/// This will panic if the element does not have the expected children.
155pub fn format_command_section(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
156    let mut children = element.children().expect("command section children");
157
158    let command_keyword = children.next().expect("command keyword");
159    assert!(command_keyword.element().kind() == SyntaxKind::CommandKeyword);
160    (&command_keyword).write(stream);
161    stream.end_word();
162
163    let open_delimiter = children.next().expect("open delimiter");
164    match open_delimiter.element().kind() {
165        SyntaxKind::OpenBrace => {
166            stream.push_literal_in_place_of_token(
167                open_delimiter
168                    .element()
169                    .as_token()
170                    .expect("open brace should be token"),
171                "<<<".to_string(),
172            );
173        }
174        SyntaxKind::OpenHeredoc => {
175            (&open_delimiter).write(stream);
176        }
177        _ => {
178            unreachable!(
179                "unexpected open delimiter in command section: {:?}",
180                open_delimiter.element().kind()
181            );
182        }
183    }
184
185    let parts = element
186        .element()
187        .as_node()
188        .expect("command section node")
189        .as_command_section()
190        .expect("command section")
191        .strip_whitespace();
192    match parts {
193        None => {
194            // The command section has mixed indentation, so we format it as is.
195            // TODO: We may want to format this differently in the future, but for now
196            // we can say "ugly input, ugly output".
197            for child in children {
198                match child.element().kind() {
199                    SyntaxKind::CloseBrace => {
200                        stream.push_literal_in_place_of_token(
201                            child
202                                .element()
203                                .as_token()
204                                .expect("close brace should be token"),
205                            ">>>".to_string(),
206                        );
207                    }
208                    SyntaxKind::CloseHeredoc => {
209                        (&child).write(stream);
210                    }
211                    SyntaxKind::LiteralCommandText | SyntaxKind::PlaceholderNode => {
212                        (&child).write(stream);
213                    }
214                    _ => {
215                        unreachable!(
216                            "unexpected child in command section: {:?}",
217                            child.element().kind()
218                        );
219                    }
220                }
221            }
222        }
223        Some(parts) => {
224            // Now we parse the stripped command section and format it.
225            // End the line after the open delimiter and increment indent.
226            stream.increment_indent();
227
228            for (part, child) in parts.iter().zip(children.by_ref()) {
229                match part {
230                    StrippedCommandPart::Text(text) => {
231                        // Manually format the text and ignore the child.
232                        for (i, line) in text.lines().enumerate() {
233                            if i > 0 {
234                                stream.end_line();
235                            }
236                            stream.push_literal(line.to_owned(), SyntaxKind::LiteralCommandText);
237                        }
238
239                        if text.ends_with('\n') {
240                            stream.end_line();
241                        }
242                    }
243                    StrippedCommandPart::Placeholder(_) => {
244                        stream.push(PreToken::TempIndentStart);
245                        (&child).write(stream);
246                        stream.push(PreToken::TempIndentEnd);
247                    }
248                }
249            }
250
251            stream.decrement_indent();
252
253            for child in children {
254                match child.element().kind() {
255                    SyntaxKind::CloseBrace => {
256                        stream.push_literal_in_place_of_token(
257                            child
258                                .element()
259                                .as_token()
260                                .expect("close brace should be token"),
261                            ">>>".to_string(),
262                        );
263                    }
264                    SyntaxKind::CloseHeredoc => {
265                        (&child).write(stream);
266                    }
267                    _ => {
268                        unreachable!(
269                            "unexpected child in command section: {:?}",
270                            child.element().kind()
271                        );
272                    }
273                }
274            }
275        }
276    }
277    stream.end_line();
278}
279
280/// Formats a [`RequirementsItem`](wdl_ast::v1::RequirementsItem).
281///
282/// # Panics
283///
284/// This will panic if the element does not have the expected children.
285pub fn format_requirements_item(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
286    let mut children = element.children().expect("requirements item children");
287
288    let name = children.next().expect("requirements item name");
289    assert!(name.element().kind() == SyntaxKind::Ident);
290    (&name).write(stream);
291
292    let colon = children.next().expect("requirements item colon");
293    assert!(colon.element().kind() == SyntaxKind::Colon);
294    (&colon).write(stream);
295    stream.end_word();
296
297    let value = children.next().expect("requirements item value");
298    (&value).write(stream);
299}
300
301/// Formats a [`RequirementsSection`](wdl_ast::v1::RequirementsSection).
302///
303/// # Panics
304///
305/// This will panic if the element does not have the expected children.
306pub fn format_requirements_section(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
307    let mut children = element.children().expect("requirements section children");
308
309    let requirements_keyword = children.next().expect("requirements keyword");
310    assert!(requirements_keyword.element().kind() == SyntaxKind::RequirementsKeyword);
311    (&requirements_keyword).write(stream);
312    stream.end_word();
313
314    let open_brace = children.next().expect("open brace");
315    assert!(open_brace.element().kind() == SyntaxKind::OpenBrace);
316    (&open_brace).write(stream);
317    stream.increment_indent();
318
319    let mut items = Vec::new();
320    let mut close_brace = None;
321
322    for child in children {
323        match child.element().kind() {
324            SyntaxKind::RequirementsItemNode => {
325                items.push(child.clone());
326            }
327            SyntaxKind::CloseBrace => {
328                close_brace = Some(child.clone());
329            }
330            _ => {
331                unreachable!(
332                    "unexpected child in requirements section: {:?}",
333                    child.element().kind()
334                );
335            }
336        }
337    }
338
339    for item in items {
340        (&item).write(stream);
341        stream.end_line();
342    }
343
344    stream.decrement_indent();
345    (&close_brace.expect("requirements close brace")).write(stream);
346    stream.end_line();
347}
348
349/// Formats a [`TaskHintsItem`](wdl_ast::v1::TaskHintsItem).
350///
351/// # Panics
352///
353/// This will panic if the element does not have the expected children.
354pub fn format_task_hints_item(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
355    let mut children = element.children().expect("task hints item children");
356
357    let name = children.next().expect("task hints item name");
358    assert!(name.element().kind() == SyntaxKind::Ident);
359    (&name).write(stream);
360
361    let colon = children.next().expect("task hints item colon");
362    assert!(colon.element().kind() == SyntaxKind::Colon);
363    (&colon).write(stream);
364    stream.end_word();
365
366    let value = children.next().expect("task hints item value");
367    (&value).write(stream);
368
369    assert!(children.next().is_none());
370}
371
372/// Formats a [`RuntimeItem`](wdl_ast::v1::RuntimeItem).
373///
374/// # Panics
375///
376/// This will panic if the element does not have the expected children.
377pub fn format_runtime_item(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
378    let mut children = element.children().expect("runtime item children");
379
380    let name = children.next().expect("runtime item name");
381    assert!(name.element().kind() == SyntaxKind::Ident);
382    (&name).write(stream);
383
384    let colon = children.next().expect("runtime item colon");
385    assert!(colon.element().kind() == SyntaxKind::Colon);
386    (&colon).write(stream);
387    stream.end_word();
388
389    let value = children.next().expect("runtime item value");
390    (&value).write(stream);
391}
392
393/// Formats a [`RuntimeSection`](wdl_ast::v1::RuntimeSection).
394///
395/// # Panics
396///
397/// This will panic if the element does not have the expected children.
398pub fn format_runtime_section(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
399    let mut children = element.children().expect("runtime section children");
400
401    let runtime_keyword = children.next().expect("runtime keyword");
402    assert!(runtime_keyword.element().kind() == SyntaxKind::RuntimeKeyword);
403    (&runtime_keyword).write(stream);
404    stream.end_word();
405
406    let open_brace = children.next().expect("open brace");
407    assert!(open_brace.element().kind() == SyntaxKind::OpenBrace);
408    (&open_brace).write(stream);
409    stream.increment_indent();
410
411    let mut items = Vec::new();
412    let mut close_brace = None;
413
414    for child in children {
415        match child.element().kind() {
416            SyntaxKind::RuntimeItemNode => {
417                items.push(child.clone());
418            }
419            SyntaxKind::CloseBrace => {
420                close_brace = Some(child.clone());
421            }
422            _ => {
423                unreachable!(
424                    "unexpected child in runtime section: {:?}",
425                    child.element().kind()
426                );
427            }
428        }
429    }
430
431    for item in items {
432        (&item).write(stream);
433        stream.end_line();
434    }
435
436    stream.decrement_indent();
437    (&close_brace.expect("runtime close brace")).write(stream);
438    stream.end_line();
439}
440
441/// Formats a [`TaskHintsSection`](wdl_ast::v1::TaskHintsSection).
442///
443/// # Panics
444///
445/// This will panic if the element does not have the expected children.
446pub fn format_task_hints_section(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
447    let mut children = element.children().expect("task hints section children");
448
449    let hints_keyword = children.next().expect("hints keyword");
450    assert!(hints_keyword.element().kind() == SyntaxKind::HintsKeyword);
451    (&hints_keyword).write(stream);
452    stream.end_word();
453
454    let open_brace = children.next().expect("open brace");
455    assert!(open_brace.element().kind() == SyntaxKind::OpenBrace);
456    (&open_brace).write(stream);
457    stream.increment_indent();
458
459    let mut items = Vec::new();
460    let mut close_brace = None;
461
462    for child in children {
463        match child.element().kind() {
464            SyntaxKind::TaskHintsItemNode => {
465                items.push(child.clone());
466            }
467            SyntaxKind::CloseBrace => {
468                close_brace = Some(child.clone());
469            }
470            _ => {
471                unreachable!(
472                    "unexpected child in task hints section: {:?}",
473                    child.element().kind()
474                );
475            }
476        }
477    }
478
479    for item in items {
480        (&item).write(stream);
481        stream.end_line();
482    }
483
484    stream.decrement_indent();
485    (&close_brace.expect("task hints close brace")).write(stream);
486    stream.end_line();
487}