1use rowan::NodeOrToken;
4use wdl_grammar::SyntaxTokenExt;
5
6use super::BoundDecl;
7use super::Decl;
8use super::Expr;
9use super::LiteralBoolean;
10use super::LiteralFloat;
11use super::LiteralInteger;
12use super::LiteralString;
13use super::OpenHeredoc;
14use super::Placeholder;
15use super::StructDefinition;
16use super::TaskKeyword;
17use super::WorkflowDefinition;
18use crate::AstNode;
19use crate::AstToken;
20use crate::Comment;
21use crate::Documented;
22use crate::Ident;
23use crate::SyntaxKind;
24use crate::SyntaxNode;
25use crate::SyntaxToken;
26use crate::TreeNode;
27use crate::TreeToken;
28use crate::v1::CommandKeyword;
29use crate::v1::MetaKeyword;
30use crate::v1::ParameterMetaKeyword;
31use crate::v1::RequirementsKeyword;
32
33pub mod common;
34pub mod requirements;
35pub mod runtime;
36
37pub const TASK_FIELDS: &[(&str, &str)] = &[
40 (TASK_FIELD_NAME, "The task name."),
41 (
42 TASK_FIELD_ID,
43 "A String with the unique ID of the task. The execution engine may choose the format for \
44 this ID, but it is suggested to include at least the following information:\nThe task \
45 name\nThe task alias, if it differs from the task name\nThe index of the task instance, \
46 if it is within a scatter statement",
47 ),
48 (
49 TASK_FIELD_CONTAINER,
50 "The URI String of the container in which the task is executing, or None if the task is \
51 being executed in the host environment.",
52 ),
53 (
54 TASK_FIELD_CPU,
55 "The allocated number of cpus as a Float. Must be greater than 0.",
56 ),
57 (
58 TASK_FIELD_MEMORY,
59 "The allocated memory in bytes as an Int. Must be greater than 0.",
60 ),
61 (
62 TASK_FIELD_GPU,
63 "An Array[String] with one specification per allocated GPU. The specification is \
64 execution engine-specific. If no GPUs were allocated, then the value must be an empty \
65 array.",
66 ),
67 (
68 TASK_FIELD_FPGA,
69 "An Array[String] with one specification per allocated FPGA. The specification is \
70 execution engine-specific. If no FPGAs were allocated, then the value must be an empty \
71 array.",
72 ),
73 (
74 TASK_FIELD_DISKS,
75 "A Map[String, Int] with one entry for each disk mount point. The key is the mount point \
76 and the value is the initial amount of disk space allocated, in bytes. The execution \
77 engine must, at a minimum, provide one entry for each disk mount point requested, but \
78 may provide more. The amount of disk space available for a given mount point may \
79 increase during the lifetime of the task (e.g., autoscaling volumes provided by some \
80 cloud services).",
81 ),
82 (
83 TASK_FIELD_ATTEMPT,
84 "The current task attempt. The value must be 0 the first time the task is executed, and \
85 incremented by 1 each time the task is retried (if any).",
86 ),
87 (
88 TASK_FIELD_PREVIOUS,
89 "An Object containing the resource requirements from the previous task attempt. Available \
90 in requirements, hints, runtime, output, and command sections. All constituent members \
91 are optional and `None` on the first attempt.",
92 ),
93 (
94 TASK_FIELD_END_TIME,
95 "An Int? whose value is the time by which the task must be completed, as a Unix time \
96 stamp. A value of 0 means that the execution engine does not impose a time limit. A \
97 value of None means that the execution engine cannot determine whether the runtime of \
98 the task is limited. A positive value is a guarantee that the task will be preempted at \
99 the specified time, but is not a guarantee that the task won't be preempted earlier.",
100 ),
101 (
102 TASK_FIELD_RETURN_CODE,
103 "An Int? whose value is initially None and is set to the value of the command's return \
104 code. The value is only guaranteed to be defined in the output section.",
105 ),
106 (
107 TASK_FIELD_META,
108 "An Object containing a copy of the task's meta section, or the empty Object if there is \
109 no meta section or if it is empty.",
110 ),
111 (
112 TASK_FIELD_PARAMETER_META,
113 "An Object containing a copy of the task's parameter_meta section, or the empty Object if \
114 there is no parameter_meta section or if it is empty.",
115 ),
116 (
117 TASK_FIELD_EXT,
118 "An Object containing execution engine-specific attributes, or the empty Object if there \
119 aren't any. Members of ext should be considered optional. It is recommended to only \
120 access a member of ext using string interpolation to avoid an error if it is not defined.",
121 ),
122];
123
124pub const RUNTIME_KEYS: &[(&str, &str)] = &[
126 (
127 TASK_REQUIREMENT_CONTAINER,
128 "Specifies the container image (e.g., Docker, Singularity) to use for the task.",
129 ),
130 (
131 TASK_REQUIREMENT_CPU,
132 "The number of CPU cores required for the task.",
133 ),
134 (
135 TASK_REQUIREMENT_MEMORY,
136 "The amount of memory required, specified as a string with units (e.g., '2 GiB').",
137 ),
138 (
139 TASK_REQUIREMENT_DISKS,
140 "Specifies the disk requirements for the task.",
141 ),
142 (TASK_REQUIREMENT_GPU, "Specifies GPU requirements."),
143];
144
145pub const REQUIREMENTS_KEY: &[(&str, &str)] = &[
147 (
148 TASK_REQUIREMENT_CONTAINER,
149 "Specifies a list of allowed container images. Use `*` to allow any POSIX environment.",
150 ),
151 (
152 TASK_REQUIREMENT_CPU,
153 "The minimum number of CPU cores required.",
154 ),
155 (
156 TASK_REQUIREMENT_MEMORY,
157 "The minimum amount of memory required.",
158 ),
159 (TASK_REQUIREMENT_GPU, "The minimum GPU requirements."),
160 (TASK_REQUIREMENT_FPGA, "The minimum FPGA requirements."),
161 (TASK_REQUIREMENT_DISKS, "The minimum disk requirements."),
162 (
163 TASK_REQUIREMENT_MAX_RETRIES,
164 "The maximum number of times the task can be retried.",
165 ),
166 (
167 TASK_REQUIREMENT_RETURN_CODES,
168 "A list of acceptable return codes from the command.",
169 ),
170];
171
172pub const TASK_HINT_KEYS: &[(&str, &str)] = &[
174 (
175 TASK_HINT_DISKS,
176 "A hint to the execution engine to mount disks with specific attributes. The value of \
177 this hint can be a String with a specification that applies to all mount points, or a \
178 Map with the key being the mount point and the value being a String with the \
179 specification for that mount point.",
180 ),
181 (
182 TASK_HINT_GPU,
183 "A hint to the execution engine to provision hardware accelerators with specific \
184 attributes. Accelerator specifications are left intentionally vague as they are \
185 primarily intended to be used in the context of a specific compute environment.",
186 ),
187 (
188 TASK_HINT_FPGA,
189 "A hint to the execution engine to provision hardware accelerators with specific \
190 attributes. Accelerator specifications are left intentionally vague as they are \
191 primarily intended to be used in the context of a specific compute environment.",
192 ),
193 (
194 TASK_HINT_INPUTS,
195 "Provides input-specific hints. Each key must refer to a parameter defined in the task's \
196 input section. A key may also used dotted notation to refer to a specific member of a \
197 struct input.",
198 ),
199 (
200 TASK_HINT_LOCALIZATION_OPTIONAL,
201 "A hint to the execution engine about whether the File inputs for this task need to be \
202 localized prior to executing the task. The value of this hint is a Boolean for which \
203 true indicates that the contents of the File inputs may be streamed on demand.",
204 ),
205 (
206 TASK_HINT_MAX_CPU,
207 "A hint to the execution engine that the task expects to use no more than the specified \
208 number of CPUs. The value of this hint has the same specification as requirements.cpu.",
209 ),
210 (
211 TASK_HINT_MAX_MEMORY,
212 "A hint to the execution engine that the task expects to use no more than the specified \
213 amount of memory. The value of this hint has the same specification as \
214 requirements.memory.",
215 ),
216 (
217 TASK_HINT_OUTPUTS,
218 "Provides output-specific hints. Each key must refer to a parameter defined in the task's \
219 output section. A key may also use dotted notation to refer to a specific member of a \
220 struct output.",
221 ),
222 (
223 TASK_HINT_SHORT_TASK,
224 "A hint to the execution engine about the expected duration of this task. The value of \
225 this hint is a Boolean for which true indicates that that this task is not expected to \
226 take long to execute, which the execution engine can interpret as permission to optimize \
227 the execution of the task.",
228 ),
229 (
230 TASK_HINT_CACHEABLE,
231 "A hint to the execution engine that the task's execution result is cacheable. The value \
232 of this hint is a Boolean for which true indicates that the execution result is \
233 cacheable and false indicates it is not. The default value of the hint depends on the \
234 engine's configuration.",
235 ),
236];
237
238pub const TASK_FIELD_NAME: &str = "name";
240pub const TASK_FIELD_ID: &str = "id";
242pub const TASK_FIELD_CONTAINER: &str = "container";
244pub const TASK_FIELD_CPU: &str = "cpu";
246pub const TASK_FIELD_MEMORY: &str = "memory";
248pub const TASK_FIELD_ATTEMPT: &str = "attempt";
250pub const TASK_FIELD_PREVIOUS: &str = "previous";
252pub const TASK_FIELD_GPU: &str = "gpu";
254pub const TASK_FIELD_FPGA: &str = "fpga";
256pub const TASK_FIELD_DISKS: &str = "disks";
258pub const TASK_FIELD_END_TIME: &str = "end_time";
260pub const TASK_FIELD_RETURN_CODE: &str = "return_code";
262pub const TASK_FIELD_META: &str = "meta";
264pub const TASK_FIELD_PARAMETER_META: &str = "parameter_meta";
266pub const TASK_FIELD_EXT: &str = "ext";
268pub const TASK_FIELD_MAX_RETRIES: &str = "max_retries";
270
271pub const TASK_REQUIREMENT_CONTAINER: &str = "container";
273pub const TASK_REQUIREMENT_CONTAINER_ALIAS: &str = "docker";
275pub const TASK_REQUIREMENT_CPU: &str = "cpu";
277pub const TASK_REQUIREMENT_DISKS: &str = "disks";
279pub const TASK_REQUIREMENT_GPU: &str = "gpu";
281pub const TASK_REQUIREMENT_FPGA: &str = "fpga";
283pub const TASK_REQUIREMENT_MAX_RETRIES: &str = "max_retries";
285pub const TASK_REQUIREMENT_MAX_RETRIES_ALIAS: &str = "maxRetries";
287pub const TASK_REQUIREMENT_MEMORY: &str = "memory";
289pub const TASK_REQUIREMENT_RETURN_CODES: &str = "return_codes";
291pub const TASK_REQUIREMENT_RETURN_CODES_ALIAS: &str = "returnCodes";
293
294pub const TASK_HINT_DISKS: &str = "disks";
296pub const TASK_HINT_GPU: &str = "gpu";
298pub const TASK_HINT_FPGA: &str = "fpga";
300pub const TASK_HINT_INPUTS: &str = "inputs";
302pub const TASK_HINT_LOCALIZATION_OPTIONAL: &str = "localization_optional";
304pub const TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS: &str = "localizationOptional";
307pub const TASK_HINT_MAX_CPU: &str = "max_cpu";
309pub const TASK_HINT_MAX_CPU_ALIAS: &str = "maxCpu";
311pub const TASK_HINT_MAX_MEMORY: &str = "max_memory";
313pub const TASK_HINT_MAX_MEMORY_ALIAS: &str = "maxMemory";
315pub const TASK_HINT_OUTPUTS: &str = "outputs";
317pub const TASK_HINT_SHORT_TASK: &str = "short_task";
319pub const TASK_HINT_SHORT_TASK_ALIAS: &str = "shortTask";
321pub const TASK_HINT_CACHEABLE: &str = "cacheable";
323
324fn unescape_command_text(s: &str, heredoc: bool, buffer: &mut String) {
326 let mut chars = s.chars().peekable();
327 while let Some(c) = chars.next() {
328 match c {
329 '\\' => match chars.peek() {
330 Some('\\') | Some('~') => {
331 buffer.push(chars.next().unwrap());
332 }
333 Some('>') if heredoc => {
334 buffer.push(chars.next().unwrap());
335 }
336 Some('$') | Some('}') if !heredoc => {
337 buffer.push(chars.next().unwrap());
338 }
339 _ => {
340 buffer.push('\\');
341 }
342 },
343 _ => {
344 buffer.push(c);
345 }
346 }
347 }
348}
349
350#[derive(Clone, Debug, PartialEq, Eq)]
352pub struct TaskDefinition<N: TreeNode = SyntaxNode>(N);
353
354impl<N: TreeNode> TaskDefinition<N> {
355 pub fn name(&self) -> Ident<N::Token> {
357 self.token().expect("task should have a name")
358 }
359
360 pub fn keyword(&self) -> TaskKeyword<N::Token> {
362 self.token().expect("task should have a keyword")
363 }
364
365 pub fn items(&self) -> impl Iterator<Item = TaskItem<N>> + use<'_, N> {
367 TaskItem::children(&self.0)
368 }
369
370 pub fn input(&self) -> Option<InputSection<N>> {
372 self.child()
373 }
374
375 pub fn output(&self) -> Option<OutputSection<N>> {
377 self.child()
378 }
379
380 pub fn command(&self) -> Option<CommandSection<N>> {
382 self.child()
383 }
384
385 pub fn requirements(&self) -> Option<RequirementsSection<N>> {
387 self.child()
388 }
389
390 pub fn hints(&self) -> Option<TaskHintsSection<N>> {
392 self.child()
393 }
394
395 pub fn runtime(&self) -> Option<RuntimeSection<N>> {
397 self.child()
398 }
399
400 pub fn metadata(&self) -> Option<MetadataSection<N>> {
402 self.child()
403 }
404
405 pub fn parameter_metadata(&self) -> Option<ParameterMetadataSection<N>> {
407 self.child()
408 }
409
410 pub fn declarations(&self) -> impl Iterator<Item = BoundDecl<N>> + use<'_, N> {
412 self.children()
413 }
414}
415
416impl<N: TreeNode> AstNode<N> for TaskDefinition<N> {
417 fn can_cast(kind: SyntaxKind) -> bool {
418 kind == SyntaxKind::TaskDefinitionNode
419 }
420
421 fn cast(inner: N) -> Option<Self> {
422 match inner.kind() {
423 SyntaxKind::TaskDefinitionNode => Some(Self(inner)),
424 _ => None,
425 }
426 }
427
428 fn inner(&self) -> &N {
429 &self.0
430 }
431}
432
433impl Documented<SyntaxNode> for TaskDefinition<SyntaxNode> {
434 fn doc_comments(&self) -> Option<Vec<Comment<<SyntaxNode as TreeNode>::Token>>> {
435 Some(crate::doc_comments::<SyntaxNode>(self.keyword().inner().preceding_trivia()).collect())
436 }
437}
438
439#[derive(Clone, Debug, PartialEq, Eq)]
441pub enum TaskItem<N: TreeNode = SyntaxNode> {
442 Input(InputSection<N>),
444 Output(OutputSection<N>),
446 Command(CommandSection<N>),
448 Requirements(RequirementsSection<N>),
450 Hints(TaskHintsSection<N>),
452 Runtime(RuntimeSection<N>),
454 Metadata(MetadataSection<N>),
456 ParameterMetadata(ParameterMetadataSection<N>),
458 Declaration(BoundDecl<N>),
460}
461
462impl<N: TreeNode> TaskItem<N> {
463 pub fn can_cast(kind: SyntaxKind) -> bool {
466 matches!(
467 kind,
468 SyntaxKind::InputSectionNode
469 | SyntaxKind::OutputSectionNode
470 | SyntaxKind::CommandSectionNode
471 | SyntaxKind::RequirementsSectionNode
472 | SyntaxKind::TaskHintsSectionNode
473 | SyntaxKind::RuntimeSectionNode
474 | SyntaxKind::MetadataSectionNode
475 | SyntaxKind::ParameterMetadataSectionNode
476 | SyntaxKind::BoundDeclNode
477 )
478 }
479
480 pub fn cast(inner: N) -> Option<Self> {
484 match inner.kind() {
485 SyntaxKind::InputSectionNode => Some(Self::Input(
486 InputSection::cast(inner).expect("input section to cast"),
487 )),
488 SyntaxKind::OutputSectionNode => Some(Self::Output(
489 OutputSection::cast(inner).expect("output section to cast"),
490 )),
491 SyntaxKind::CommandSectionNode => Some(Self::Command(
492 CommandSection::cast(inner).expect("command section to cast"),
493 )),
494 SyntaxKind::RequirementsSectionNode => Some(Self::Requirements(
495 RequirementsSection::cast(inner).expect("requirements section to cast"),
496 )),
497 SyntaxKind::RuntimeSectionNode => Some(Self::Runtime(
498 RuntimeSection::cast(inner).expect("runtime section to cast"),
499 )),
500 SyntaxKind::MetadataSectionNode => Some(Self::Metadata(
501 MetadataSection::cast(inner).expect("metadata section to cast"),
502 )),
503 SyntaxKind::ParameterMetadataSectionNode => Some(Self::ParameterMetadata(
504 ParameterMetadataSection::cast(inner).expect("parameter metadata section to cast"),
505 )),
506 SyntaxKind::TaskHintsSectionNode => Some(Self::Hints(
507 TaskHintsSection::cast(inner).expect("task hints section to cast"),
508 )),
509 SyntaxKind::BoundDeclNode => Some(Self::Declaration(
510 BoundDecl::cast(inner).expect("bound decl to cast"),
511 )),
512 _ => None,
513 }
514 }
515
516 pub fn inner(&self) -> &N {
518 match self {
519 Self::Input(element) => element.inner(),
520 Self::Output(element) => element.inner(),
521 Self::Command(element) => element.inner(),
522 Self::Requirements(element) => element.inner(),
523 Self::Hints(element) => element.inner(),
524 Self::Runtime(element) => element.inner(),
525 Self::Metadata(element) => element.inner(),
526 Self::ParameterMetadata(element) => element.inner(),
527 Self::Declaration(element) => element.inner(),
528 }
529 }
530
531 pub fn as_input_section(&self) -> Option<&InputSection<N>> {
537 match self {
538 Self::Input(s) => Some(s),
539 _ => None,
540 }
541 }
542
543 pub fn into_input_section(self) -> Option<InputSection<N>> {
549 match self {
550 Self::Input(s) => Some(s),
551 _ => None,
552 }
553 }
554
555 pub fn as_output_section(&self) -> Option<&OutputSection<N>> {
561 match self {
562 Self::Output(s) => Some(s),
563 _ => None,
564 }
565 }
566
567 pub fn into_output_section(self) -> Option<OutputSection<N>> {
573 match self {
574 Self::Output(s) => Some(s),
575 _ => None,
576 }
577 }
578
579 pub fn as_command_section(&self) -> Option<&CommandSection<N>> {
585 match self {
586 Self::Command(s) => Some(s),
587 _ => None,
588 }
589 }
590
591 pub fn into_command_section(self) -> Option<CommandSection<N>> {
597 match self {
598 Self::Command(s) => Some(s),
599 _ => None,
600 }
601 }
602
603 pub fn as_requirements_section(&self) -> Option<&RequirementsSection<N>> {
609 match self {
610 Self::Requirements(s) => Some(s),
611 _ => None,
612 }
613 }
614
615 pub fn into_requirements_section(self) -> Option<RequirementsSection<N>> {
622 match self {
623 Self::Requirements(s) => Some(s),
624 _ => None,
625 }
626 }
627
628 pub fn as_hints_section(&self) -> Option<&TaskHintsSection<N>> {
634 match self {
635 Self::Hints(s) => Some(s),
636 _ => None,
637 }
638 }
639
640 pub fn into_hints_section(self) -> Option<TaskHintsSection<N>> {
646 match self {
647 Self::Hints(s) => Some(s),
648 _ => None,
649 }
650 }
651
652 pub fn as_runtime_section(&self) -> Option<&RuntimeSection<N>> {
658 match self {
659 Self::Runtime(s) => Some(s),
660 _ => None,
661 }
662 }
663
664 pub fn into_runtime_section(self) -> Option<RuntimeSection<N>> {
670 match self {
671 Self::Runtime(s) => Some(s),
672 _ => None,
673 }
674 }
675
676 pub fn as_metadata_section(&self) -> Option<&MetadataSection<N>> {
682 match self {
683 Self::Metadata(s) => Some(s),
684 _ => None,
685 }
686 }
687
688 pub fn into_metadata_section(self) -> Option<MetadataSection<N>> {
694 match self {
695 Self::Metadata(s) => Some(s),
696 _ => None,
697 }
698 }
699
700 pub fn as_parameter_metadata_section(&self) -> Option<&ParameterMetadataSection<N>> {
707 match self {
708 Self::ParameterMetadata(s) => Some(s),
709 _ => None,
710 }
711 }
712
713 pub fn into_parameter_metadata_section(self) -> Option<ParameterMetadataSection<N>> {
720 match self {
721 Self::ParameterMetadata(s) => Some(s),
722 _ => None,
723 }
724 }
725
726 pub fn as_declaration(&self) -> Option<&BoundDecl<N>> {
732 match self {
733 Self::Declaration(d) => Some(d),
734 _ => None,
735 }
736 }
737
738 pub fn into_declaration(self) -> Option<BoundDecl<N>> {
744 match self {
745 Self::Declaration(d) => Some(d),
746 _ => None,
747 }
748 }
749
750 pub fn child(node: &N) -> Option<Self> {
752 node.children().find_map(Self::cast)
753 }
754
755 pub fn children(node: &N) -> impl Iterator<Item = Self> + use<'_, N> {
757 node.children().filter_map(Self::cast)
758 }
759}
760
761#[derive(Clone, Debug, PartialEq, Eq)]
763pub enum SectionParent<N: TreeNode = SyntaxNode> {
764 Task(TaskDefinition<N>),
766 Workflow(WorkflowDefinition<N>),
768 Struct(StructDefinition<N>),
770}
771
772impl<N: TreeNode> SectionParent<N> {
773 pub fn can_cast(kind: SyntaxKind) -> bool {
776 matches!(
777 kind,
778 SyntaxKind::TaskDefinitionNode
779 | SyntaxKind::WorkflowDefinitionNode
780 | SyntaxKind::StructDefinitionNode
781 )
782 }
783
784 pub fn cast(inner: N) -> Option<Self> {
788 match inner.kind() {
789 SyntaxKind::TaskDefinitionNode => Some(Self::Task(
790 TaskDefinition::cast(inner).expect("task definition to cast"),
791 )),
792 SyntaxKind::WorkflowDefinitionNode => Some(Self::Workflow(
793 WorkflowDefinition::cast(inner).expect("workflow definition to cast"),
794 )),
795 SyntaxKind::StructDefinitionNode => Some(Self::Struct(
796 StructDefinition::cast(inner).expect("struct definition to cast"),
797 )),
798 _ => None,
799 }
800 }
801
802 pub fn inner(&self) -> &N {
804 match self {
805 Self::Task(element) => element.inner(),
806 Self::Workflow(element) => element.inner(),
807 Self::Struct(element) => element.inner(),
808 }
809 }
810
811 pub fn name(&self) -> Ident<N::Token> {
813 match self {
814 Self::Task(t) => t.name(),
815 Self::Workflow(w) => w.name(),
816 Self::Struct(s) => s.name(),
817 }
818 }
819
820 pub fn as_task(&self) -> Option<&TaskDefinition<N>> {
826 match self {
827 Self::Task(task) => Some(task),
828 _ => None,
829 }
830 }
831
832 pub fn into_task(self) -> Option<TaskDefinition<N>> {
838 match self {
839 Self::Task(task) => Some(task),
840 _ => None,
841 }
842 }
843
844 pub fn unwrap_task(self) -> TaskDefinition<N> {
850 match self {
851 Self::Task(task) => task,
852 _ => panic!("not a task definition"),
853 }
854 }
855
856 pub fn as_workflow(&self) -> Option<&WorkflowDefinition<N>> {
862 match self {
863 Self::Workflow(workflow) => Some(workflow),
864 _ => None,
865 }
866 }
867
868 pub fn into_workflow(self) -> Option<WorkflowDefinition<N>> {
874 match self {
875 Self::Workflow(workflow) => Some(workflow),
876 _ => None,
877 }
878 }
879
880 pub fn unwrap_workflow(self) -> WorkflowDefinition<N> {
886 match self {
887 Self::Workflow(workflow) => workflow,
888 _ => panic!("not a workflow definition"),
889 }
890 }
891
892 pub fn as_struct(&self) -> Option<&StructDefinition<N>> {
898 match self {
899 Self::Struct(r#struct) => Some(r#struct),
900 _ => None,
901 }
902 }
903
904 pub fn into_struct(self) -> Option<StructDefinition<N>> {
910 match self {
911 Self::Struct(r#struct) => Some(r#struct),
912 _ => None,
913 }
914 }
915
916 pub fn unwrap_struct(self) -> StructDefinition<N> {
922 match self {
923 Self::Struct(def) => def,
924 _ => panic!("not a struct definition"),
925 }
926 }
927
928 pub fn child(node: &N) -> Option<Self> {
930 node.children().find_map(Self::cast)
931 }
932
933 pub fn children(node: &N) -> impl Iterator<Item = Self> + use<'_, N> {
935 node.children().filter_map(Self::cast)
936 }
937}
938
939#[derive(Clone, Debug, PartialEq, Eq)]
941pub struct InputSection<N: TreeNode = SyntaxNode>(N);
942
943impl<N: TreeNode> InputSection<N> {
944 pub fn declarations(&self) -> impl Iterator<Item = Decl<N>> + use<'_, N> {
946 Decl::children(&self.0)
947 }
948
949 pub fn parent(&self) -> SectionParent<N> {
951 SectionParent::cast(self.0.parent().expect("should have a parent"))
952 .expect("parent should cast")
953 }
954}
955
956impl<N: TreeNode> AstNode<N> for InputSection<N> {
957 fn can_cast(kind: SyntaxKind) -> bool {
958 kind == SyntaxKind::InputSectionNode
959 }
960
961 fn cast(inner: N) -> Option<Self> {
962 match inner.kind() {
963 SyntaxKind::InputSectionNode => Some(Self(inner)),
964 _ => None,
965 }
966 }
967
968 fn inner(&self) -> &N {
969 &self.0
970 }
971}
972
973#[derive(Clone, Debug, PartialEq, Eq)]
975pub struct OutputSection<N: TreeNode = SyntaxNode>(N);
976
977impl<N: TreeNode> OutputSection<N> {
978 pub fn declarations(&self) -> impl Iterator<Item = BoundDecl<N>> + use<'_, N> {
980 self.children()
981 }
982
983 pub fn parent(&self) -> SectionParent<N> {
985 SectionParent::cast(self.0.parent().expect("should have a parent"))
986 .expect("parent should cast")
987 }
988}
989
990impl<N: TreeNode> AstNode<N> for OutputSection<N> {
991 fn can_cast(kind: SyntaxKind) -> bool {
992 kind == SyntaxKind::OutputSectionNode
993 }
994
995 fn cast(inner: N) -> Option<Self> {
996 match inner.kind() {
997 SyntaxKind::OutputSectionNode => Some(Self(inner)),
998 _ => None,
999 }
1000 }
1001
1002 fn inner(&self) -> &N {
1003 &self.0
1004 }
1005}
1006
1007#[derive(Clone, Debug, PartialEq, Eq)]
1011pub enum StrippedCommandPart<N: TreeNode = SyntaxNode> {
1012 Text(String),
1014 Placeholder(Placeholder<N>),
1016}
1017
1018#[derive(Clone, Debug, PartialEq, Eq)]
1020pub struct CommandSection<N: TreeNode = SyntaxNode>(N);
1021
1022impl<N: TreeNode> CommandSection<N> {
1023 pub fn is_heredoc(&self) -> bool {
1025 self.token::<OpenHeredoc<N::Token>>().is_some()
1026 }
1027
1028 pub fn parts(&self) -> impl Iterator<Item = CommandPart<N>> + use<'_, N> {
1030 self.0.children_with_tokens().filter_map(CommandPart::cast)
1031 }
1032
1033 pub fn count_whitespace(&self) -> Option<usize> {
1037 let mut min_leading_spaces = usize::MAX;
1038 let mut min_leading_tabs = usize::MAX;
1039 let mut parsing_leading_whitespace = false; let mut leading_spaces = 0;
1042 let mut leading_tabs = 0;
1043 for part in self.parts() {
1044 match part {
1045 CommandPart::Text(text) => {
1046 for c in text.text().chars() {
1047 match c {
1048 ' ' if parsing_leading_whitespace => {
1049 leading_spaces += 1;
1050 }
1051 '\t' if parsing_leading_whitespace => {
1052 leading_tabs += 1;
1053 }
1054 '\n' => {
1055 parsing_leading_whitespace = true;
1056 leading_spaces = 0;
1057 leading_tabs = 0;
1058 }
1059 '\r' => {}
1060 _ => {
1061 if parsing_leading_whitespace {
1062 parsing_leading_whitespace = false;
1063 if leading_spaces == 0 && leading_tabs == 0 {
1064 min_leading_spaces = 0;
1065 min_leading_tabs = 0;
1066 continue;
1067 }
1068 if leading_spaces < min_leading_spaces && leading_spaces > 0 {
1069 min_leading_spaces = leading_spaces;
1070 }
1071 if leading_tabs < min_leading_tabs && leading_tabs > 0 {
1072 min_leading_tabs = leading_tabs;
1073 }
1074 }
1075 }
1076 }
1077 }
1078 }
1080 CommandPart::Placeholder(_) => {
1081 if parsing_leading_whitespace {
1082 parsing_leading_whitespace = false;
1083 if leading_spaces == 0 && leading_tabs == 0 {
1084 min_leading_spaces = 0;
1085 min_leading_tabs = 0;
1086 continue;
1087 }
1088 if leading_spaces < min_leading_spaces && leading_spaces > 0 {
1089 min_leading_spaces = leading_spaces;
1090 }
1091 if leading_tabs < min_leading_tabs && leading_tabs > 0 {
1092 min_leading_tabs = leading_tabs;
1093 }
1094 }
1095 }
1096 }
1097 }
1098
1099 if (min_leading_spaces == 0 && min_leading_tabs == 0)
1101 || (min_leading_spaces == usize::MAX && min_leading_tabs == usize::MAX)
1102 {
1103 return Some(0);
1104 }
1105
1106 if (min_leading_spaces > 0 && min_leading_spaces != usize::MAX)
1108 && (min_leading_tabs > 0 && min_leading_tabs != usize::MAX)
1109 {
1110 return None;
1111 }
1112
1113 let final_leading_whitespace = if min_leading_spaces < min_leading_tabs {
1116 min_leading_spaces
1117 } else {
1118 min_leading_tabs
1119 };
1120
1121 Some(final_leading_whitespace)
1122 }
1123
1124 pub fn strip_whitespace(&self) -> Option<Vec<StrippedCommandPart<N>>> {
1128 let mut result = Vec::new();
1129 let heredoc = self.is_heredoc();
1130 for part in self.parts() {
1131 match part {
1132 CommandPart::Text(text) => {
1133 let mut s = String::new();
1134 unescape_command_text(text.text(), heredoc, &mut s);
1135 result.push(StrippedCommandPart::Text(s));
1136 }
1137 CommandPart::Placeholder(p) => {
1138 result.push(StrippedCommandPart::Placeholder(p));
1139 }
1140 }
1141 }
1142
1143 let mut whole_first_line_trimmed = false;
1145 if let Some(StrippedCommandPart::Text(text)) = result.first_mut() {
1146 let end_of_first_line = text.find('\n').map(|p| p + 1).unwrap_or(text.len());
1147 let line = &text[..end_of_first_line];
1148 let len = line.len() - line.trim_start().len();
1149 whole_first_line_trimmed = len == line.len();
1150 text.replace_range(..len, "");
1151 }
1152
1153 if let Some(StrippedCommandPart::Text(text)) = result.last_mut() {
1155 if let Some(index) = text.rfind(|c| !matches!(c, ' ' | '\t')) {
1156 text.truncate(index + 1);
1157 } else {
1158 text.clear();
1159 }
1160
1161 if text.ends_with('\n') {
1162 text.pop();
1163 }
1164
1165 if text.ends_with('\r') {
1166 text.pop();
1167 }
1168 }
1169
1170 let num_stripped_chars = self.count_whitespace()?;
1172
1173 if num_stripped_chars == 0 {
1175 return Some(result);
1176 }
1177
1178 let mut strip_leading_whitespace = whole_first_line_trimmed;
1182 for part in &mut result {
1183 match part {
1184 StrippedCommandPart::Text(text) => {
1185 let mut offset = 0;
1186 while let Some(next) = text[offset..].find('\n') {
1187 let next = next + offset;
1188 if offset > 0 {
1189 strip_leading_whitespace = true;
1190 }
1191
1192 if !strip_leading_whitespace {
1193 offset = next + 1;
1194 continue;
1195 }
1196
1197 let line = &text[offset..next];
1198 let line = line.strip_suffix('\r').unwrap_or(line);
1199 let len = line.len().min(num_stripped_chars);
1200 text.replace_range(offset..offset + len, "");
1201 offset = next + 1 - len;
1202 }
1203
1204 if strip_leading_whitespace || offset > 0 {
1206 let line = &text[offset..];
1207 let line = line.strip_suffix('\r').unwrap_or(line);
1208 let len = line.len().min(num_stripped_chars);
1209 text.replace_range(offset..offset + len, "");
1210 }
1211 }
1212 StrippedCommandPart::Placeholder(_) => {
1213 strip_leading_whitespace = false;
1214 }
1215 }
1216 }
1217
1218 Some(result)
1219 }
1220
1221 pub fn parent(&self) -> SectionParent<N> {
1223 SectionParent::cast(self.0.parent().expect("should have a parent"))
1224 .expect("parent should cast")
1225 }
1226
1227 pub fn keyword(&self) -> CommandKeyword<N::Token> {
1229 self.token()
1230 .expect("CommandSection must have CommandKeyword")
1231 }
1232}
1233
1234impl<N: TreeNode> AstNode<N> for CommandSection<N> {
1235 fn can_cast(kind: SyntaxKind) -> bool {
1236 kind == SyntaxKind::CommandSectionNode
1237 }
1238
1239 fn cast(inner: N) -> Option<Self> {
1240 match inner.kind() {
1241 SyntaxKind::CommandSectionNode => Some(Self(inner)),
1242 _ => None,
1243 }
1244 }
1245
1246 fn inner(&self) -> &N {
1247 &self.0
1248 }
1249}
1250
1251#[derive(Clone, Debug, PartialEq, Eq)]
1253pub struct CommandText<T: TreeToken = SyntaxToken>(T);
1254
1255impl<T: TreeToken> CommandText<T> {
1256 pub fn unescape_to(&self, heredoc: bool, buffer: &mut String) {
1262 unescape_command_text(self.text(), heredoc, buffer);
1263 }
1264}
1265
1266impl<T: TreeToken> AstToken<T> for CommandText<T> {
1267 fn can_cast(kind: SyntaxKind) -> bool {
1268 kind == SyntaxKind::LiteralCommandText
1269 }
1270
1271 fn cast(inner: T) -> Option<Self> {
1272 match inner.kind() {
1273 SyntaxKind::LiteralCommandText => Some(Self(inner)),
1274 _ => None,
1275 }
1276 }
1277
1278 fn inner(&self) -> &T {
1279 &self.0
1280 }
1281}
1282
1283#[derive(Clone, Debug, PartialEq, Eq)]
1285pub enum CommandPart<N: TreeNode = SyntaxNode> {
1286 Text(CommandText<N::Token>),
1288 Placeholder(Placeholder<N>),
1290}
1291
1292impl<N: TreeNode> CommandPart<N> {
1293 pub fn unwrap_text(self) -> CommandText<N::Token> {
1299 match self {
1300 Self::Text(text) => text,
1301 _ => panic!("not string text"),
1302 }
1303 }
1304
1305 pub fn unwrap_placeholder(self) -> Placeholder<N> {
1311 match self {
1312 Self::Placeholder(p) => p,
1313 _ => panic!("not a placeholder"),
1314 }
1315 }
1316
1317 fn cast(element: NodeOrToken<N, N::Token>) -> Option<Self> {
1321 match element {
1322 NodeOrToken::Node(n) => Some(Self::Placeholder(Placeholder::cast(n)?)),
1323 NodeOrToken::Token(t) => Some(Self::Text(CommandText::cast(t)?)),
1324 }
1325 }
1326}
1327
1328#[derive(Clone, Debug, PartialEq, Eq)]
1330pub struct RequirementsSection<N: TreeNode = SyntaxNode>(N);
1331
1332impl<N: TreeNode> RequirementsSection<N> {
1333 pub fn items(&self) -> impl Iterator<Item = RequirementsItem<N>> + use<'_, N> {
1335 self.children()
1336 }
1337
1338 pub fn parent(&self) -> SectionParent<N> {
1340 SectionParent::cast(self.0.parent().expect("should have a parent"))
1341 .expect("parent should cast")
1342 }
1343
1344 pub fn container(&self) -> Option<requirements::item::Container<N>> {
1347 self.child()
1350 }
1351
1352 pub fn keyword(&self) -> RequirementsKeyword<N::Token> {
1354 self.token()
1355 .expect("RequirementsSection must have RequirementsKeyword")
1356 }
1357}
1358
1359impl<N: TreeNode> AstNode<N> for RequirementsSection<N> {
1360 fn can_cast(kind: SyntaxKind) -> bool {
1361 kind == SyntaxKind::RequirementsSectionNode
1362 }
1363
1364 fn cast(inner: N) -> Option<Self> {
1365 match inner.kind() {
1366 SyntaxKind::RequirementsSectionNode => Some(Self(inner)),
1367 _ => None,
1368 }
1369 }
1370
1371 fn inner(&self) -> &N {
1372 &self.0
1373 }
1374}
1375
1376#[derive(Clone, Debug, PartialEq, Eq)]
1378pub struct RequirementsItem<N: TreeNode = SyntaxNode>(N);
1379
1380impl<N: TreeNode> RequirementsItem<N> {
1381 pub fn name(&self) -> Ident<N::Token> {
1383 self.token().expect("expected an item name")
1384 }
1385
1386 pub fn expr(&self) -> Expr<N> {
1388 Expr::child(&self.0).expect("expected an item expression")
1389 }
1390
1391 pub fn into_container(self) -> Option<requirements::item::Container<N>> {
1394 requirements::item::Container::try_from(self).ok()
1395 }
1396}
1397
1398impl<N: TreeNode> AstNode<N> for RequirementsItem<N> {
1399 fn can_cast(kind: SyntaxKind) -> bool {
1400 kind == SyntaxKind::RequirementsItemNode
1401 }
1402
1403 fn cast(inner: N) -> Option<Self> {
1404 match inner.kind() {
1405 SyntaxKind::RequirementsItemNode => Some(Self(inner)),
1406 _ => None,
1407 }
1408 }
1409
1410 fn inner(&self) -> &N {
1411 &self.0
1412 }
1413}
1414
1415#[derive(Clone, Debug, PartialEq, Eq)]
1417pub struct TaskHintsSection<N: TreeNode = SyntaxNode>(N);
1418
1419impl<N: TreeNode> TaskHintsSection<N> {
1420 pub fn items(&self) -> impl Iterator<Item = TaskHintsItem<N>> + use<'_, N> {
1422 self.children()
1423 }
1424
1425 pub fn parent(&self) -> TaskDefinition<N> {
1427 TaskDefinition::cast(self.0.parent().expect("should have a parent"))
1428 .expect("parent should cast")
1429 }
1430}
1431
1432impl<N: TreeNode> AstNode<N> for TaskHintsSection<N> {
1433 fn can_cast(kind: SyntaxKind) -> bool {
1434 kind == SyntaxKind::TaskHintsSectionNode
1435 }
1436
1437 fn cast(inner: N) -> Option<Self> {
1438 match inner.kind() {
1439 SyntaxKind::TaskHintsSectionNode => Some(Self(inner)),
1440 _ => None,
1441 }
1442 }
1443
1444 fn inner(&self) -> &N {
1445 &self.0
1446 }
1447}
1448
1449#[derive(Clone, Debug, PartialEq, Eq)]
1451pub struct TaskHintsItem<N: TreeNode = SyntaxNode>(N);
1452
1453impl<N: TreeNode> TaskHintsItem<N> {
1454 pub fn name(&self) -> Ident<N::Token> {
1456 self.token().expect("expected an item name")
1457 }
1458
1459 pub fn expr(&self) -> Expr<N> {
1461 Expr::child(&self.0).expect("expected an item expression")
1462 }
1463}
1464
1465impl<N: TreeNode> AstNode<N> for TaskHintsItem<N> {
1466 fn can_cast(kind: SyntaxKind) -> bool {
1467 kind == SyntaxKind::TaskHintsItemNode
1468 }
1469
1470 fn cast(inner: N) -> Option<Self> {
1471 match inner.kind() {
1472 SyntaxKind::TaskHintsItemNode => Some(Self(inner)),
1473 _ => None,
1474 }
1475 }
1476
1477 fn inner(&self) -> &N {
1478 &self.0
1479 }
1480}
1481
1482#[derive(Clone, Debug, PartialEq, Eq)]
1484pub struct RuntimeSection<N: TreeNode = SyntaxNode>(N);
1485
1486impl<N: TreeNode> RuntimeSection<N> {
1487 pub fn items(&self) -> impl Iterator<Item = RuntimeItem<N>> + use<'_, N> {
1489 self.children()
1490 }
1491
1492 pub fn parent(&self) -> SectionParent<N> {
1494 SectionParent::cast(self.0.parent().expect("should have a parent"))
1495 .expect("parent should cast")
1496 }
1497
1498 pub fn container(&self) -> Option<runtime::item::Container<N>> {
1501 self.child()
1504 }
1505}
1506
1507impl<N: TreeNode> AstNode<N> for RuntimeSection<N> {
1508 fn can_cast(kind: SyntaxKind) -> bool {
1509 kind == SyntaxKind::RuntimeSectionNode
1510 }
1511
1512 fn cast(inner: N) -> Option<Self> {
1513 match inner.kind() {
1514 SyntaxKind::RuntimeSectionNode => Some(Self(inner)),
1515 _ => None,
1516 }
1517 }
1518
1519 fn inner(&self) -> &N {
1520 &self.0
1521 }
1522}
1523
1524#[derive(Clone, Debug, PartialEq, Eq)]
1526pub struct RuntimeItem<N: TreeNode = SyntaxNode>(N);
1527
1528impl<N: TreeNode> RuntimeItem<N> {
1529 pub fn name(&self) -> Ident<N::Token> {
1531 self.token().expect("expected an item name")
1532 }
1533
1534 pub fn expr(&self) -> Expr<N> {
1536 Expr::child(&self.0).expect("expected an item expression")
1537 }
1538
1539 pub fn into_container(self) -> Option<runtime::item::Container<N>> {
1542 runtime::item::Container::try_from(self).ok()
1543 }
1544}
1545
1546impl<N: TreeNode> AstNode<N> for RuntimeItem<N> {
1547 fn can_cast(kind: SyntaxKind) -> bool {
1548 kind == SyntaxKind::RuntimeItemNode
1549 }
1550
1551 fn cast(inner: N) -> Option<Self> {
1552 match inner.kind() {
1553 SyntaxKind::RuntimeItemNode => Some(Self(inner)),
1554 _ => None,
1555 }
1556 }
1557
1558 fn inner(&self) -> &N {
1559 &self.0
1560 }
1561}
1562
1563#[derive(Clone, Debug, PartialEq, Eq)]
1565pub struct MetadataSection<N: TreeNode = SyntaxNode>(N);
1566
1567impl<N: TreeNode> MetadataSection<N> {
1568 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1570 self.children()
1571 }
1572
1573 pub fn parent(&self) -> SectionParent<N> {
1575 SectionParent::cast(self.0.parent().expect("should have a parent"))
1576 .expect("parent should cast")
1577 }
1578
1579 pub fn keyword(&self) -> MetaKeyword<N::Token> {
1581 self.token().expect("MetadataSection must have MetaKeyword")
1582 }
1583}
1584
1585impl<N: TreeNode> AstNode<N> for MetadataSection<N> {
1586 fn can_cast(kind: SyntaxKind) -> bool {
1587 kind == SyntaxKind::MetadataSectionNode
1588 }
1589
1590 fn cast(inner: N) -> Option<Self> {
1591 match inner.kind() {
1592 SyntaxKind::MetadataSectionNode => Some(Self(inner)),
1593 _ => None,
1594 }
1595 }
1596
1597 fn inner(&self) -> &N {
1598 &self.0
1599 }
1600}
1601
1602#[derive(Clone, Debug, PartialEq, Eq)]
1604pub struct MetadataObjectItem<N: TreeNode = SyntaxNode>(N);
1605
1606impl<N: TreeNode> MetadataObjectItem<N> {
1607 pub fn name(&self) -> Ident<N::Token> {
1609 self.token().expect("expected a name")
1610 }
1611
1612 pub fn value(&self) -> MetadataValue<N> {
1614 self.child().expect("expected a value")
1615 }
1616}
1617
1618impl<N: TreeNode> AstNode<N> for MetadataObjectItem<N> {
1619 fn can_cast(kind: SyntaxKind) -> bool {
1620 kind == SyntaxKind::MetadataObjectItemNode
1621 }
1622
1623 fn cast(inner: N) -> Option<Self> {
1624 match inner.kind() {
1625 SyntaxKind::MetadataObjectItemNode => Some(Self(inner)),
1626 _ => None,
1627 }
1628 }
1629
1630 fn inner(&self) -> &N {
1631 &self.0
1632 }
1633}
1634
1635#[derive(Clone, Debug, PartialEq, Eq)]
1637pub enum MetadataValue<N: TreeNode = SyntaxNode> {
1638 Boolean(LiteralBoolean<N>),
1640 Integer(LiteralInteger<N>),
1642 Float(LiteralFloat<N>),
1644 String(LiteralString<N>),
1646 Null(LiteralNull<N>),
1648 Object(MetadataObject<N>),
1650 Array(MetadataArray<N>),
1652}
1653
1654impl<N: TreeNode> MetadataValue<N> {
1655 pub fn unwrap_boolean(self) -> LiteralBoolean<N> {
1661 match self {
1662 Self::Boolean(b) => b,
1663 _ => panic!("not a boolean"),
1664 }
1665 }
1666
1667 pub fn unwrap_integer(self) -> LiteralInteger<N> {
1673 match self {
1674 Self::Integer(i) => i,
1675 _ => panic!("not an integer"),
1676 }
1677 }
1678
1679 pub fn unwrap_float(self) -> LiteralFloat<N> {
1685 match self {
1686 Self::Float(f) => f,
1687 _ => panic!("not a float"),
1688 }
1689 }
1690
1691 pub fn unwrap_string(self) -> LiteralString<N> {
1697 match self {
1698 Self::String(s) => s,
1699 _ => panic!("not a string"),
1700 }
1701 }
1702
1703 pub fn unwrap_null(self) -> LiteralNull<N> {
1709 match self {
1710 Self::Null(n) => n,
1711 _ => panic!("not a null"),
1712 }
1713 }
1714
1715 pub fn unwrap_object(self) -> MetadataObject<N> {
1721 match self {
1722 Self::Object(o) => o,
1723 _ => panic!("not an object"),
1724 }
1725 }
1726
1727 pub fn unwrap_array(self) -> MetadataArray<N> {
1733 match self {
1734 Self::Array(a) => a,
1735 _ => panic!("not an array"),
1736 }
1737 }
1738}
1739
1740impl<N: TreeNode> AstNode<N> for MetadataValue<N> {
1741 fn can_cast(kind: SyntaxKind) -> bool {
1742 matches!(
1743 kind,
1744 SyntaxKind::LiteralBooleanNode
1745 | SyntaxKind::LiteralIntegerNode
1746 | SyntaxKind::LiteralFloatNode
1747 | SyntaxKind::LiteralStringNode
1748 | SyntaxKind::LiteralNullNode
1749 | SyntaxKind::MetadataObjectNode
1750 | SyntaxKind::MetadataArrayNode
1751 )
1752 }
1753
1754 fn cast(inner: N) -> Option<Self> {
1755 match inner.kind() {
1756 SyntaxKind::LiteralBooleanNode => Some(Self::Boolean(LiteralBoolean(inner))),
1757 SyntaxKind::LiteralIntegerNode => Some(Self::Integer(LiteralInteger(inner))),
1758 SyntaxKind::LiteralFloatNode => Some(Self::Float(LiteralFloat(inner))),
1759 SyntaxKind::LiteralStringNode => Some(Self::String(LiteralString(inner))),
1760 SyntaxKind::LiteralNullNode => Some(Self::Null(LiteralNull(inner))),
1761 SyntaxKind::MetadataObjectNode => Some(Self::Object(MetadataObject(inner))),
1762 SyntaxKind::MetadataArrayNode => Some(Self::Array(MetadataArray(inner))),
1763 _ => None,
1764 }
1765 }
1766
1767 fn inner(&self) -> &N {
1768 match self {
1769 Self::Boolean(b) => &b.0,
1770 Self::Integer(i) => &i.0,
1771 Self::Float(f) => &f.0,
1772 Self::String(s) => &s.0,
1773 Self::Null(n) => &n.0,
1774 Self::Object(o) => &o.0,
1775 Self::Array(a) => &a.0,
1776 }
1777 }
1778}
1779
1780#[derive(Clone, Debug, PartialEq, Eq)]
1782pub struct LiteralNull<N: TreeNode = SyntaxNode>(N);
1783
1784impl<N: TreeNode> AstNode<N> for LiteralNull<N> {
1785 fn can_cast(kind: SyntaxKind) -> bool {
1786 kind == SyntaxKind::LiteralNullNode
1787 }
1788
1789 fn cast(inner: N) -> Option<Self> {
1790 match inner.kind() {
1791 SyntaxKind::LiteralNullNode => Some(Self(inner)),
1792 _ => None,
1793 }
1794 }
1795
1796 fn inner(&self) -> &N {
1797 &self.0
1798 }
1799}
1800
1801#[derive(Clone, Debug, PartialEq, Eq)]
1803pub struct MetadataObject<N: TreeNode = SyntaxNode>(N);
1804
1805impl<N: TreeNode> MetadataObject<N> {
1806 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1808 self.children()
1809 }
1810}
1811
1812impl<N: TreeNode> AstNode<N> for MetadataObject<N> {
1813 fn can_cast(kind: SyntaxKind) -> bool {
1814 kind == SyntaxKind::MetadataObjectNode
1815 }
1816
1817 fn cast(inner: N) -> Option<Self> {
1818 match inner.kind() {
1819 SyntaxKind::MetadataObjectNode => Some(Self(inner)),
1820 _ => None,
1821 }
1822 }
1823
1824 fn inner(&self) -> &N {
1825 &self.0
1826 }
1827}
1828
1829#[derive(Clone, Debug, PartialEq, Eq)]
1831pub struct MetadataArray<N: TreeNode = SyntaxNode>(N);
1832
1833impl<N: TreeNode> MetadataArray<N> {
1834 pub fn elements(&self) -> impl Iterator<Item = MetadataValue<N>> + use<'_, N> {
1836 self.children()
1837 }
1838}
1839
1840impl<N: TreeNode> AstNode<N> for MetadataArray<N> {
1841 fn can_cast(kind: SyntaxKind) -> bool {
1842 kind == SyntaxKind::MetadataArrayNode
1843 }
1844
1845 fn cast(inner: N) -> Option<Self> {
1846 match inner.kind() {
1847 SyntaxKind::MetadataArrayNode => Some(Self(inner)),
1848 _ => None,
1849 }
1850 }
1851
1852 fn inner(&self) -> &N {
1853 &self.0
1854 }
1855}
1856
1857#[derive(Clone, Debug, PartialEq, Eq)]
1859pub struct ParameterMetadataSection<N: TreeNode = SyntaxNode>(N);
1860
1861impl<N: TreeNode> ParameterMetadataSection<N> {
1862 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1864 self.children()
1865 }
1866
1867 pub fn parent(&self) -> SectionParent<N> {
1869 SectionParent::cast(self.0.parent().expect("should have a parent"))
1870 .expect("parent should cast")
1871 }
1872
1873 pub fn keyword(&self) -> ParameterMetaKeyword<N::Token> {
1875 self.token()
1876 .expect("ParameterMetadataSection must have ParameterMetaKeyword")
1877 }
1878}
1879
1880impl<N: TreeNode> AstNode<N> for ParameterMetadataSection<N> {
1881 fn can_cast(kind: SyntaxKind) -> bool {
1882 kind == SyntaxKind::ParameterMetadataSectionNode
1883 }
1884
1885 fn cast(inner: N) -> Option<Self> {
1886 match inner.kind() {
1887 SyntaxKind::ParameterMetadataSectionNode => Some(Self(inner)),
1888 _ => None,
1889 }
1890 }
1891
1892 fn inner(&self) -> &N {
1893 &self.0
1894 }
1895}
1896
1897#[cfg(test)]
1898mod test {
1899 use pretty_assertions::assert_eq;
1900
1901 use super::*;
1902 use crate::Document;
1903
1904 #[test]
1905 fn tasks() {
1906 let (document, diagnostics) = Document::parse(
1907 r#"
1908version 1.2
1909
1910task test {
1911 input {
1912 String name
1913 }
1914
1915 output {
1916 String output = stdout()
1917 }
1918
1919 command <<<
1920 printf "hello, ~{name}!
1921 >>>
1922
1923 requirements {
1924 container: "baz/qux"
1925 }
1926
1927 hints {
1928 foo: "bar"
1929 }
1930
1931 runtime {
1932 container: "foo/bar"
1933 }
1934
1935 meta {
1936 description: "a test"
1937 foo: null
1938 }
1939
1940 parameter_meta {
1941 name: {
1942 help: "a name to greet"
1943 }
1944 }
1945
1946 String x = "private"
1947}
1948"#,
1949 );
1950
1951 assert!(diagnostics.is_empty());
1952 let ast = document.ast();
1953 let ast = ast.as_v1().expect("should be a V1 AST");
1954 let tasks: Vec<_> = ast.tasks().collect();
1955 assert_eq!(tasks.len(), 1);
1956 assert_eq!(tasks[0].name().text(), "test");
1957
1958 let input = tasks[0].input().expect("should have an input section");
1960 assert_eq!(input.parent().unwrap_task().name().text(), "test");
1961 let decls: Vec<_> = input.declarations().collect();
1962 assert_eq!(decls.len(), 1);
1963 assert_eq!(
1964 decls[0].clone().unwrap_unbound_decl().ty().to_string(),
1965 "String"
1966 );
1967 assert_eq!(decls[0].clone().unwrap_unbound_decl().name().text(), "name");
1968
1969 let output = tasks[0].output().expect("should have an output section");
1971 assert_eq!(output.parent().unwrap_task().name().text(), "test");
1972 let decls: Vec<_> = output.declarations().collect();
1973 assert_eq!(decls.len(), 1);
1974 assert_eq!(decls[0].ty().to_string(), "String");
1975 assert_eq!(decls[0].name().text(), "output");
1976 assert_eq!(decls[0].expr().unwrap_call().target().text(), "stdout");
1977
1978 let command = tasks[0].command().expect("should have a command section");
1980 assert_eq!(command.parent().name().text(), "test");
1981 assert!(command.is_heredoc());
1982 let parts: Vec<_> = command.parts().collect();
1983 assert_eq!(parts.len(), 3);
1984 assert_eq!(
1985 parts[0].clone().unwrap_text().text(),
1986 "\n printf \"hello, "
1987 );
1988 assert_eq!(
1989 parts[1]
1990 .clone()
1991 .unwrap_placeholder()
1992 .expr()
1993 .unwrap_name_ref()
1994 .name()
1995 .text(),
1996 "name"
1997 );
1998 assert_eq!(parts[2].clone().unwrap_text().text(), "!\n ");
1999
2000 let requirements = tasks[0]
2002 .requirements()
2003 .expect("should have a requirements section");
2004 assert_eq!(requirements.parent().name().text(), "test");
2005 let items: Vec<_> = requirements.items().collect();
2006 assert_eq!(items.len(), 1);
2007 assert_eq!(items[0].name().text(), TASK_REQUIREMENT_CONTAINER);
2008 assert_eq!(
2009 items[0]
2010 .expr()
2011 .unwrap_literal()
2012 .unwrap_string()
2013 .text()
2014 .unwrap()
2015 .text(),
2016 "baz/qux"
2017 );
2018
2019 let hints = tasks[0].hints().expect("should have a hints section");
2021 assert_eq!(hints.parent().name().text(), "test");
2022 let items: Vec<_> = hints.items().collect();
2023 assert_eq!(items.len(), 1);
2024 assert_eq!(items[0].name().text(), "foo");
2025 assert_eq!(
2026 items[0]
2027 .expr()
2028 .unwrap_literal()
2029 .unwrap_string()
2030 .text()
2031 .unwrap()
2032 .text(),
2033 "bar"
2034 );
2035
2036 let runtime = tasks[0].runtime().expect("should have a runtime section");
2038 assert_eq!(runtime.parent().name().text(), "test");
2039 let items: Vec<_> = runtime.items().collect();
2040 assert_eq!(items.len(), 1);
2041 assert_eq!(items[0].name().text(), TASK_REQUIREMENT_CONTAINER);
2042 assert_eq!(
2043 items[0]
2044 .expr()
2045 .unwrap_literal()
2046 .unwrap_string()
2047 .text()
2048 .unwrap()
2049 .text(),
2050 "foo/bar"
2051 );
2052
2053 let metadata = tasks[0].metadata().expect("should have a metadata section");
2055 assert_eq!(metadata.parent().unwrap_task().name().text(), "test");
2056 let items: Vec<_> = metadata.items().collect();
2057 assert_eq!(items.len(), 2);
2058 assert_eq!(items[0].name().text(), "description");
2059 assert_eq!(
2060 items[0].value().unwrap_string().text().unwrap().text(),
2061 "a test"
2062 );
2063
2064 assert_eq!(items[1].name().text(), "foo");
2066 items[1].value().unwrap_null();
2067
2068 let param_meta = tasks[0]
2070 .parameter_metadata()
2071 .expect("should have a parameter metadata section");
2072 assert_eq!(param_meta.parent().unwrap_task().name().text(), "test");
2073 let items: Vec<_> = param_meta.items().collect();
2074 assert_eq!(items.len(), 1);
2075 assert_eq!(items[0].name().text(), "name");
2076 let items: Vec<_> = items[0].value().unwrap_object().items().collect();
2077 assert_eq!(items.len(), 1);
2078 assert_eq!(items[0].name().text(), "help");
2079 assert_eq!(
2080 items[0].value().unwrap_string().text().unwrap().text(),
2081 "a name to greet"
2082 );
2083
2084 let decls: Vec<_> = tasks[0].declarations().collect();
2086 assert_eq!(decls.len(), 1);
2087
2088 assert_eq!(decls[0].ty().to_string(), "String");
2090 assert_eq!(decls[0].name().text(), "x");
2091 assert_eq!(
2092 decls[0]
2093 .expr()
2094 .unwrap_literal()
2095 .unwrap_string()
2096 .text()
2097 .unwrap()
2098 .text(),
2099 "private"
2100 );
2101 }
2102
2103 #[test]
2104 fn whitespace_stripping_without_interpolation() {
2105 let (document, diagnostics) = Document::parse(
2106 r#"
2107version 1.2
2108
2109task test {
2110 command <<<
2111 echo "hello"
2112 echo "world"
2113 echo \
2114 "goodbye"
2115 >>>
2116}
2117"#,
2118 );
2119
2120 assert!(diagnostics.is_empty());
2121 let ast = document.ast();
2122 let ast = ast.as_v1().expect("should be a V1 AST");
2123 let tasks: Vec<_> = ast.tasks().collect();
2124 assert_eq!(tasks.len(), 1);
2125
2126 let command = tasks[0].command().expect("should have a command section");
2127
2128 let stripped = command.strip_whitespace().unwrap();
2129
2130 assert_eq!(stripped.len(), 1);
2131 let text = match &stripped[0] {
2132 StrippedCommandPart::Text(text) => text,
2133 _ => panic!("expected text"),
2134 };
2135 assert_eq!(
2136 text,
2137 "echo \"hello\"\necho \"world\"\necho \\\n \"goodbye\""
2138 );
2139 }
2140
2141 #[test]
2142 fn whitespace_stripping_with_interpolation() {
2143 let (document, diagnostics) = Document::parse(
2144 r#"
2145version 1.2
2146
2147task test {
2148 input {
2149 String name
2150 Boolean flag
2151 }
2152
2153 command <<<
2154 echo "hello, ~{
2155if flag
2156then name
2157 else "Jerry"
2158 }!"
2159 >>>
2160}
2161 "#,
2162 );
2163
2164 assert!(diagnostics.is_empty());
2165 let ast = document.ast();
2166 let ast = ast.as_v1().expect("should be a V1 AST");
2167 let tasks: Vec<_> = ast.tasks().collect();
2168 assert_eq!(tasks.len(), 1);
2169
2170 let command = tasks[0].command().expect("should have a command section");
2171
2172 let stripped = command.strip_whitespace().unwrap();
2173 assert_eq!(stripped.len(), 3);
2174 let text = match &stripped[0] {
2175 StrippedCommandPart::Text(text) => text,
2176 _ => panic!("expected text"),
2177 };
2178 assert_eq!(text, "echo \"hello, ");
2179
2180 let _placeholder = match &stripped[1] {
2181 StrippedCommandPart::Placeholder(p) => p,
2182 _ => panic!("expected placeholder"),
2183 };
2184 let text = match &stripped[2] {
2187 StrippedCommandPart::Text(text) => text,
2188 _ => panic!("expected text"),
2189 };
2190 assert_eq!(text, "!\"");
2191 }
2192
2193 #[test]
2194 fn whitespace_stripping_when_interpolation_starts_line() {
2195 let (document, diagnostics) = Document::parse(
2196 r#"
2197version 1.2
2198
2199task test {
2200 input {
2201 Int placeholder
2202 }
2203
2204 command <<<
2205 # other weird whitspace
2206 ~{placeholder} "$trailing_pholder" ~{placeholder}
2207 ~{placeholder} somecommand.py "$leading_pholder"
2208 >>>
2209}
2210"#,
2211 );
2212
2213 assert!(diagnostics.is_empty());
2214 let ast = document.ast();
2215 let ast = ast.as_v1().expect("should be a V1 AST");
2216 let tasks: Vec<_> = ast.tasks().collect();
2217 assert_eq!(tasks.len(), 1);
2218
2219 let command = tasks[0].command().expect("should have a command section");
2220
2221 let stripped = command.strip_whitespace().unwrap();
2222 assert_eq!(stripped.len(), 7);
2223 let text = match &stripped[0] {
2224 StrippedCommandPart::Text(text) => text,
2225 _ => panic!("expected text"),
2226 };
2227 assert_eq!(text, " # other weird whitspace\n");
2228
2229 let _placeholder = match &stripped[1] {
2230 StrippedCommandPart::Placeholder(p) => p,
2231 _ => panic!("expected placeholder"),
2232 };
2233 let text = match &stripped[2] {
2236 StrippedCommandPart::Text(text) => text,
2237 _ => panic!("expected text"),
2238 };
2239 assert_eq!(text, " \"$trailing_pholder\" ");
2240
2241 let _placeholder = match &stripped[3] {
2242 StrippedCommandPart::Placeholder(p) => p,
2243 _ => panic!("expected placeholder"),
2244 };
2245 let text = match &stripped[4] {
2248 StrippedCommandPart::Text(text) => text,
2249 _ => panic!("expected text"),
2250 };
2251 assert_eq!(text, "\n");
2252
2253 let _placeholder = match &stripped[5] {
2254 StrippedCommandPart::Placeholder(p) => p,
2255 _ => panic!("expected placeholder"),
2256 };
2257 let text = match &stripped[6] {
2260 StrippedCommandPart::Text(text) => text,
2261 _ => panic!("expected text"),
2262 };
2263 assert_eq!(text, " somecommand.py \"$leading_pholder\"");
2264 }
2265
2266 #[test]
2267 fn whitespace_stripping_when_command_is_empty() {
2268 let (document, diagnostics) = Document::parse(
2269 r#"
2270version 1.2
2271
2272task test {
2273 command <<<>>>
2274}
2275 "#,
2276 );
2277
2278 assert!(diagnostics.is_empty());
2279 let ast = document.ast();
2280 let ast = ast.as_v1().expect("should be a V1 AST");
2281 let tasks: Vec<_> = ast.tasks().collect();
2282 assert_eq!(tasks.len(), 1);
2283
2284 let command = tasks[0].command().expect("should have a command section");
2285
2286 let stripped = command.strip_whitespace().unwrap();
2287 assert_eq!(stripped.len(), 0);
2288 }
2289
2290 #[test]
2291 fn whitespace_stripping_when_command_is_one_line_of_whitespace() {
2292 let (document, diagnostics) = Document::parse(
2293 r#"
2294version 1.2
2295
2296task test {
2297 command <<< >>>
2298}
2299 "#,
2300 );
2301
2302 assert!(diagnostics.is_empty());
2303 let ast = document.ast();
2304 let ast = ast.as_v1().expect("should be a V1 AST");
2305 let tasks: Vec<_> = ast.tasks().collect();
2306 assert_eq!(tasks.len(), 1);
2307
2308 let command = tasks[0].command().expect("should have a command section");
2309
2310 let stripped = command.strip_whitespace().unwrap();
2311 assert_eq!(stripped.len(), 1);
2312 let text = match &stripped[0] {
2313 StrippedCommandPart::Text(text) => text,
2314 _ => panic!("expected text"),
2315 };
2316 assert_eq!(text, "");
2317 }
2318
2319 #[test]
2320 fn whitespace_stripping_when_command_is_one_newline() {
2321 let (document, diagnostics) = Document::parse(
2322 r#"
2323version 1.2
2324
2325task test {
2326 command <<<
2327 >>>
2328}
2329 "#,
2330 );
2331
2332 assert!(diagnostics.is_empty());
2333 let ast = document.ast();
2334 let ast = ast.as_v1().expect("should be a V1 AST");
2335 let tasks: Vec<_> = ast.tasks().collect();
2336 assert_eq!(tasks.len(), 1);
2337
2338 let command = tasks[0].command().expect("should have a command section");
2339
2340 let stripped = command.strip_whitespace().unwrap();
2341 assert_eq!(stripped.len(), 1);
2342 let text = match &stripped[0] {
2343 StrippedCommandPart::Text(text) => text,
2344 _ => panic!("expected text"),
2345 };
2346 assert_eq!(text, "");
2347 }
2348
2349 #[test]
2350 fn whitespace_stripping_when_command_is_a_blank_line() {
2351 let (document, diagnostics) = Document::parse(
2352 r#"
2353version 1.2
2354
2355task test {
2356 command <<<
2357
2358 >>>
2359}
2360 "#,
2361 );
2362
2363 assert!(diagnostics.is_empty());
2364 let ast = document.ast();
2365 let ast = ast.as_v1().expect("should be a V1 AST");
2366 let tasks: Vec<_> = ast.tasks().collect();
2367 assert_eq!(tasks.len(), 1);
2368
2369 let command = tasks[0].command().expect("should have a command section");
2370
2371 let stripped = command.strip_whitespace().unwrap();
2372 assert_eq!(stripped.len(), 1);
2373 let text = match &stripped[0] {
2374 StrippedCommandPart::Text(text) => text,
2375 _ => panic!("expected text"),
2376 };
2377 assert_eq!(text, "");
2378 }
2379
2380 #[test]
2381 fn whitespace_stripping_when_command_is_a_blank_line_with_spaces() {
2382 let (document, diagnostics) = Document::parse(
2383 r#"
2384version 1.2
2385
2386task test {
2387 command <<<
2388
2389 >>>
2390}
2391 "#,
2392 );
2393
2394 assert!(diagnostics.is_empty());
2395 let ast = document.ast();
2396 let ast = ast.as_v1().expect("should be a V1 AST");
2397 let tasks: Vec<_> = ast.tasks().collect();
2398 assert_eq!(tasks.len(), 1);
2399
2400 let command = tasks[0].command().expect("should have a command section");
2401
2402 let stripped = command.strip_whitespace().unwrap();
2403 assert_eq!(stripped.len(), 1);
2404 let text = match &stripped[0] {
2405 StrippedCommandPart::Text(text) => text,
2406 _ => panic!("expected text"),
2407 };
2408 assert_eq!(text, " ");
2409 }
2410
2411 #[test]
2412 fn whitespace_stripping_with_mixed_indentation() {
2413 let (document, diagnostics) = Document::parse(
2414 r#"
2415version 1.2
2416
2417task test {
2418 command <<<
2419 echo "hello"
2420 echo "world"
2421 echo \
2422 "goodbye"
2423 >>>
2424 }"#,
2425 );
2426
2427 assert!(diagnostics.is_empty());
2428 let ast = document.ast();
2429 let ast = ast.as_v1().expect("should be a V1 AST");
2430 let tasks: Vec<_> = ast.tasks().collect();
2431 assert_eq!(tasks.len(), 1);
2432
2433 let command = tasks[0].command().expect("should have a command section");
2434
2435 let stripped = command.strip_whitespace();
2436 assert!(stripped.is_none());
2437 }
2438
2439 #[test]
2440 fn whitespace_stripping_with_funky_indentation() {
2441 let (document, diagnostics) = Document::parse(
2442 r#"
2443version 1.2
2444
2445task test {
2446 command <<<
2447 echo "hello"
2448 echo "world"
2449 echo \
2450 "goodbye"
2451 >>>
2452 }"#,
2453 );
2454
2455 assert!(diagnostics.is_empty());
2456 let ast = document.ast();
2457 let ast = ast.as_v1().expect("should be a V1 AST");
2458 let tasks: Vec<_> = ast.tasks().collect();
2459 assert_eq!(tasks.len(), 1);
2460
2461 let command = tasks[0].command().expect("should have a command section");
2462
2463 let stripped = command.strip_whitespace().unwrap();
2464 assert_eq!(stripped.len(), 1);
2465 let text = match &stripped[0] {
2466 StrippedCommandPart::Text(text) => text,
2467 _ => panic!("expected text"),
2468 };
2469 assert_eq!(
2470 text,
2471 "echo \"hello\"\n echo \"world\"\necho \\\n \"goodbye\""
2472 );
2473 }
2474
2475 #[test]
2477 fn whitespace_stripping_with_content_on_first_line() {
2478 let (document, diagnostics) = Document::parse(
2479 r#"
2480version 1.2
2481
2482task test {
2483 command <<< weird stuff $firstlinelint
2484 # other weird whitespace
2485 somecommand.py $line120 ~{placeholder}
2486 >>>
2487 }"#,
2488 );
2489
2490 assert!(diagnostics.is_empty());
2491 let ast = document.ast();
2492 let ast = ast.as_v1().expect("should be a V1 AST");
2493 let tasks: Vec<_> = ast.tasks().collect();
2494 assert_eq!(tasks.len(), 1);
2495
2496 let command = tasks[0].command().expect("should have a command section");
2497
2498 let stripped = command.strip_whitespace().unwrap();
2499 assert_eq!(stripped.len(), 3);
2500 let text = match &stripped[0] {
2501 StrippedCommandPart::Text(text) => text,
2502 _ => panic!("expected text"),
2503 };
2504 assert_eq!(
2505 text,
2506 "weird stuff $firstlinelint\n # other weird whitespace\nsomecommand.py $line120 "
2507 );
2508
2509 let _placeholder = match &stripped[1] {
2510 StrippedCommandPart::Placeholder(p) => p,
2511 _ => panic!("expected placeholder"),
2512 };
2513 let text = match &stripped[2] {
2516 StrippedCommandPart::Text(text) => text,
2517 _ => panic!("expected text"),
2518 };
2519 assert_eq!(text, "");
2520 }
2521
2522 #[test]
2523 fn whitespace_stripping_on_windows() {
2524 let (document, diagnostics) = Document::parse(
2525 "version 1.2\r\ntask test {\r\n command <<<\r\n echo \"hello\"\r\n \
2526 >>>\r\n}\r\n",
2527 );
2528
2529 assert!(diagnostics.is_empty());
2530 let ast = document.ast();
2531 let ast = ast.as_v1().expect("should be a V1 AST");
2532 let tasks: Vec<_> = ast.tasks().collect();
2533 assert_eq!(tasks.len(), 1);
2534
2535 let command = tasks[0].command().expect("should have a command section");
2536 let stripped = command.strip_whitespace().unwrap();
2537 assert_eq!(stripped.len(), 1);
2538 let text = match &stripped[0] {
2539 StrippedCommandPart::Text(text) => text,
2540 _ => panic!("expected text"),
2541 };
2542 assert_eq!(text, "echo \"hello\"");
2543 }
2544}