1use std::fmt;
4
5use rowan::NodeOrToken;
6
7use super::BoundDecl;
8use super::Decl;
9use super::Expr;
10use super::LiteralBoolean;
11use super::LiteralFloat;
12use super::LiteralInteger;
13use super::LiteralString;
14use super::OpenHeredoc;
15use super::Placeholder;
16use super::StructDefinition;
17use super::WorkflowDefinition;
18use crate::AstNode;
19use crate::AstToken;
20use crate::Ident;
21use crate::SyntaxKind;
22use crate::SyntaxNode;
23use crate::SyntaxToken;
24use crate::TreeNode;
25use crate::TreeToken;
26use crate::v1::display::write_input_section;
27use crate::v1::display::write_output_section;
28
29pub mod common;
30pub mod requirements;
31pub mod runtime;
32
33pub const TASK_FIELDS: &[(&str, &str)] = &[
36 (TASK_FIELD_NAME, "The task name."),
37 (
38 TASK_FIELD_ID,
39 "A String with the unique ID of the task. The execution engine may choose the format for \
40 this ID, but it is suggested to include at least the following information:\nThe task \
41 name\nThe task alias, if it differs from the task name\nThe index of the task instance, \
42 if it is within a scatter statement",
43 ),
44 (
45 TASK_FIELD_CONTAINER,
46 "The URI String of the container in which the task is executing, or None if the task is \
47 being executed in the host environment.",
48 ),
49 (
50 TASK_FIELD_CPU,
51 "The allocated number of cpus as a Float. Must be greater than 0.",
52 ),
53 (
54 TASK_FIELD_MEMORY,
55 "The allocated memory in bytes as an Int. Must be greater than 0.",
56 ),
57 (
58 TASK_FIELD_GPU,
59 "An Array[String] with one specification per allocated GPU. The specification is \
60 execution engine-specific. If no GPUs were allocated, then the value must be an empty \
61 array.",
62 ),
63 (
64 TASK_FIELD_FPGA,
65 "An Array[String] with one specification per allocated FPGA. The specification is \
66 execution engine-specific. If no FPGAs were allocated, then the value must be an empty \
67 array.",
68 ),
69 (
70 TASK_FIELD_DISKS,
71 "A Map[String, Int] with one entry for each disk mount point. The key is the mount point \
72 and the value is the initial amount of disk space allocated, in bytes. The execution \
73 engine must, at a minimum, provide one entry for each disk mount point requested, but \
74 may provide more. The amount of disk space available for a given mount point may \
75 increase during the lifetime of the task (e.g., autoscaling volumes provided by some \
76 cloud services).",
77 ),
78 (
79 TASK_FIELD_ATTEMPT,
80 "The current task attempt. The value must be 0 the first time the task is executed, and \
81 incremented by 1 each time the task is retried (if any).",
82 ),
83 (
84 TASK_FIELD_PREVIOUS,
85 "An Object containing the resource requirements from the previous task attempt. Available \
86 in requirements, hints, runtime, output, and command sections. All constituent members \
87 are optional and `None` on the first attempt.",
88 ),
89 (
90 TASK_FIELD_END_TIME,
91 "An Int? whose value is the time by which the task must be completed, as a Unix time \
92 stamp. A value of 0 means that the execution engine does not impose a time limit. A \
93 value of None means that the execution engine cannot determine whether the runtime of \
94 the task is limited. A positive value is a guarantee that the task will be preempted at \
95 the specified time, but is not a guarantee that the task won't be preempted earlier.",
96 ),
97 (
98 TASK_FIELD_RETURN_CODE,
99 "An Int? whose value is initially None and is set to the value of the command's return \
100 code. The value is only guaranteed to be defined in the output section.",
101 ),
102 (
103 TASK_FIELD_META,
104 "An Object containing a copy of the task's meta section, or the empty Object if there is \
105 no meta section or if it is empty.",
106 ),
107 (
108 TASK_FIELD_PARAMETER_META,
109 "An Object containing a copy of the task's parameter_meta section, or the empty Object if \
110 there is no parameter_meta section or if it is empty.",
111 ),
112 (
113 TASK_FIELD_EXT,
114 "An Object containing execution engine-specific attributes, or the empty Object if there \
115 aren't any. Members of ext should be considered optional. It is recommended to only \
116 access a member of ext using string interpolation to avoid an error if it is not defined.",
117 ),
118];
119
120pub const RUNTIME_KEYS: &[(&str, &str)] = &[
122 (
123 TASK_REQUIREMENT_CONTAINER,
124 "Specifies the container image (e.g., Docker, Singularity) to use for the task.",
125 ),
126 (
127 TASK_REQUIREMENT_CPU,
128 "The number of CPU cores required for the task.",
129 ),
130 (
131 TASK_REQUIREMENT_MEMORY,
132 "The amount of memory required, specified as a string with units (e.g., '2 GiB').",
133 ),
134 (
135 TASK_REQUIREMENT_DISKS,
136 "Specifies the disk requirements for the task.",
137 ),
138 (TASK_REQUIREMENT_GPU, "Specifies GPU requirements."),
139];
140
141pub const REQUIREMENTS_KEY: &[(&str, &str)] = &[
143 (
144 TASK_REQUIREMENT_CONTAINER,
145 "Specifies a list of allowed container images. Use `*` to allow any POSIX environment.",
146 ),
147 (
148 TASK_REQUIREMENT_CPU,
149 "The minimum number of CPU cores required.",
150 ),
151 (
152 TASK_REQUIREMENT_MEMORY,
153 "The minimum amount of memory required.",
154 ),
155 (TASK_REQUIREMENT_GPU, "The minimum GPU requirements."),
156 (TASK_REQUIREMENT_FPGA, "The minimum FPGA requirements."),
157 (TASK_REQUIREMENT_DISKS, "The minimum disk requirements."),
158 (
159 TASK_REQUIREMENT_MAX_RETRIES,
160 "The maximum number of times the task can be retried.",
161 ),
162 (
163 TASK_REQUIREMENT_RETURN_CODES,
164 "A list of acceptable return codes from the command.",
165 ),
166];
167
168pub const TASK_HINT_KEYS: &[(&str, &str)] = &[
170 (
171 TASK_HINT_DISKS,
172 "A hint to the execution engine to mount disks with specific attributes. The value of \
173 this hint can be a String with a specification that applies to all mount points, or a \
174 Map with the key being the mount point and the value being a String with the \
175 specification for that mount point.",
176 ),
177 (
178 TASK_HINT_GPU,
179 "A hint to the execution engine to provision hardware accelerators with specific \
180 attributes. Accelerator specifications are left intentionally vague as they are \
181 primarily intended to be used in the context of a specific compute environment.",
182 ),
183 (
184 TASK_HINT_FPGA,
185 "A hint to the execution engine to provision hardware accelerators with specific \
186 attributes. Accelerator specifications are left intentionally vague as they are \
187 primarily intended to be used in the context of a specific compute environment.",
188 ),
189 (
190 TASK_HINT_INPUTS,
191 "Provides input-specific hints. Each key must refer to a parameter defined in the task's \
192 input section. A key may also used dotted notation to refer to a specific member of a \
193 struct input.",
194 ),
195 (
196 TASK_HINT_LOCALIZATION_OPTIONAL,
197 "A hint to the execution engine about whether the File inputs for this task need to be \
198 localized prior to executing the task. The value of this hint is a Boolean for which \
199 true indicates that the contents of the File inputs may be streamed on demand.",
200 ),
201 (
202 TASK_HINT_MAX_CPU,
203 "A hint to the execution engine that the task expects to use no more than the specified \
204 number of CPUs. The value of this hint has the same specification as requirements.cpu.",
205 ),
206 (
207 TASK_HINT_MAX_MEMORY,
208 "A hint to the execution engine that the task expects to use no more than the specified \
209 amount of memory. The value of this hint has the same specification as \
210 requirements.memory.",
211 ),
212 (
213 TASK_HINT_OUTPUTS,
214 "Provides output-specific hints. Each key must refer to a parameter defined in the task's \
215 output section. A key may also use dotted notation to refer to a specific member of a \
216 struct output.",
217 ),
218 (
219 TASK_HINT_SHORT_TASK,
220 "A hint to the execution engine about the expected duration of this task. The value of \
221 this hint is a Boolean for which true indicates that that this task is not expected to \
222 take long to execute, which the execution engine can interpret as permission to optimize \
223 the execution of the task.",
224 ),
225 (
226 TASK_HINT_CACHEABLE,
227 "A hint to the execution engine that the task's execution result is cacheable. The value \
228 of this hint is a Boolean for which true indicates that the execution result is \
229 cacheable and false indicates it is not. The default value of the hint depends on the \
230 engine's configuration.",
231 ),
232];
233
234pub const TASK_FIELD_NAME: &str = "name";
236pub const TASK_FIELD_ID: &str = "id";
238pub const TASK_FIELD_CONTAINER: &str = "container";
240pub const TASK_FIELD_CPU: &str = "cpu";
242pub const TASK_FIELD_MEMORY: &str = "memory";
244pub const TASK_FIELD_ATTEMPT: &str = "attempt";
246pub const TASK_FIELD_PREVIOUS: &str = "previous";
248pub const TASK_FIELD_GPU: &str = "gpu";
250pub const TASK_FIELD_FPGA: &str = "fpga";
252pub const TASK_FIELD_DISKS: &str = "disks";
254pub const TASK_FIELD_END_TIME: &str = "end_time";
256pub const TASK_FIELD_RETURN_CODE: &str = "return_code";
258pub const TASK_FIELD_META: &str = "meta";
260pub const TASK_FIELD_PARAMETER_META: &str = "parameter_meta";
262pub const TASK_FIELD_EXT: &str = "ext";
264pub const TASK_FIELD_MAX_RETRIES: &str = "max_retries";
266
267pub const TASK_REQUIREMENT_CONTAINER: &str = "container";
269pub const TASK_REQUIREMENT_CONTAINER_ALIAS: &str = "docker";
271pub const TASK_REQUIREMENT_CPU: &str = "cpu";
273pub const TASK_REQUIREMENT_DISKS: &str = "disks";
275pub const TASK_REQUIREMENT_GPU: &str = "gpu";
277pub const TASK_REQUIREMENT_FPGA: &str = "fpga";
279pub const TASK_REQUIREMENT_MAX_RETRIES: &str = "max_retries";
281pub const TASK_REQUIREMENT_MAX_RETRIES_ALIAS: &str = "maxRetries";
283pub const TASK_REQUIREMENT_MEMORY: &str = "memory";
285pub const TASK_REQUIREMENT_RETURN_CODES: &str = "return_codes";
287pub const TASK_REQUIREMENT_RETURN_CODES_ALIAS: &str = "returnCodes";
289
290pub const TASK_HINT_DISKS: &str = "disks";
292pub const TASK_HINT_GPU: &str = "gpu";
294pub const TASK_HINT_FPGA: &str = "fpga";
296pub const TASK_HINT_INPUTS: &str = "inputs";
298pub const TASK_HINT_LOCALIZATION_OPTIONAL: &str = "localization_optional";
300pub const TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS: &str = "localizationOptional";
303pub const TASK_HINT_MAX_CPU: &str = "max_cpu";
305pub const TASK_HINT_MAX_CPU_ALIAS: &str = "maxCpu";
307pub const TASK_HINT_MAX_MEMORY: &str = "max_memory";
309pub const TASK_HINT_MAX_MEMORY_ALIAS: &str = "maxMemory";
311pub const TASK_HINT_OUTPUTS: &str = "outputs";
313pub const TASK_HINT_SHORT_TASK: &str = "short_task";
315pub const TASK_HINT_SHORT_TASK_ALIAS: &str = "shortTask";
317pub const TASK_HINT_CACHEABLE: &str = "cacheable";
319
320fn unescape_command_text(s: &str, heredoc: bool, buffer: &mut String) {
322 let mut chars = s.chars().peekable();
323 while let Some(c) = chars.next() {
324 match c {
325 '\\' => match chars.peek() {
326 Some('\\') | Some('~') => {
327 buffer.push(chars.next().unwrap());
328 }
329 Some('>') if heredoc => {
330 buffer.push(chars.next().unwrap());
331 }
332 Some('$') | Some('}') if !heredoc => {
333 buffer.push(chars.next().unwrap());
334 }
335 _ => {
336 buffer.push('\\');
337 }
338 },
339 _ => {
340 buffer.push(c);
341 }
342 }
343 }
344}
345
346#[derive(Clone, Debug, PartialEq, Eq)]
348pub struct TaskDefinition<N: TreeNode = SyntaxNode>(N);
349
350impl<N: TreeNode> TaskDefinition<N> {
351 pub fn name(&self) -> Ident<N::Token> {
353 self.token().expect("task should have a name")
354 }
355
356 pub fn items(&self) -> impl Iterator<Item = TaskItem<N>> + use<'_, N> {
358 TaskItem::children(&self.0)
359 }
360
361 pub fn input(&self) -> Option<InputSection<N>> {
363 self.child()
364 }
365
366 pub fn output(&self) -> Option<OutputSection<N>> {
368 self.child()
369 }
370
371 pub fn command(&self) -> Option<CommandSection<N>> {
373 self.child()
374 }
375
376 pub fn requirements(&self) -> Option<RequirementsSection<N>> {
378 self.child()
379 }
380
381 pub fn hints(&self) -> Option<TaskHintsSection<N>> {
383 self.child()
384 }
385
386 pub fn runtime(&self) -> Option<RuntimeSection<N>> {
388 self.child()
389 }
390
391 pub fn metadata(&self) -> Option<MetadataSection<N>> {
393 self.child()
394 }
395
396 pub fn parameter_metadata(&self) -> Option<ParameterMetadataSection<N>> {
398 self.child()
399 }
400
401 pub fn declarations(&self) -> impl Iterator<Item = BoundDecl<N>> + use<'_, N> {
403 self.children()
404 }
405
406 pub fn markdown_description(&self, f: &mut impl fmt::Write) -> fmt::Result {
408 writeln!(f, "```wdl\ntask {}\n```\n---", self.name().text())?;
409
410 if let Some(meta) = self.metadata()
411 && let Some(desc) = meta.items().find(|i| i.name().text() == "description")
412 && let MetadataValue::String(s) = desc.value()
413 && let Some(text) = s.text()
414 {
415 writeln!(f, "{}\n", text.text())?;
416 }
417
418 write_input_section(f, self.input().as_ref(), self.parameter_metadata().as_ref())?;
419 write_output_section(
420 f,
421 self.output().as_ref(),
422 self.parameter_metadata().as_ref(),
423 )?;
424
425 Ok(())
426 }
427}
428
429impl<N: TreeNode> AstNode<N> for TaskDefinition<N> {
430 fn can_cast(kind: SyntaxKind) -> bool {
431 kind == SyntaxKind::TaskDefinitionNode
432 }
433
434 fn cast(inner: N) -> Option<Self> {
435 match inner.kind() {
436 SyntaxKind::TaskDefinitionNode => Some(Self(inner)),
437 _ => None,
438 }
439 }
440
441 fn inner(&self) -> &N {
442 &self.0
443 }
444}
445
446#[derive(Clone, Debug, PartialEq, Eq)]
448pub enum TaskItem<N: TreeNode = SyntaxNode> {
449 Input(InputSection<N>),
451 Output(OutputSection<N>),
453 Command(CommandSection<N>),
455 Requirements(RequirementsSection<N>),
457 Hints(TaskHintsSection<N>),
459 Runtime(RuntimeSection<N>),
461 Metadata(MetadataSection<N>),
463 ParameterMetadata(ParameterMetadataSection<N>),
465 Declaration(BoundDecl<N>),
467}
468
469impl<N: TreeNode> TaskItem<N> {
470 pub fn can_cast(kind: SyntaxKind) -> bool {
473 matches!(
474 kind,
475 SyntaxKind::InputSectionNode
476 | SyntaxKind::OutputSectionNode
477 | SyntaxKind::CommandSectionNode
478 | SyntaxKind::RequirementsSectionNode
479 | SyntaxKind::TaskHintsSectionNode
480 | SyntaxKind::RuntimeSectionNode
481 | SyntaxKind::MetadataSectionNode
482 | SyntaxKind::ParameterMetadataSectionNode
483 | SyntaxKind::BoundDeclNode
484 )
485 }
486
487 pub fn cast(inner: N) -> Option<Self> {
491 match inner.kind() {
492 SyntaxKind::InputSectionNode => Some(Self::Input(
493 InputSection::cast(inner).expect("input section to cast"),
494 )),
495 SyntaxKind::OutputSectionNode => Some(Self::Output(
496 OutputSection::cast(inner).expect("output section to cast"),
497 )),
498 SyntaxKind::CommandSectionNode => Some(Self::Command(
499 CommandSection::cast(inner).expect("command section to cast"),
500 )),
501 SyntaxKind::RequirementsSectionNode => Some(Self::Requirements(
502 RequirementsSection::cast(inner).expect("requirements section to cast"),
503 )),
504 SyntaxKind::RuntimeSectionNode => Some(Self::Runtime(
505 RuntimeSection::cast(inner).expect("runtime section to cast"),
506 )),
507 SyntaxKind::MetadataSectionNode => Some(Self::Metadata(
508 MetadataSection::cast(inner).expect("metadata section to cast"),
509 )),
510 SyntaxKind::ParameterMetadataSectionNode => Some(Self::ParameterMetadata(
511 ParameterMetadataSection::cast(inner).expect("parameter metadata section to cast"),
512 )),
513 SyntaxKind::TaskHintsSectionNode => Some(Self::Hints(
514 TaskHintsSection::cast(inner).expect("task hints section to cast"),
515 )),
516 SyntaxKind::BoundDeclNode => Some(Self::Declaration(
517 BoundDecl::cast(inner).expect("bound decl to cast"),
518 )),
519 _ => None,
520 }
521 }
522
523 pub fn inner(&self) -> &N {
525 match self {
526 Self::Input(element) => element.inner(),
527 Self::Output(element) => element.inner(),
528 Self::Command(element) => element.inner(),
529 Self::Requirements(element) => element.inner(),
530 Self::Hints(element) => element.inner(),
531 Self::Runtime(element) => element.inner(),
532 Self::Metadata(element) => element.inner(),
533 Self::ParameterMetadata(element) => element.inner(),
534 Self::Declaration(element) => element.inner(),
535 }
536 }
537
538 pub fn as_input_section(&self) -> Option<&InputSection<N>> {
544 match self {
545 Self::Input(s) => Some(s),
546 _ => None,
547 }
548 }
549
550 pub fn into_input_section(self) -> Option<InputSection<N>> {
556 match self {
557 Self::Input(s) => Some(s),
558 _ => None,
559 }
560 }
561
562 pub fn as_output_section(&self) -> Option<&OutputSection<N>> {
568 match self {
569 Self::Output(s) => Some(s),
570 _ => None,
571 }
572 }
573
574 pub fn into_output_section(self) -> Option<OutputSection<N>> {
580 match self {
581 Self::Output(s) => Some(s),
582 _ => None,
583 }
584 }
585
586 pub fn as_command_section(&self) -> Option<&CommandSection<N>> {
592 match self {
593 Self::Command(s) => Some(s),
594 _ => None,
595 }
596 }
597
598 pub fn into_command_section(self) -> Option<CommandSection<N>> {
604 match self {
605 Self::Command(s) => Some(s),
606 _ => None,
607 }
608 }
609
610 pub fn as_requirements_section(&self) -> Option<&RequirementsSection<N>> {
616 match self {
617 Self::Requirements(s) => Some(s),
618 _ => None,
619 }
620 }
621
622 pub fn into_requirements_section(self) -> Option<RequirementsSection<N>> {
629 match self {
630 Self::Requirements(s) => Some(s),
631 _ => None,
632 }
633 }
634
635 pub fn as_hints_section(&self) -> Option<&TaskHintsSection<N>> {
641 match self {
642 Self::Hints(s) => Some(s),
643 _ => None,
644 }
645 }
646
647 pub fn into_hints_section(self) -> Option<TaskHintsSection<N>> {
653 match self {
654 Self::Hints(s) => Some(s),
655 _ => None,
656 }
657 }
658
659 pub fn as_runtime_section(&self) -> Option<&RuntimeSection<N>> {
665 match self {
666 Self::Runtime(s) => Some(s),
667 _ => None,
668 }
669 }
670
671 pub fn into_runtime_section(self) -> Option<RuntimeSection<N>> {
677 match self {
678 Self::Runtime(s) => Some(s),
679 _ => None,
680 }
681 }
682
683 pub fn as_metadata_section(&self) -> Option<&MetadataSection<N>> {
689 match self {
690 Self::Metadata(s) => Some(s),
691 _ => None,
692 }
693 }
694
695 pub fn into_metadata_section(self) -> Option<MetadataSection<N>> {
701 match self {
702 Self::Metadata(s) => Some(s),
703 _ => None,
704 }
705 }
706
707 pub fn as_parameter_metadata_section(&self) -> Option<&ParameterMetadataSection<N>> {
714 match self {
715 Self::ParameterMetadata(s) => Some(s),
716 _ => None,
717 }
718 }
719
720 pub fn into_parameter_metadata_section(self) -> Option<ParameterMetadataSection<N>> {
727 match self {
728 Self::ParameterMetadata(s) => Some(s),
729 _ => None,
730 }
731 }
732
733 pub fn as_declaration(&self) -> Option<&BoundDecl<N>> {
739 match self {
740 Self::Declaration(d) => Some(d),
741 _ => None,
742 }
743 }
744
745 pub fn into_declaration(self) -> Option<BoundDecl<N>> {
751 match self {
752 Self::Declaration(d) => Some(d),
753 _ => None,
754 }
755 }
756
757 pub fn child(node: &N) -> Option<Self> {
759 node.children().find_map(Self::cast)
760 }
761
762 pub fn children(node: &N) -> impl Iterator<Item = Self> + use<'_, N> {
764 node.children().filter_map(Self::cast)
765 }
766}
767
768#[derive(Clone, Debug, PartialEq, Eq)]
770pub enum SectionParent<N: TreeNode = SyntaxNode> {
771 Task(TaskDefinition<N>),
773 Workflow(WorkflowDefinition<N>),
775 Struct(StructDefinition<N>),
777}
778
779impl<N: TreeNode> SectionParent<N> {
780 pub fn can_cast(kind: SyntaxKind) -> bool {
783 matches!(
784 kind,
785 SyntaxKind::TaskDefinitionNode
786 | SyntaxKind::WorkflowDefinitionNode
787 | SyntaxKind::StructDefinitionNode
788 )
789 }
790
791 pub fn cast(inner: N) -> Option<Self> {
795 match inner.kind() {
796 SyntaxKind::TaskDefinitionNode => Some(Self::Task(
797 TaskDefinition::cast(inner).expect("task definition to cast"),
798 )),
799 SyntaxKind::WorkflowDefinitionNode => Some(Self::Workflow(
800 WorkflowDefinition::cast(inner).expect("workflow definition to cast"),
801 )),
802 SyntaxKind::StructDefinitionNode => Some(Self::Struct(
803 StructDefinition::cast(inner).expect("struct definition to cast"),
804 )),
805 _ => None,
806 }
807 }
808
809 pub fn inner(&self) -> &N {
811 match self {
812 Self::Task(element) => element.inner(),
813 Self::Workflow(element) => element.inner(),
814 Self::Struct(element) => element.inner(),
815 }
816 }
817
818 pub fn name(&self) -> Ident<N::Token> {
820 match self {
821 Self::Task(t) => t.name(),
822 Self::Workflow(w) => w.name(),
823 Self::Struct(s) => s.name(),
824 }
825 }
826
827 pub fn as_task(&self) -> Option<&TaskDefinition<N>> {
833 match self {
834 Self::Task(task) => Some(task),
835 _ => None,
836 }
837 }
838
839 pub fn into_task(self) -> Option<TaskDefinition<N>> {
845 match self {
846 Self::Task(task) => Some(task),
847 _ => None,
848 }
849 }
850
851 pub fn unwrap_task(self) -> TaskDefinition<N> {
857 match self {
858 Self::Task(task) => task,
859 _ => panic!("not a task definition"),
860 }
861 }
862
863 pub fn as_workflow(&self) -> Option<&WorkflowDefinition<N>> {
869 match self {
870 Self::Workflow(workflow) => Some(workflow),
871 _ => None,
872 }
873 }
874
875 pub fn into_workflow(self) -> Option<WorkflowDefinition<N>> {
881 match self {
882 Self::Workflow(workflow) => Some(workflow),
883 _ => None,
884 }
885 }
886
887 pub fn unwrap_workflow(self) -> WorkflowDefinition<N> {
893 match self {
894 Self::Workflow(workflow) => workflow,
895 _ => panic!("not a workflow definition"),
896 }
897 }
898
899 pub fn as_struct(&self) -> Option<&StructDefinition<N>> {
905 match self {
906 Self::Struct(r#struct) => Some(r#struct),
907 _ => None,
908 }
909 }
910
911 pub fn into_struct(self) -> Option<StructDefinition<N>> {
917 match self {
918 Self::Struct(r#struct) => Some(r#struct),
919 _ => None,
920 }
921 }
922
923 pub fn unwrap_struct(self) -> StructDefinition<N> {
929 match self {
930 Self::Struct(def) => def,
931 _ => panic!("not a struct definition"),
932 }
933 }
934
935 pub fn child(node: &N) -> Option<Self> {
937 node.children().find_map(Self::cast)
938 }
939
940 pub fn children(node: &N) -> impl Iterator<Item = Self> + use<'_, N> {
942 node.children().filter_map(Self::cast)
943 }
944}
945
946#[derive(Clone, Debug, PartialEq, Eq)]
948pub struct InputSection<N: TreeNode = SyntaxNode>(N);
949
950impl<N: TreeNode> InputSection<N> {
951 pub fn declarations(&self) -> impl Iterator<Item = Decl<N>> + use<'_, N> {
953 Decl::children(&self.0)
954 }
955
956 pub fn parent(&self) -> SectionParent<N> {
958 SectionParent::cast(self.0.parent().expect("should have a parent"))
959 .expect("parent should cast")
960 }
961}
962
963impl<N: TreeNode> AstNode<N> for InputSection<N> {
964 fn can_cast(kind: SyntaxKind) -> bool {
965 kind == SyntaxKind::InputSectionNode
966 }
967
968 fn cast(inner: N) -> Option<Self> {
969 match inner.kind() {
970 SyntaxKind::InputSectionNode => Some(Self(inner)),
971 _ => None,
972 }
973 }
974
975 fn inner(&self) -> &N {
976 &self.0
977 }
978}
979
980#[derive(Clone, Debug, PartialEq, Eq)]
982pub struct OutputSection<N: TreeNode = SyntaxNode>(N);
983
984impl<N: TreeNode> OutputSection<N> {
985 pub fn declarations(&self) -> impl Iterator<Item = BoundDecl<N>> + use<'_, N> {
987 self.children()
988 }
989
990 pub fn parent(&self) -> SectionParent<N> {
992 SectionParent::cast(self.0.parent().expect("should have a parent"))
993 .expect("parent should cast")
994 }
995}
996
997impl<N: TreeNode> AstNode<N> for OutputSection<N> {
998 fn can_cast(kind: SyntaxKind) -> bool {
999 kind == SyntaxKind::OutputSectionNode
1000 }
1001
1002 fn cast(inner: N) -> Option<Self> {
1003 match inner.kind() {
1004 SyntaxKind::OutputSectionNode => Some(Self(inner)),
1005 _ => None,
1006 }
1007 }
1008
1009 fn inner(&self) -> &N {
1010 &self.0
1011 }
1012}
1013
1014#[derive(Clone, Debug, PartialEq, Eq)]
1018pub enum StrippedCommandPart<N: TreeNode = SyntaxNode> {
1019 Text(String),
1021 Placeholder(Placeholder<N>),
1023}
1024
1025#[derive(Clone, Debug, PartialEq, Eq)]
1027pub struct CommandSection<N: TreeNode = SyntaxNode>(N);
1028
1029impl<N: TreeNode> CommandSection<N> {
1030 pub fn is_heredoc(&self) -> bool {
1032 self.token::<OpenHeredoc<N::Token>>().is_some()
1033 }
1034
1035 pub fn parts(&self) -> impl Iterator<Item = CommandPart<N>> + use<'_, N> {
1037 self.0.children_with_tokens().filter_map(CommandPart::cast)
1038 }
1039
1040 pub fn count_whitespace(&self) -> Option<usize> {
1044 let mut min_leading_spaces = usize::MAX;
1045 let mut min_leading_tabs = usize::MAX;
1046 let mut parsing_leading_whitespace = false; let mut leading_spaces = 0;
1049 let mut leading_tabs = 0;
1050 for part in self.parts() {
1051 match part {
1052 CommandPart::Text(text) => {
1053 for c in text.text().chars() {
1054 match c {
1055 ' ' if parsing_leading_whitespace => {
1056 leading_spaces += 1;
1057 }
1058 '\t' if parsing_leading_whitespace => {
1059 leading_tabs += 1;
1060 }
1061 '\n' => {
1062 parsing_leading_whitespace = true;
1063 leading_spaces = 0;
1064 leading_tabs = 0;
1065 }
1066 '\r' => {}
1067 _ => {
1068 if parsing_leading_whitespace {
1069 parsing_leading_whitespace = false;
1070 if leading_spaces == 0 && leading_tabs == 0 {
1071 min_leading_spaces = 0;
1072 min_leading_tabs = 0;
1073 continue;
1074 }
1075 if leading_spaces < min_leading_spaces && leading_spaces > 0 {
1076 min_leading_spaces = leading_spaces;
1077 }
1078 if leading_tabs < min_leading_tabs && leading_tabs > 0 {
1079 min_leading_tabs = leading_tabs;
1080 }
1081 }
1082 }
1083 }
1084 }
1085 }
1087 CommandPart::Placeholder(_) => {
1088 if parsing_leading_whitespace {
1089 parsing_leading_whitespace = false;
1090 if leading_spaces == 0 && leading_tabs == 0 {
1091 min_leading_spaces = 0;
1092 min_leading_tabs = 0;
1093 continue;
1094 }
1095 if leading_spaces < min_leading_spaces && leading_spaces > 0 {
1096 min_leading_spaces = leading_spaces;
1097 }
1098 if leading_tabs < min_leading_tabs && leading_tabs > 0 {
1099 min_leading_tabs = leading_tabs;
1100 }
1101 }
1102 }
1103 }
1104 }
1105
1106 if (min_leading_spaces == 0 && min_leading_tabs == 0)
1108 || (min_leading_spaces == usize::MAX && min_leading_tabs == usize::MAX)
1109 {
1110 return Some(0);
1111 }
1112
1113 if (min_leading_spaces > 0 && min_leading_spaces != usize::MAX)
1115 && (min_leading_tabs > 0 && min_leading_tabs != usize::MAX)
1116 {
1117 return None;
1118 }
1119
1120 let final_leading_whitespace = if min_leading_spaces < min_leading_tabs {
1123 min_leading_spaces
1124 } else {
1125 min_leading_tabs
1126 };
1127
1128 Some(final_leading_whitespace)
1129 }
1130
1131 pub fn strip_whitespace(&self) -> Option<Vec<StrippedCommandPart<N>>> {
1135 let mut result = Vec::new();
1136 let heredoc = self.is_heredoc();
1137 for part in self.parts() {
1138 match part {
1139 CommandPart::Text(text) => {
1140 let mut s = String::new();
1141 unescape_command_text(text.text(), heredoc, &mut s);
1142 result.push(StrippedCommandPart::Text(s));
1143 }
1144 CommandPart::Placeholder(p) => {
1145 result.push(StrippedCommandPart::Placeholder(p));
1146 }
1147 }
1148 }
1149
1150 let mut whole_first_line_trimmed = false;
1152 if let Some(StrippedCommandPart::Text(text)) = result.first_mut() {
1153 let end_of_first_line = text.find('\n').map(|p| p + 1).unwrap_or(text.len());
1154 let line = &text[..end_of_first_line];
1155 let len = line.len() - line.trim_start().len();
1156 whole_first_line_trimmed = len == line.len();
1157 text.replace_range(..len, "");
1158 }
1159
1160 if let Some(StrippedCommandPart::Text(text)) = result.last_mut() {
1162 if let Some(index) = text.rfind(|c| !matches!(c, ' ' | '\t')) {
1163 text.truncate(index + 1);
1164 } else {
1165 text.clear();
1166 }
1167
1168 if text.ends_with('\n') {
1169 text.pop();
1170 }
1171
1172 if text.ends_with('\r') {
1173 text.pop();
1174 }
1175 }
1176
1177 let num_stripped_chars = self.count_whitespace()?;
1179
1180 if num_stripped_chars == 0 {
1182 return Some(result);
1183 }
1184
1185 let mut strip_leading_whitespace = whole_first_line_trimmed;
1189 for part in &mut result {
1190 match part {
1191 StrippedCommandPart::Text(text) => {
1192 let mut offset = 0;
1193 while let Some(next) = text[offset..].find('\n') {
1194 let next = next + offset;
1195 if offset > 0 {
1196 strip_leading_whitespace = true;
1197 }
1198
1199 if !strip_leading_whitespace {
1200 offset = next + 1;
1201 continue;
1202 }
1203
1204 let line = &text[offset..next];
1205 let line = line.strip_suffix('\r').unwrap_or(line);
1206 let len = line.len().min(num_stripped_chars);
1207 text.replace_range(offset..offset + len, "");
1208 offset = next + 1 - len;
1209 }
1210
1211 if strip_leading_whitespace || offset > 0 {
1213 let line = &text[offset..];
1214 let line = line.strip_suffix('\r').unwrap_or(line);
1215 let len = line.len().min(num_stripped_chars);
1216 text.replace_range(offset..offset + len, "");
1217 }
1218 }
1219 StrippedCommandPart::Placeholder(_) => {
1220 strip_leading_whitespace = false;
1221 }
1222 }
1223 }
1224
1225 Some(result)
1226 }
1227
1228 pub fn parent(&self) -> SectionParent<N> {
1230 SectionParent::cast(self.0.parent().expect("should have a parent"))
1231 .expect("parent should cast")
1232 }
1233}
1234
1235impl<N: TreeNode> AstNode<N> for CommandSection<N> {
1236 fn can_cast(kind: SyntaxKind) -> bool {
1237 kind == SyntaxKind::CommandSectionNode
1238 }
1239
1240 fn cast(inner: N) -> Option<Self> {
1241 match inner.kind() {
1242 SyntaxKind::CommandSectionNode => Some(Self(inner)),
1243 _ => None,
1244 }
1245 }
1246
1247 fn inner(&self) -> &N {
1248 &self.0
1249 }
1250}
1251
1252#[derive(Clone, Debug, PartialEq, Eq)]
1254pub struct CommandText<T: TreeToken = SyntaxToken>(T);
1255
1256impl<T: TreeToken> CommandText<T> {
1257 pub fn unescape_to(&self, heredoc: bool, buffer: &mut String) {
1263 unescape_command_text(self.text(), heredoc, buffer);
1264 }
1265}
1266
1267impl<T: TreeToken> AstToken<T> for CommandText<T> {
1268 fn can_cast(kind: SyntaxKind) -> bool {
1269 kind == SyntaxKind::LiteralCommandText
1270 }
1271
1272 fn cast(inner: T) -> Option<Self> {
1273 match inner.kind() {
1274 SyntaxKind::LiteralCommandText => Some(Self(inner)),
1275 _ => None,
1276 }
1277 }
1278
1279 fn inner(&self) -> &T {
1280 &self.0
1281 }
1282}
1283
1284#[derive(Clone, Debug, PartialEq, Eq)]
1286pub enum CommandPart<N: TreeNode = SyntaxNode> {
1287 Text(CommandText<N::Token>),
1289 Placeholder(Placeholder<N>),
1291}
1292
1293impl<N: TreeNode> CommandPart<N> {
1294 pub fn unwrap_text(self) -> CommandText<N::Token> {
1300 match self {
1301 Self::Text(text) => text,
1302 _ => panic!("not string text"),
1303 }
1304 }
1305
1306 pub fn unwrap_placeholder(self) -> Placeholder<N> {
1312 match self {
1313 Self::Placeholder(p) => p,
1314 _ => panic!("not a placeholder"),
1315 }
1316 }
1317
1318 fn cast(element: NodeOrToken<N, N::Token>) -> Option<Self> {
1322 match element {
1323 NodeOrToken::Node(n) => Some(Self::Placeholder(Placeholder::cast(n)?)),
1324 NodeOrToken::Token(t) => Some(Self::Text(CommandText::cast(t)?)),
1325 }
1326 }
1327}
1328
1329#[derive(Clone, Debug, PartialEq, Eq)]
1331pub struct RequirementsSection<N: TreeNode = SyntaxNode>(N);
1332
1333impl<N: TreeNode> RequirementsSection<N> {
1334 pub fn items(&self) -> impl Iterator<Item = RequirementsItem<N>> + use<'_, N> {
1336 self.children()
1337 }
1338
1339 pub fn parent(&self) -> SectionParent<N> {
1341 SectionParent::cast(self.0.parent().expect("should have a parent"))
1342 .expect("parent should cast")
1343 }
1344
1345 pub fn container(&self) -> Option<requirements::item::Container<N>> {
1348 self.child()
1351 }
1352}
1353
1354impl<N: TreeNode> AstNode<N> for RequirementsSection<N> {
1355 fn can_cast(kind: SyntaxKind) -> bool {
1356 kind == SyntaxKind::RequirementsSectionNode
1357 }
1358
1359 fn cast(inner: N) -> Option<Self> {
1360 match inner.kind() {
1361 SyntaxKind::RequirementsSectionNode => Some(Self(inner)),
1362 _ => None,
1363 }
1364 }
1365
1366 fn inner(&self) -> &N {
1367 &self.0
1368 }
1369}
1370
1371#[derive(Clone, Debug, PartialEq, Eq)]
1373pub struct RequirementsItem<N: TreeNode = SyntaxNode>(N);
1374
1375impl<N: TreeNode> RequirementsItem<N> {
1376 pub fn name(&self) -> Ident<N::Token> {
1378 self.token().expect("expected an item name")
1379 }
1380
1381 pub fn expr(&self) -> Expr<N> {
1383 Expr::child(&self.0).expect("expected an item expression")
1384 }
1385
1386 pub fn into_container(self) -> Option<requirements::item::Container<N>> {
1389 requirements::item::Container::try_from(self).ok()
1390 }
1391}
1392
1393impl<N: TreeNode> AstNode<N> for RequirementsItem<N> {
1394 fn can_cast(kind: SyntaxKind) -> bool {
1395 kind == SyntaxKind::RequirementsItemNode
1396 }
1397
1398 fn cast(inner: N) -> Option<Self> {
1399 match inner.kind() {
1400 SyntaxKind::RequirementsItemNode => Some(Self(inner)),
1401 _ => None,
1402 }
1403 }
1404
1405 fn inner(&self) -> &N {
1406 &self.0
1407 }
1408}
1409
1410#[derive(Clone, Debug, PartialEq, Eq)]
1412pub struct TaskHintsSection<N: TreeNode = SyntaxNode>(N);
1413
1414impl<N: TreeNode> TaskHintsSection<N> {
1415 pub fn items(&self) -> impl Iterator<Item = TaskHintsItem<N>> + use<'_, N> {
1417 self.children()
1418 }
1419
1420 pub fn parent(&self) -> TaskDefinition<N> {
1422 TaskDefinition::cast(self.0.parent().expect("should have a parent"))
1423 .expect("parent should cast")
1424 }
1425}
1426
1427impl<N: TreeNode> AstNode<N> for TaskHintsSection<N> {
1428 fn can_cast(kind: SyntaxKind) -> bool {
1429 kind == SyntaxKind::TaskHintsSectionNode
1430 }
1431
1432 fn cast(inner: N) -> Option<Self> {
1433 match inner.kind() {
1434 SyntaxKind::TaskHintsSectionNode => Some(Self(inner)),
1435 _ => None,
1436 }
1437 }
1438
1439 fn inner(&self) -> &N {
1440 &self.0
1441 }
1442}
1443
1444#[derive(Clone, Debug, PartialEq, Eq)]
1446pub struct TaskHintsItem<N: TreeNode = SyntaxNode>(N);
1447
1448impl<N: TreeNode> TaskHintsItem<N> {
1449 pub fn name(&self) -> Ident<N::Token> {
1451 self.token().expect("expected an item name")
1452 }
1453
1454 pub fn expr(&self) -> Expr<N> {
1456 Expr::child(&self.0).expect("expected an item expression")
1457 }
1458}
1459
1460impl<N: TreeNode> AstNode<N> for TaskHintsItem<N> {
1461 fn can_cast(kind: SyntaxKind) -> bool {
1462 kind == SyntaxKind::TaskHintsItemNode
1463 }
1464
1465 fn cast(inner: N) -> Option<Self> {
1466 match inner.kind() {
1467 SyntaxKind::TaskHintsItemNode => Some(Self(inner)),
1468 _ => None,
1469 }
1470 }
1471
1472 fn inner(&self) -> &N {
1473 &self.0
1474 }
1475}
1476
1477#[derive(Clone, Debug, PartialEq, Eq)]
1479pub struct RuntimeSection<N: TreeNode = SyntaxNode>(N);
1480
1481impl<N: TreeNode> RuntimeSection<N> {
1482 pub fn items(&self) -> impl Iterator<Item = RuntimeItem<N>> + use<'_, N> {
1484 self.children()
1485 }
1486
1487 pub fn parent(&self) -> SectionParent<N> {
1489 SectionParent::cast(self.0.parent().expect("should have a parent"))
1490 .expect("parent should cast")
1491 }
1492
1493 pub fn container(&self) -> Option<runtime::item::Container<N>> {
1496 self.child()
1499 }
1500}
1501
1502impl<N: TreeNode> AstNode<N> for RuntimeSection<N> {
1503 fn can_cast(kind: SyntaxKind) -> bool {
1504 kind == SyntaxKind::RuntimeSectionNode
1505 }
1506
1507 fn cast(inner: N) -> Option<Self> {
1508 match inner.kind() {
1509 SyntaxKind::RuntimeSectionNode => Some(Self(inner)),
1510 _ => None,
1511 }
1512 }
1513
1514 fn inner(&self) -> &N {
1515 &self.0
1516 }
1517}
1518
1519#[derive(Clone, Debug, PartialEq, Eq)]
1521pub struct RuntimeItem<N: TreeNode = SyntaxNode>(N);
1522
1523impl<N: TreeNode> RuntimeItem<N> {
1524 pub fn name(&self) -> Ident<N::Token> {
1526 self.token().expect("expected an item name")
1527 }
1528
1529 pub fn expr(&self) -> Expr<N> {
1531 Expr::child(&self.0).expect("expected an item expression")
1532 }
1533
1534 pub fn into_container(self) -> Option<runtime::item::Container<N>> {
1537 runtime::item::Container::try_from(self).ok()
1538 }
1539}
1540
1541impl<N: TreeNode> AstNode<N> for RuntimeItem<N> {
1542 fn can_cast(kind: SyntaxKind) -> bool {
1543 kind == SyntaxKind::RuntimeItemNode
1544 }
1545
1546 fn cast(inner: N) -> Option<Self> {
1547 match inner.kind() {
1548 SyntaxKind::RuntimeItemNode => Some(Self(inner)),
1549 _ => None,
1550 }
1551 }
1552
1553 fn inner(&self) -> &N {
1554 &self.0
1555 }
1556}
1557
1558#[derive(Clone, Debug, PartialEq, Eq)]
1560pub struct MetadataSection<N: TreeNode = SyntaxNode>(N);
1561
1562impl<N: TreeNode> MetadataSection<N> {
1563 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1565 self.children()
1566 }
1567
1568 pub fn parent(&self) -> SectionParent<N> {
1570 SectionParent::cast(self.0.parent().expect("should have a parent"))
1571 .expect("parent should cast")
1572 }
1573}
1574
1575impl<N: TreeNode> AstNode<N> for MetadataSection<N> {
1576 fn can_cast(kind: SyntaxKind) -> bool {
1577 kind == SyntaxKind::MetadataSectionNode
1578 }
1579
1580 fn cast(inner: N) -> Option<Self> {
1581 match inner.kind() {
1582 SyntaxKind::MetadataSectionNode => Some(Self(inner)),
1583 _ => None,
1584 }
1585 }
1586
1587 fn inner(&self) -> &N {
1588 &self.0
1589 }
1590}
1591
1592#[derive(Clone, Debug, PartialEq, Eq)]
1594pub struct MetadataObjectItem<N: TreeNode = SyntaxNode>(N);
1595
1596impl<N: TreeNode> MetadataObjectItem<N> {
1597 pub fn name(&self) -> Ident<N::Token> {
1599 self.token().expect("expected a name")
1600 }
1601
1602 pub fn value(&self) -> MetadataValue<N> {
1604 self.child().expect("expected a value")
1605 }
1606}
1607
1608impl<N: TreeNode> AstNode<N> for MetadataObjectItem<N> {
1609 fn can_cast(kind: SyntaxKind) -> bool {
1610 kind == SyntaxKind::MetadataObjectItemNode
1611 }
1612
1613 fn cast(inner: N) -> Option<Self> {
1614 match inner.kind() {
1615 SyntaxKind::MetadataObjectItemNode => Some(Self(inner)),
1616 _ => None,
1617 }
1618 }
1619
1620 fn inner(&self) -> &N {
1621 &self.0
1622 }
1623}
1624
1625#[derive(Clone, Debug, PartialEq, Eq)]
1627pub enum MetadataValue<N: TreeNode = SyntaxNode> {
1628 Boolean(LiteralBoolean<N>),
1630 Integer(LiteralInteger<N>),
1632 Float(LiteralFloat<N>),
1634 String(LiteralString<N>),
1636 Null(LiteralNull<N>),
1638 Object(MetadataObject<N>),
1640 Array(MetadataArray<N>),
1642}
1643
1644impl<N: TreeNode> MetadataValue<N> {
1645 pub fn unwrap_boolean(self) -> LiteralBoolean<N> {
1651 match self {
1652 Self::Boolean(b) => b,
1653 _ => panic!("not a boolean"),
1654 }
1655 }
1656
1657 pub fn unwrap_integer(self) -> LiteralInteger<N> {
1663 match self {
1664 Self::Integer(i) => i,
1665 _ => panic!("not an integer"),
1666 }
1667 }
1668
1669 pub fn unwrap_float(self) -> LiteralFloat<N> {
1675 match self {
1676 Self::Float(f) => f,
1677 _ => panic!("not a float"),
1678 }
1679 }
1680
1681 pub fn unwrap_string(self) -> LiteralString<N> {
1687 match self {
1688 Self::String(s) => s,
1689 _ => panic!("not a string"),
1690 }
1691 }
1692
1693 pub fn unwrap_null(self) -> LiteralNull<N> {
1699 match self {
1700 Self::Null(n) => n,
1701 _ => panic!("not a null"),
1702 }
1703 }
1704
1705 pub fn unwrap_object(self) -> MetadataObject<N> {
1711 match self {
1712 Self::Object(o) => o,
1713 _ => panic!("not an object"),
1714 }
1715 }
1716
1717 pub fn unwrap_array(self) -> MetadataArray<N> {
1723 match self {
1724 Self::Array(a) => a,
1725 _ => panic!("not an array"),
1726 }
1727 }
1728}
1729
1730impl<N: TreeNode> AstNode<N> for MetadataValue<N> {
1731 fn can_cast(kind: SyntaxKind) -> bool {
1732 matches!(
1733 kind,
1734 SyntaxKind::LiteralBooleanNode
1735 | SyntaxKind::LiteralIntegerNode
1736 | SyntaxKind::LiteralFloatNode
1737 | SyntaxKind::LiteralStringNode
1738 | SyntaxKind::LiteralNullNode
1739 | SyntaxKind::MetadataObjectNode
1740 | SyntaxKind::MetadataArrayNode
1741 )
1742 }
1743
1744 fn cast(inner: N) -> Option<Self> {
1745 match inner.kind() {
1746 SyntaxKind::LiteralBooleanNode => Some(Self::Boolean(LiteralBoolean(inner))),
1747 SyntaxKind::LiteralIntegerNode => Some(Self::Integer(LiteralInteger(inner))),
1748 SyntaxKind::LiteralFloatNode => Some(Self::Float(LiteralFloat(inner))),
1749 SyntaxKind::LiteralStringNode => Some(Self::String(LiteralString(inner))),
1750 SyntaxKind::LiteralNullNode => Some(Self::Null(LiteralNull(inner))),
1751 SyntaxKind::MetadataObjectNode => Some(Self::Object(MetadataObject(inner))),
1752 SyntaxKind::MetadataArrayNode => Some(Self::Array(MetadataArray(inner))),
1753 _ => None,
1754 }
1755 }
1756
1757 fn inner(&self) -> &N {
1758 match self {
1759 Self::Boolean(b) => &b.0,
1760 Self::Integer(i) => &i.0,
1761 Self::Float(f) => &f.0,
1762 Self::String(s) => &s.0,
1763 Self::Null(n) => &n.0,
1764 Self::Object(o) => &o.0,
1765 Self::Array(a) => &a.0,
1766 }
1767 }
1768}
1769
1770#[derive(Clone, Debug, PartialEq, Eq)]
1772pub struct LiteralNull<N: TreeNode = SyntaxNode>(N);
1773
1774impl<N: TreeNode> AstNode<N> for LiteralNull<N> {
1775 fn can_cast(kind: SyntaxKind) -> bool {
1776 kind == SyntaxKind::LiteralNullNode
1777 }
1778
1779 fn cast(inner: N) -> Option<Self> {
1780 match inner.kind() {
1781 SyntaxKind::LiteralNullNode => Some(Self(inner)),
1782 _ => None,
1783 }
1784 }
1785
1786 fn inner(&self) -> &N {
1787 &self.0
1788 }
1789}
1790
1791#[derive(Clone, Debug, PartialEq, Eq)]
1793pub struct MetadataObject<N: TreeNode = SyntaxNode>(N);
1794
1795impl<N: TreeNode> MetadataObject<N> {
1796 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1798 self.children()
1799 }
1800}
1801
1802impl<N: TreeNode> AstNode<N> for MetadataObject<N> {
1803 fn can_cast(kind: SyntaxKind) -> bool {
1804 kind == SyntaxKind::MetadataObjectNode
1805 }
1806
1807 fn cast(inner: N) -> Option<Self> {
1808 match inner.kind() {
1809 SyntaxKind::MetadataObjectNode => Some(Self(inner)),
1810 _ => None,
1811 }
1812 }
1813
1814 fn inner(&self) -> &N {
1815 &self.0
1816 }
1817}
1818
1819#[derive(Clone, Debug, PartialEq, Eq)]
1821pub struct MetadataArray<N: TreeNode = SyntaxNode>(N);
1822
1823impl<N: TreeNode> MetadataArray<N> {
1824 pub fn elements(&self) -> impl Iterator<Item = MetadataValue<N>> + use<'_, N> {
1826 self.children()
1827 }
1828}
1829
1830impl<N: TreeNode> AstNode<N> for MetadataArray<N> {
1831 fn can_cast(kind: SyntaxKind) -> bool {
1832 kind == SyntaxKind::MetadataArrayNode
1833 }
1834
1835 fn cast(inner: N) -> Option<Self> {
1836 match inner.kind() {
1837 SyntaxKind::MetadataArrayNode => Some(Self(inner)),
1838 _ => None,
1839 }
1840 }
1841
1842 fn inner(&self) -> &N {
1843 &self.0
1844 }
1845}
1846
1847#[derive(Clone, Debug, PartialEq, Eq)]
1849pub struct ParameterMetadataSection<N: TreeNode = SyntaxNode>(N);
1850
1851impl<N: TreeNode> ParameterMetadataSection<N> {
1852 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1854 self.children()
1855 }
1856
1857 pub fn parent(&self) -> SectionParent<N> {
1859 SectionParent::cast(self.0.parent().expect("should have a parent"))
1860 .expect("parent should cast")
1861 }
1862}
1863
1864impl<N: TreeNode> AstNode<N> for ParameterMetadataSection<N> {
1865 fn can_cast(kind: SyntaxKind) -> bool {
1866 kind == SyntaxKind::ParameterMetadataSectionNode
1867 }
1868
1869 fn cast(inner: N) -> Option<Self> {
1870 match inner.kind() {
1871 SyntaxKind::ParameterMetadataSectionNode => Some(Self(inner)),
1872 _ => None,
1873 }
1874 }
1875
1876 fn inner(&self) -> &N {
1877 &self.0
1878 }
1879}
1880
1881#[cfg(test)]
1882mod test {
1883 use pretty_assertions::assert_eq;
1884
1885 use super::*;
1886 use crate::Document;
1887
1888 #[test]
1889 fn tasks() {
1890 let (document, diagnostics) = Document::parse(
1891 r#"
1892version 1.2
1893
1894task test {
1895 input {
1896 String name
1897 }
1898
1899 output {
1900 String output = stdout()
1901 }
1902
1903 command <<<
1904 printf "hello, ~{name}!
1905 >>>
1906
1907 requirements {
1908 container: "baz/qux"
1909 }
1910
1911 hints {
1912 foo: "bar"
1913 }
1914
1915 runtime {
1916 container: "foo/bar"
1917 }
1918
1919 meta {
1920 description: "a test"
1921 foo: null
1922 }
1923
1924 parameter_meta {
1925 name: {
1926 help: "a name to greet"
1927 }
1928 }
1929
1930 String x = "private"
1931}
1932"#,
1933 );
1934
1935 assert!(diagnostics.is_empty());
1936 let ast = document.ast();
1937 let ast = ast.as_v1().expect("should be a V1 AST");
1938 let tasks: Vec<_> = ast.tasks().collect();
1939 assert_eq!(tasks.len(), 1);
1940 assert_eq!(tasks[0].name().text(), "test");
1941
1942 let input = tasks[0].input().expect("should have an input section");
1944 assert_eq!(input.parent().unwrap_task().name().text(), "test");
1945 let decls: Vec<_> = input.declarations().collect();
1946 assert_eq!(decls.len(), 1);
1947 assert_eq!(
1948 decls[0].clone().unwrap_unbound_decl().ty().to_string(),
1949 "String"
1950 );
1951 assert_eq!(decls[0].clone().unwrap_unbound_decl().name().text(), "name");
1952
1953 let output = tasks[0].output().expect("should have an output section");
1955 assert_eq!(output.parent().unwrap_task().name().text(), "test");
1956 let decls: Vec<_> = output.declarations().collect();
1957 assert_eq!(decls.len(), 1);
1958 assert_eq!(decls[0].ty().to_string(), "String");
1959 assert_eq!(decls[0].name().text(), "output");
1960 assert_eq!(decls[0].expr().unwrap_call().target().text(), "stdout");
1961
1962 let command = tasks[0].command().expect("should have a command section");
1964 assert_eq!(command.parent().name().text(), "test");
1965 assert!(command.is_heredoc());
1966 let parts: Vec<_> = command.parts().collect();
1967 assert_eq!(parts.len(), 3);
1968 assert_eq!(
1969 parts[0].clone().unwrap_text().text(),
1970 "\n printf \"hello, "
1971 );
1972 assert_eq!(
1973 parts[1]
1974 .clone()
1975 .unwrap_placeholder()
1976 .expr()
1977 .unwrap_name_ref()
1978 .name()
1979 .text(),
1980 "name"
1981 );
1982 assert_eq!(parts[2].clone().unwrap_text().text(), "!\n ");
1983
1984 let requirements = tasks[0]
1986 .requirements()
1987 .expect("should have a requirements section");
1988 assert_eq!(requirements.parent().name().text(), "test");
1989 let items: Vec<_> = requirements.items().collect();
1990 assert_eq!(items.len(), 1);
1991 assert_eq!(items[0].name().text(), TASK_REQUIREMENT_CONTAINER);
1992 assert_eq!(
1993 items[0]
1994 .expr()
1995 .unwrap_literal()
1996 .unwrap_string()
1997 .text()
1998 .unwrap()
1999 .text(),
2000 "baz/qux"
2001 );
2002
2003 let hints = tasks[0].hints().expect("should have a hints section");
2005 assert_eq!(hints.parent().name().text(), "test");
2006 let items: Vec<_> = hints.items().collect();
2007 assert_eq!(items.len(), 1);
2008 assert_eq!(items[0].name().text(), "foo");
2009 assert_eq!(
2010 items[0]
2011 .expr()
2012 .unwrap_literal()
2013 .unwrap_string()
2014 .text()
2015 .unwrap()
2016 .text(),
2017 "bar"
2018 );
2019
2020 let runtime = tasks[0].runtime().expect("should have a runtime section");
2022 assert_eq!(runtime.parent().name().text(), "test");
2023 let items: Vec<_> = runtime.items().collect();
2024 assert_eq!(items.len(), 1);
2025 assert_eq!(items[0].name().text(), TASK_REQUIREMENT_CONTAINER);
2026 assert_eq!(
2027 items[0]
2028 .expr()
2029 .unwrap_literal()
2030 .unwrap_string()
2031 .text()
2032 .unwrap()
2033 .text(),
2034 "foo/bar"
2035 );
2036
2037 let metadata = tasks[0].metadata().expect("should have a metadata section");
2039 assert_eq!(metadata.parent().unwrap_task().name().text(), "test");
2040 let items: Vec<_> = metadata.items().collect();
2041 assert_eq!(items.len(), 2);
2042 assert_eq!(items[0].name().text(), "description");
2043 assert_eq!(
2044 items[0].value().unwrap_string().text().unwrap().text(),
2045 "a test"
2046 );
2047
2048 assert_eq!(items[1].name().text(), "foo");
2050 items[1].value().unwrap_null();
2051
2052 let param_meta = tasks[0]
2054 .parameter_metadata()
2055 .expect("should have a parameter metadata section");
2056 assert_eq!(param_meta.parent().unwrap_task().name().text(), "test");
2057 let items: Vec<_> = param_meta.items().collect();
2058 assert_eq!(items.len(), 1);
2059 assert_eq!(items[0].name().text(), "name");
2060 let items: Vec<_> = items[0].value().unwrap_object().items().collect();
2061 assert_eq!(items.len(), 1);
2062 assert_eq!(items[0].name().text(), "help");
2063 assert_eq!(
2064 items[0].value().unwrap_string().text().unwrap().text(),
2065 "a name to greet"
2066 );
2067
2068 let decls: Vec<_> = tasks[0].declarations().collect();
2070 assert_eq!(decls.len(), 1);
2071
2072 assert_eq!(decls[0].ty().to_string(), "String");
2074 assert_eq!(decls[0].name().text(), "x");
2075 assert_eq!(
2076 decls[0]
2077 .expr()
2078 .unwrap_literal()
2079 .unwrap_string()
2080 .text()
2081 .unwrap()
2082 .text(),
2083 "private"
2084 );
2085 }
2086
2087 #[test]
2088 fn whitespace_stripping_without_interpolation() {
2089 let (document, diagnostics) = Document::parse(
2090 r#"
2091version 1.2
2092
2093task test {
2094 command <<<
2095 echo "hello"
2096 echo "world"
2097 echo \
2098 "goodbye"
2099 >>>
2100}
2101"#,
2102 );
2103
2104 assert!(diagnostics.is_empty());
2105 let ast = document.ast();
2106 let ast = ast.as_v1().expect("should be a V1 AST");
2107 let tasks: Vec<_> = ast.tasks().collect();
2108 assert_eq!(tasks.len(), 1);
2109
2110 let command = tasks[0].command().expect("should have a command section");
2111
2112 let stripped = command.strip_whitespace().unwrap();
2113
2114 assert_eq!(stripped.len(), 1);
2115 let text = match &stripped[0] {
2116 StrippedCommandPart::Text(text) => text,
2117 _ => panic!("expected text"),
2118 };
2119 assert_eq!(
2120 text,
2121 "echo \"hello\"\necho \"world\"\necho \\\n \"goodbye\""
2122 );
2123 }
2124
2125 #[test]
2126 fn whitespace_stripping_with_interpolation() {
2127 let (document, diagnostics) = Document::parse(
2128 r#"
2129version 1.2
2130
2131task test {
2132 input {
2133 String name
2134 Boolean flag
2135 }
2136
2137 command <<<
2138 echo "hello, ~{
2139if flag
2140then name
2141 else "Jerry"
2142 }!"
2143 >>>
2144}
2145 "#,
2146 );
2147
2148 assert!(diagnostics.is_empty());
2149 let ast = document.ast();
2150 let ast = ast.as_v1().expect("should be a V1 AST");
2151 let tasks: Vec<_> = ast.tasks().collect();
2152 assert_eq!(tasks.len(), 1);
2153
2154 let command = tasks[0].command().expect("should have a command section");
2155
2156 let stripped = command.strip_whitespace().unwrap();
2157 assert_eq!(stripped.len(), 3);
2158 let text = match &stripped[0] {
2159 StrippedCommandPart::Text(text) => text,
2160 _ => panic!("expected text"),
2161 };
2162 assert_eq!(text, "echo \"hello, ");
2163
2164 let _placeholder = match &stripped[1] {
2165 StrippedCommandPart::Placeholder(p) => p,
2166 _ => panic!("expected placeholder"),
2167 };
2168 let text = match &stripped[2] {
2171 StrippedCommandPart::Text(text) => text,
2172 _ => panic!("expected text"),
2173 };
2174 assert_eq!(text, "!\"");
2175 }
2176
2177 #[test]
2178 fn whitespace_stripping_when_interpolation_starts_line() {
2179 let (document, diagnostics) = Document::parse(
2180 r#"
2181version 1.2
2182
2183task test {
2184 input {
2185 Int placeholder
2186 }
2187
2188 command <<<
2189 # other weird whitspace
2190 ~{placeholder} "$trailing_pholder" ~{placeholder}
2191 ~{placeholder} somecommand.py "$leading_pholder"
2192 >>>
2193}
2194"#,
2195 );
2196
2197 assert!(diagnostics.is_empty());
2198 let ast = document.ast();
2199 let ast = ast.as_v1().expect("should be a V1 AST");
2200 let tasks: Vec<_> = ast.tasks().collect();
2201 assert_eq!(tasks.len(), 1);
2202
2203 let command = tasks[0].command().expect("should have a command section");
2204
2205 let stripped = command.strip_whitespace().unwrap();
2206 assert_eq!(stripped.len(), 7);
2207 let text = match &stripped[0] {
2208 StrippedCommandPart::Text(text) => text,
2209 _ => panic!("expected text"),
2210 };
2211 assert_eq!(text, " # other weird whitspace\n");
2212
2213 let _placeholder = match &stripped[1] {
2214 StrippedCommandPart::Placeholder(p) => p,
2215 _ => panic!("expected placeholder"),
2216 };
2217 let text = match &stripped[2] {
2220 StrippedCommandPart::Text(text) => text,
2221 _ => panic!("expected text"),
2222 };
2223 assert_eq!(text, " \"$trailing_pholder\" ");
2224
2225 let _placeholder = match &stripped[3] {
2226 StrippedCommandPart::Placeholder(p) => p,
2227 _ => panic!("expected placeholder"),
2228 };
2229 let text = match &stripped[4] {
2232 StrippedCommandPart::Text(text) => text,
2233 _ => panic!("expected text"),
2234 };
2235 assert_eq!(text, "\n");
2236
2237 let _placeholder = match &stripped[5] {
2238 StrippedCommandPart::Placeholder(p) => p,
2239 _ => panic!("expected placeholder"),
2240 };
2241 let text = match &stripped[6] {
2244 StrippedCommandPart::Text(text) => text,
2245 _ => panic!("expected text"),
2246 };
2247 assert_eq!(text, " somecommand.py \"$leading_pholder\"");
2248 }
2249
2250 #[test]
2251 fn whitespace_stripping_when_command_is_empty() {
2252 let (document, diagnostics) = Document::parse(
2253 r#"
2254version 1.2
2255
2256task test {
2257 command <<<>>>
2258}
2259 "#,
2260 );
2261
2262 assert!(diagnostics.is_empty());
2263 let ast = document.ast();
2264 let ast = ast.as_v1().expect("should be a V1 AST");
2265 let tasks: Vec<_> = ast.tasks().collect();
2266 assert_eq!(tasks.len(), 1);
2267
2268 let command = tasks[0].command().expect("should have a command section");
2269
2270 let stripped = command.strip_whitespace().unwrap();
2271 assert_eq!(stripped.len(), 0);
2272 }
2273
2274 #[test]
2275 fn whitespace_stripping_when_command_is_one_line_of_whitespace() {
2276 let (document, diagnostics) = Document::parse(
2277 r#"
2278version 1.2
2279
2280task test {
2281 command <<< >>>
2282}
2283 "#,
2284 );
2285
2286 assert!(diagnostics.is_empty());
2287 let ast = document.ast();
2288 let ast = ast.as_v1().expect("should be a V1 AST");
2289 let tasks: Vec<_> = ast.tasks().collect();
2290 assert_eq!(tasks.len(), 1);
2291
2292 let command = tasks[0].command().expect("should have a command section");
2293
2294 let stripped = command.strip_whitespace().unwrap();
2295 assert_eq!(stripped.len(), 1);
2296 let text = match &stripped[0] {
2297 StrippedCommandPart::Text(text) => text,
2298 _ => panic!("expected text"),
2299 };
2300 assert_eq!(text, "");
2301 }
2302
2303 #[test]
2304 fn whitespace_stripping_when_command_is_one_newline() {
2305 let (document, diagnostics) = Document::parse(
2306 r#"
2307version 1.2
2308
2309task test {
2310 command <<<
2311 >>>
2312}
2313 "#,
2314 );
2315
2316 assert!(diagnostics.is_empty());
2317 let ast = document.ast();
2318 let ast = ast.as_v1().expect("should be a V1 AST");
2319 let tasks: Vec<_> = ast.tasks().collect();
2320 assert_eq!(tasks.len(), 1);
2321
2322 let command = tasks[0].command().expect("should have a command section");
2323
2324 let stripped = command.strip_whitespace().unwrap();
2325 assert_eq!(stripped.len(), 1);
2326 let text = match &stripped[0] {
2327 StrippedCommandPart::Text(text) => text,
2328 _ => panic!("expected text"),
2329 };
2330 assert_eq!(text, "");
2331 }
2332
2333 #[test]
2334 fn whitespace_stripping_when_command_is_a_blank_line() {
2335 let (document, diagnostics) = Document::parse(
2336 r#"
2337version 1.2
2338
2339task test {
2340 command <<<
2341
2342 >>>
2343}
2344 "#,
2345 );
2346
2347 assert!(diagnostics.is_empty());
2348 let ast = document.ast();
2349 let ast = ast.as_v1().expect("should be a V1 AST");
2350 let tasks: Vec<_> = ast.tasks().collect();
2351 assert_eq!(tasks.len(), 1);
2352
2353 let command = tasks[0].command().expect("should have a command section");
2354
2355 let stripped = command.strip_whitespace().unwrap();
2356 assert_eq!(stripped.len(), 1);
2357 let text = match &stripped[0] {
2358 StrippedCommandPart::Text(text) => text,
2359 _ => panic!("expected text"),
2360 };
2361 assert_eq!(text, "");
2362 }
2363
2364 #[test]
2365 fn whitespace_stripping_when_command_is_a_blank_line_with_spaces() {
2366 let (document, diagnostics) = Document::parse(
2367 r#"
2368version 1.2
2369
2370task test {
2371 command <<<
2372
2373 >>>
2374}
2375 "#,
2376 );
2377
2378 assert!(diagnostics.is_empty());
2379 let ast = document.ast();
2380 let ast = ast.as_v1().expect("should be a V1 AST");
2381 let tasks: Vec<_> = ast.tasks().collect();
2382 assert_eq!(tasks.len(), 1);
2383
2384 let command = tasks[0].command().expect("should have a command section");
2385
2386 let stripped = command.strip_whitespace().unwrap();
2387 assert_eq!(stripped.len(), 1);
2388 let text = match &stripped[0] {
2389 StrippedCommandPart::Text(text) => text,
2390 _ => panic!("expected text"),
2391 };
2392 assert_eq!(text, " ");
2393 }
2394
2395 #[test]
2396 fn whitespace_stripping_with_mixed_indentation() {
2397 let (document, diagnostics) = Document::parse(
2398 r#"
2399version 1.2
2400
2401task test {
2402 command <<<
2403 echo "hello"
2404 echo "world"
2405 echo \
2406 "goodbye"
2407 >>>
2408 }"#,
2409 );
2410
2411 assert!(diagnostics.is_empty());
2412 let ast = document.ast();
2413 let ast = ast.as_v1().expect("should be a V1 AST");
2414 let tasks: Vec<_> = ast.tasks().collect();
2415 assert_eq!(tasks.len(), 1);
2416
2417 let command = tasks[0].command().expect("should have a command section");
2418
2419 let stripped = command.strip_whitespace();
2420 assert!(stripped.is_none());
2421 }
2422
2423 #[test]
2424 fn whitespace_stripping_with_funky_indentation() {
2425 let (document, diagnostics) = Document::parse(
2426 r#"
2427version 1.2
2428
2429task test {
2430 command <<<
2431 echo "hello"
2432 echo "world"
2433 echo \
2434 "goodbye"
2435 >>>
2436 }"#,
2437 );
2438
2439 assert!(diagnostics.is_empty());
2440 let ast = document.ast();
2441 let ast = ast.as_v1().expect("should be a V1 AST");
2442 let tasks: Vec<_> = ast.tasks().collect();
2443 assert_eq!(tasks.len(), 1);
2444
2445 let command = tasks[0].command().expect("should have a command section");
2446
2447 let stripped = command.strip_whitespace().unwrap();
2448 assert_eq!(stripped.len(), 1);
2449 let text = match &stripped[0] {
2450 StrippedCommandPart::Text(text) => text,
2451 _ => panic!("expected text"),
2452 };
2453 assert_eq!(
2454 text,
2455 "echo \"hello\"\n echo \"world\"\necho \\\n \"goodbye\""
2456 );
2457 }
2458
2459 #[test]
2461 fn whitespace_stripping_with_content_on_first_line() {
2462 let (document, diagnostics) = Document::parse(
2463 r#"
2464version 1.2
2465
2466task test {
2467 command <<< weird stuff $firstlinelint
2468 # other weird whitespace
2469 somecommand.py $line120 ~{placeholder}
2470 >>>
2471 }"#,
2472 );
2473
2474 assert!(diagnostics.is_empty());
2475 let ast = document.ast();
2476 let ast = ast.as_v1().expect("should be a V1 AST");
2477 let tasks: Vec<_> = ast.tasks().collect();
2478 assert_eq!(tasks.len(), 1);
2479
2480 let command = tasks[0].command().expect("should have a command section");
2481
2482 let stripped = command.strip_whitespace().unwrap();
2483 assert_eq!(stripped.len(), 3);
2484 let text = match &stripped[0] {
2485 StrippedCommandPart::Text(text) => text,
2486 _ => panic!("expected text"),
2487 };
2488 assert_eq!(
2489 text,
2490 "weird stuff $firstlinelint\n # other weird whitespace\nsomecommand.py $line120 "
2491 );
2492
2493 let _placeholder = match &stripped[1] {
2494 StrippedCommandPart::Placeholder(p) => p,
2495 _ => panic!("expected placeholder"),
2496 };
2497 let text = match &stripped[2] {
2500 StrippedCommandPart::Text(text) => text,
2501 _ => panic!("expected text"),
2502 };
2503 assert_eq!(text, "");
2504 }
2505
2506 #[test]
2507 fn whitespace_stripping_on_windows() {
2508 let (document, diagnostics) = Document::parse(
2509 "version 1.2\r\ntask test {\r\n command <<<\r\n echo \"hello\"\r\n \
2510 >>>\r\n}\r\n",
2511 );
2512
2513 assert!(diagnostics.is_empty());
2514 let ast = document.ast();
2515 let ast = ast.as_v1().expect("should be a V1 AST");
2516 let tasks: Vec<_> = ast.tasks().collect();
2517 assert_eq!(tasks.len(), 1);
2518
2519 let command = tasks[0].command().expect("should have a command section");
2520 let stripped = command.strip_whitespace().unwrap();
2521 assert_eq!(stripped.len(), 1);
2522 let text = match &stripped[0] {
2523 StrippedCommandPart::Text(text) => text,
2524 _ => panic!("expected text"),
2525 };
2526 assert_eq!(text, "echo \"hello\"");
2527 }
2528}