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