Skip to main content

wdl_format/v1/
task.rs

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