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 count_whitespace(&self) -> Option<usize> {
1025 let mut min_leading_spaces = usize::MAX;
1026 let mut min_leading_tabs = usize::MAX;
1027 let mut parsing_leading_whitespace = false; let mut leading_spaces = 0;
1030 let mut leading_tabs = 0;
1031 for part in self.parts() {
1032 match part {
1033 CommandPart::Text(text) => {
1034 for c in text.text().chars() {
1035 match c {
1036 ' ' if parsing_leading_whitespace => {
1037 leading_spaces += 1;
1038 }
1039 '\t' if parsing_leading_whitespace => {
1040 leading_tabs += 1;
1041 }
1042 '\n' => {
1043 parsing_leading_whitespace = true;
1044 leading_spaces = 0;
1045 leading_tabs = 0;
1046 }
1047 '\r' => {}
1048 _ => {
1049 if parsing_leading_whitespace {
1050 parsing_leading_whitespace = false;
1051 if leading_spaces == 0 && leading_tabs == 0 {
1052 min_leading_spaces = 0;
1053 min_leading_tabs = 0;
1054 continue;
1055 }
1056 if leading_spaces < min_leading_spaces && leading_spaces > 0 {
1057 min_leading_spaces = leading_spaces;
1058 }
1059 if leading_tabs < min_leading_tabs && leading_tabs > 0 {
1060 min_leading_tabs = leading_tabs;
1061 }
1062 }
1063 }
1064 }
1065 }
1066 }
1068 CommandPart::Placeholder(_) => {
1069 if parsing_leading_whitespace {
1070 parsing_leading_whitespace = false;
1071 if leading_spaces == 0 && leading_tabs == 0 {
1072 min_leading_spaces = 0;
1073 min_leading_tabs = 0;
1074 continue;
1075 }
1076 if leading_spaces < min_leading_spaces && leading_spaces > 0 {
1077 min_leading_spaces = leading_spaces;
1078 }
1079 if leading_tabs < min_leading_tabs && leading_tabs > 0 {
1080 min_leading_tabs = leading_tabs;
1081 }
1082 }
1083 }
1084 }
1085 }
1086
1087 if (min_leading_spaces == 0 && min_leading_tabs == 0)
1089 || (min_leading_spaces == usize::MAX && min_leading_tabs == usize::MAX)
1090 {
1091 return Some(0);
1092 }
1093
1094 if (min_leading_spaces > 0 && min_leading_spaces != usize::MAX)
1096 && (min_leading_tabs > 0 && min_leading_tabs != usize::MAX)
1097 {
1098 return None;
1099 }
1100
1101 let final_leading_whitespace = if min_leading_spaces < min_leading_tabs {
1104 min_leading_spaces
1105 } else {
1106 min_leading_tabs
1107 };
1108
1109 Some(final_leading_whitespace)
1110 }
1111
1112 pub fn strip_whitespace(&self) -> Option<Vec<StrippedCommandPart<N>>> {
1116 let mut result = Vec::new();
1117 let heredoc = self.is_heredoc();
1118 for part in self.parts() {
1119 match part {
1120 CommandPart::Text(text) => {
1121 let mut s = String::new();
1122 unescape_command_text(text.text(), heredoc, &mut s);
1123 result.push(StrippedCommandPart::Text(s));
1124 }
1125 CommandPart::Placeholder(p) => {
1126 result.push(StrippedCommandPart::Placeholder(p));
1127 }
1128 }
1129 }
1130
1131 let mut whole_first_line_trimmed = false;
1133 if let Some(StrippedCommandPart::Text(text)) = result.first_mut() {
1134 let end_of_first_line = text.find('\n').map(|p| p + 1).unwrap_or(text.len());
1135 let line = &text[..end_of_first_line];
1136 let len = line.len() - line.trim_start().len();
1137 whole_first_line_trimmed = len == line.len();
1138 text.replace_range(..len, "");
1139 }
1140
1141 if let Some(StrippedCommandPart::Text(text)) = result.last_mut() {
1143 if let Some(index) = text.rfind(|c| !matches!(c, ' ' | '\t')) {
1144 text.truncate(index + 1);
1145 } else {
1146 text.clear();
1147 }
1148
1149 if text.ends_with('\n') {
1150 text.pop();
1151 }
1152
1153 if text.ends_with('\r') {
1154 text.pop();
1155 }
1156 }
1157
1158 let num_stripped_chars = self.count_whitespace()?;
1160
1161 if num_stripped_chars == 0 {
1163 return Some(result);
1164 }
1165
1166 let mut strip_leading_whitespace = whole_first_line_trimmed;
1170 for part in &mut result {
1171 match part {
1172 StrippedCommandPart::Text(text) => {
1173 let mut offset = 0;
1174 while let Some(next) = text[offset..].find('\n') {
1175 let next = next + offset;
1176 if offset > 0 {
1177 strip_leading_whitespace = true;
1178 }
1179
1180 if !strip_leading_whitespace {
1181 offset = next + 1;
1182 continue;
1183 }
1184
1185 let line = &text[offset..next];
1186 let line = line.strip_suffix('\r').unwrap_or(line);
1187 let len = line.len().min(num_stripped_chars);
1188 text.replace_range(offset..offset + len, "");
1189 offset = next + 1 - len;
1190 }
1191
1192 if strip_leading_whitespace || offset > 0 {
1194 let line = &text[offset..];
1195 let line = line.strip_suffix('\r').unwrap_or(line);
1196 let len = line.len().min(num_stripped_chars);
1197 text.replace_range(offset..offset + len, "");
1198 }
1199 }
1200 StrippedCommandPart::Placeholder(_) => {
1201 strip_leading_whitespace = false;
1202 }
1203 }
1204 }
1205
1206 Some(result)
1207 }
1208
1209 pub fn parent(&self) -> SectionParent<N> {
1211 SectionParent::cast(self.0.parent().expect("should have a parent"))
1212 .expect("parent should cast")
1213 }
1214}
1215
1216impl<N: TreeNode> AstNode<N> for CommandSection<N> {
1217 fn can_cast(kind: SyntaxKind) -> bool {
1218 kind == SyntaxKind::CommandSectionNode
1219 }
1220
1221 fn cast(inner: N) -> Option<Self> {
1222 match inner.kind() {
1223 SyntaxKind::CommandSectionNode => Some(Self(inner)),
1224 _ => None,
1225 }
1226 }
1227
1228 fn inner(&self) -> &N {
1229 &self.0
1230 }
1231}
1232
1233#[derive(Clone, Debug, PartialEq, Eq)]
1235pub struct CommandText<T: TreeToken = SyntaxToken>(T);
1236
1237impl<T: TreeToken> CommandText<T> {
1238 pub fn unescape_to(&self, heredoc: bool, buffer: &mut String) {
1244 unescape_command_text(self.text(), heredoc, buffer);
1245 }
1246}
1247
1248impl<T: TreeToken> AstToken<T> for CommandText<T> {
1249 fn can_cast(kind: SyntaxKind) -> bool {
1250 kind == SyntaxKind::LiteralCommandText
1251 }
1252
1253 fn cast(inner: T) -> Option<Self> {
1254 match inner.kind() {
1255 SyntaxKind::LiteralCommandText => Some(Self(inner)),
1256 _ => None,
1257 }
1258 }
1259
1260 fn inner(&self) -> &T {
1261 &self.0
1262 }
1263}
1264
1265#[derive(Clone, Debug, PartialEq, Eq)]
1267pub enum CommandPart<N: TreeNode = SyntaxNode> {
1268 Text(CommandText<N::Token>),
1270 Placeholder(Placeholder<N>),
1272}
1273
1274impl<N: TreeNode> CommandPart<N> {
1275 pub fn unwrap_text(self) -> CommandText<N::Token> {
1281 match self {
1282 Self::Text(text) => text,
1283 _ => panic!("not string text"),
1284 }
1285 }
1286
1287 pub fn unwrap_placeholder(self) -> Placeholder<N> {
1293 match self {
1294 Self::Placeholder(p) => p,
1295 _ => panic!("not a placeholder"),
1296 }
1297 }
1298
1299 fn cast(element: NodeOrToken<N, N::Token>) -> Option<Self> {
1303 match element {
1304 NodeOrToken::Node(n) => Some(Self::Placeholder(Placeholder::cast(n)?)),
1305 NodeOrToken::Token(t) => Some(Self::Text(CommandText::cast(t)?)),
1306 }
1307 }
1308}
1309
1310#[derive(Clone, Debug, PartialEq, Eq)]
1312pub struct RequirementsSection<N: TreeNode = SyntaxNode>(N);
1313
1314impl<N: TreeNode> RequirementsSection<N> {
1315 pub fn items(&self) -> impl Iterator<Item = RequirementsItem<N>> + use<'_, N> {
1317 self.children()
1318 }
1319
1320 pub fn parent(&self) -> SectionParent<N> {
1322 SectionParent::cast(self.0.parent().expect("should have a parent"))
1323 .expect("parent should cast")
1324 }
1325
1326 pub fn container(&self) -> Option<requirements::item::Container<N>> {
1329 self.child()
1332 }
1333}
1334
1335impl<N: TreeNode> AstNode<N> for RequirementsSection<N> {
1336 fn can_cast(kind: SyntaxKind) -> bool {
1337 kind == SyntaxKind::RequirementsSectionNode
1338 }
1339
1340 fn cast(inner: N) -> Option<Self> {
1341 match inner.kind() {
1342 SyntaxKind::RequirementsSectionNode => Some(Self(inner)),
1343 _ => None,
1344 }
1345 }
1346
1347 fn inner(&self) -> &N {
1348 &self.0
1349 }
1350}
1351
1352#[derive(Clone, Debug, PartialEq, Eq)]
1354pub struct RequirementsItem<N: TreeNode = SyntaxNode>(N);
1355
1356impl<N: TreeNode> RequirementsItem<N> {
1357 pub fn name(&self) -> Ident<N::Token> {
1359 self.token().expect("expected an item name")
1360 }
1361
1362 pub fn expr(&self) -> Expr<N> {
1364 Expr::child(&self.0).expect("expected an item expression")
1365 }
1366
1367 pub fn into_container(self) -> Option<requirements::item::Container<N>> {
1370 requirements::item::Container::try_from(self).ok()
1371 }
1372}
1373
1374impl<N: TreeNode> AstNode<N> for RequirementsItem<N> {
1375 fn can_cast(kind: SyntaxKind) -> bool {
1376 kind == SyntaxKind::RequirementsItemNode
1377 }
1378
1379 fn cast(inner: N) -> Option<Self> {
1380 match inner.kind() {
1381 SyntaxKind::RequirementsItemNode => Some(Self(inner)),
1382 _ => None,
1383 }
1384 }
1385
1386 fn inner(&self) -> &N {
1387 &self.0
1388 }
1389}
1390
1391#[derive(Clone, Debug, PartialEq, Eq)]
1393pub struct TaskHintsSection<N: TreeNode = SyntaxNode>(N);
1394
1395impl<N: TreeNode> TaskHintsSection<N> {
1396 pub fn items(&self) -> impl Iterator<Item = TaskHintsItem<N>> + use<'_, N> {
1398 self.children()
1399 }
1400
1401 pub fn parent(&self) -> TaskDefinition<N> {
1403 TaskDefinition::cast(self.0.parent().expect("should have a parent"))
1404 .expect("parent should cast")
1405 }
1406}
1407
1408impl<N: TreeNode> AstNode<N> for TaskHintsSection<N> {
1409 fn can_cast(kind: SyntaxKind) -> bool {
1410 kind == SyntaxKind::TaskHintsSectionNode
1411 }
1412
1413 fn cast(inner: N) -> Option<Self> {
1414 match inner.kind() {
1415 SyntaxKind::TaskHintsSectionNode => Some(Self(inner)),
1416 _ => None,
1417 }
1418 }
1419
1420 fn inner(&self) -> &N {
1421 &self.0
1422 }
1423}
1424
1425#[derive(Clone, Debug, PartialEq, Eq)]
1427pub struct TaskHintsItem<N: TreeNode = SyntaxNode>(N);
1428
1429impl<N: TreeNode> TaskHintsItem<N> {
1430 pub fn name(&self) -> Ident<N::Token> {
1432 self.token().expect("expected an item name")
1433 }
1434
1435 pub fn expr(&self) -> Expr<N> {
1437 Expr::child(&self.0).expect("expected an item expression")
1438 }
1439}
1440
1441impl<N: TreeNode> AstNode<N> for TaskHintsItem<N> {
1442 fn can_cast(kind: SyntaxKind) -> bool {
1443 kind == SyntaxKind::TaskHintsItemNode
1444 }
1445
1446 fn cast(inner: N) -> Option<Self> {
1447 match inner.kind() {
1448 SyntaxKind::TaskHintsItemNode => Some(Self(inner)),
1449 _ => None,
1450 }
1451 }
1452
1453 fn inner(&self) -> &N {
1454 &self.0
1455 }
1456}
1457
1458#[derive(Clone, Debug, PartialEq, Eq)]
1460pub struct RuntimeSection<N: TreeNode = SyntaxNode>(N);
1461
1462impl<N: TreeNode> RuntimeSection<N> {
1463 pub fn items(&self) -> impl Iterator<Item = RuntimeItem<N>> + use<'_, N> {
1465 self.children()
1466 }
1467
1468 pub fn parent(&self) -> SectionParent<N> {
1470 SectionParent::cast(self.0.parent().expect("should have a parent"))
1471 .expect("parent should cast")
1472 }
1473
1474 pub fn container(&self) -> Option<runtime::item::Container<N>> {
1477 self.child()
1480 }
1481}
1482
1483impl<N: TreeNode> AstNode<N> for RuntimeSection<N> {
1484 fn can_cast(kind: SyntaxKind) -> bool {
1485 kind == SyntaxKind::RuntimeSectionNode
1486 }
1487
1488 fn cast(inner: N) -> Option<Self> {
1489 match inner.kind() {
1490 SyntaxKind::RuntimeSectionNode => Some(Self(inner)),
1491 _ => None,
1492 }
1493 }
1494
1495 fn inner(&self) -> &N {
1496 &self.0
1497 }
1498}
1499
1500#[derive(Clone, Debug, PartialEq, Eq)]
1502pub struct RuntimeItem<N: TreeNode = SyntaxNode>(N);
1503
1504impl<N: TreeNode> RuntimeItem<N> {
1505 pub fn name(&self) -> Ident<N::Token> {
1507 self.token().expect("expected an item name")
1508 }
1509
1510 pub fn expr(&self) -> Expr<N> {
1512 Expr::child(&self.0).expect("expected an item expression")
1513 }
1514
1515 pub fn into_container(self) -> Option<runtime::item::Container<N>> {
1518 runtime::item::Container::try_from(self).ok()
1519 }
1520}
1521
1522impl<N: TreeNode> AstNode<N> for RuntimeItem<N> {
1523 fn can_cast(kind: SyntaxKind) -> bool {
1524 kind == SyntaxKind::RuntimeItemNode
1525 }
1526
1527 fn cast(inner: N) -> Option<Self> {
1528 match inner.kind() {
1529 SyntaxKind::RuntimeItemNode => Some(Self(inner)),
1530 _ => None,
1531 }
1532 }
1533
1534 fn inner(&self) -> &N {
1535 &self.0
1536 }
1537}
1538
1539#[derive(Clone, Debug, PartialEq, Eq)]
1541pub struct MetadataSection<N: TreeNode = SyntaxNode>(N);
1542
1543impl<N: TreeNode> MetadataSection<N> {
1544 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1546 self.children()
1547 }
1548
1549 pub fn parent(&self) -> SectionParent<N> {
1551 SectionParent::cast(self.0.parent().expect("should have a parent"))
1552 .expect("parent should cast")
1553 }
1554}
1555
1556impl<N: TreeNode> AstNode<N> for MetadataSection<N> {
1557 fn can_cast(kind: SyntaxKind) -> bool {
1558 kind == SyntaxKind::MetadataSectionNode
1559 }
1560
1561 fn cast(inner: N) -> Option<Self> {
1562 match inner.kind() {
1563 SyntaxKind::MetadataSectionNode => Some(Self(inner)),
1564 _ => None,
1565 }
1566 }
1567
1568 fn inner(&self) -> &N {
1569 &self.0
1570 }
1571}
1572
1573#[derive(Clone, Debug, PartialEq, Eq)]
1575pub struct MetadataObjectItem<N: TreeNode = SyntaxNode>(N);
1576
1577impl<N: TreeNode> MetadataObjectItem<N> {
1578 pub fn name(&self) -> Ident<N::Token> {
1580 self.token().expect("expected a name")
1581 }
1582
1583 pub fn value(&self) -> MetadataValue<N> {
1585 self.child().expect("expected a value")
1586 }
1587}
1588
1589impl<N: TreeNode> AstNode<N> for MetadataObjectItem<N> {
1590 fn can_cast(kind: SyntaxKind) -> bool {
1591 kind == SyntaxKind::MetadataObjectItemNode
1592 }
1593
1594 fn cast(inner: N) -> Option<Self> {
1595 match inner.kind() {
1596 SyntaxKind::MetadataObjectItemNode => Some(Self(inner)),
1597 _ => None,
1598 }
1599 }
1600
1601 fn inner(&self) -> &N {
1602 &self.0
1603 }
1604}
1605
1606#[derive(Clone, Debug, PartialEq, Eq)]
1608pub enum MetadataValue<N: TreeNode = SyntaxNode> {
1609 Boolean(LiteralBoolean<N>),
1611 Integer(LiteralInteger<N>),
1613 Float(LiteralFloat<N>),
1615 String(LiteralString<N>),
1617 Null(LiteralNull<N>),
1619 Object(MetadataObject<N>),
1621 Array(MetadataArray<N>),
1623}
1624
1625impl<N: TreeNode> MetadataValue<N> {
1626 pub fn unwrap_boolean(self) -> LiteralBoolean<N> {
1632 match self {
1633 Self::Boolean(b) => b,
1634 _ => panic!("not a boolean"),
1635 }
1636 }
1637
1638 pub fn unwrap_integer(self) -> LiteralInteger<N> {
1644 match self {
1645 Self::Integer(i) => i,
1646 _ => panic!("not an integer"),
1647 }
1648 }
1649
1650 pub fn unwrap_float(self) -> LiteralFloat<N> {
1656 match self {
1657 Self::Float(f) => f,
1658 _ => panic!("not a float"),
1659 }
1660 }
1661
1662 pub fn unwrap_string(self) -> LiteralString<N> {
1668 match self {
1669 Self::String(s) => s,
1670 _ => panic!("not a string"),
1671 }
1672 }
1673
1674 pub fn unwrap_null(self) -> LiteralNull<N> {
1680 match self {
1681 Self::Null(n) => n,
1682 _ => panic!("not a null"),
1683 }
1684 }
1685
1686 pub fn unwrap_object(self) -> MetadataObject<N> {
1692 match self {
1693 Self::Object(o) => o,
1694 _ => panic!("not an object"),
1695 }
1696 }
1697
1698 pub fn unwrap_array(self) -> MetadataArray<N> {
1704 match self {
1705 Self::Array(a) => a,
1706 _ => panic!("not an array"),
1707 }
1708 }
1709}
1710
1711impl<N: TreeNode> AstNode<N> for MetadataValue<N> {
1712 fn can_cast(kind: SyntaxKind) -> bool {
1713 matches!(
1714 kind,
1715 SyntaxKind::LiteralBooleanNode
1716 | SyntaxKind::LiteralIntegerNode
1717 | SyntaxKind::LiteralFloatNode
1718 | SyntaxKind::LiteralStringNode
1719 | SyntaxKind::LiteralNullNode
1720 | SyntaxKind::MetadataObjectNode
1721 | SyntaxKind::MetadataArrayNode
1722 )
1723 }
1724
1725 fn cast(inner: N) -> Option<Self> {
1726 match inner.kind() {
1727 SyntaxKind::LiteralBooleanNode => Some(Self::Boolean(LiteralBoolean(inner))),
1728 SyntaxKind::LiteralIntegerNode => Some(Self::Integer(LiteralInteger(inner))),
1729 SyntaxKind::LiteralFloatNode => Some(Self::Float(LiteralFloat(inner))),
1730 SyntaxKind::LiteralStringNode => Some(Self::String(LiteralString(inner))),
1731 SyntaxKind::LiteralNullNode => Some(Self::Null(LiteralNull(inner))),
1732 SyntaxKind::MetadataObjectNode => Some(Self::Object(MetadataObject(inner))),
1733 SyntaxKind::MetadataArrayNode => Some(Self::Array(MetadataArray(inner))),
1734 _ => None,
1735 }
1736 }
1737
1738 fn inner(&self) -> &N {
1739 match self {
1740 Self::Boolean(b) => &b.0,
1741 Self::Integer(i) => &i.0,
1742 Self::Float(f) => &f.0,
1743 Self::String(s) => &s.0,
1744 Self::Null(n) => &n.0,
1745 Self::Object(o) => &o.0,
1746 Self::Array(a) => &a.0,
1747 }
1748 }
1749}
1750
1751#[derive(Clone, Debug, PartialEq, Eq)]
1753pub struct LiteralNull<N: TreeNode = SyntaxNode>(N);
1754
1755impl<N: TreeNode> AstNode<N> for LiteralNull<N> {
1756 fn can_cast(kind: SyntaxKind) -> bool {
1757 kind == SyntaxKind::LiteralNullNode
1758 }
1759
1760 fn cast(inner: N) -> Option<Self> {
1761 match inner.kind() {
1762 SyntaxKind::LiteralNullNode => Some(Self(inner)),
1763 _ => None,
1764 }
1765 }
1766
1767 fn inner(&self) -> &N {
1768 &self.0
1769 }
1770}
1771
1772#[derive(Clone, Debug, PartialEq, Eq)]
1774pub struct MetadataObject<N: TreeNode = SyntaxNode>(N);
1775
1776impl<N: TreeNode> MetadataObject<N> {
1777 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1779 self.children()
1780 }
1781}
1782
1783impl<N: TreeNode> AstNode<N> for MetadataObject<N> {
1784 fn can_cast(kind: SyntaxKind) -> bool {
1785 kind == SyntaxKind::MetadataObjectNode
1786 }
1787
1788 fn cast(inner: N) -> Option<Self> {
1789 match inner.kind() {
1790 SyntaxKind::MetadataObjectNode => Some(Self(inner)),
1791 _ => None,
1792 }
1793 }
1794
1795 fn inner(&self) -> &N {
1796 &self.0
1797 }
1798}
1799
1800#[derive(Clone, Debug, PartialEq, Eq)]
1802pub struct MetadataArray<N: TreeNode = SyntaxNode>(N);
1803
1804impl<N: TreeNode> MetadataArray<N> {
1805 pub fn elements(&self) -> impl Iterator<Item = MetadataValue<N>> + use<'_, N> {
1807 self.children()
1808 }
1809}
1810
1811impl<N: TreeNode> AstNode<N> for MetadataArray<N> {
1812 fn can_cast(kind: SyntaxKind) -> bool {
1813 kind == SyntaxKind::MetadataArrayNode
1814 }
1815
1816 fn cast(inner: N) -> Option<Self> {
1817 match inner.kind() {
1818 SyntaxKind::MetadataArrayNode => Some(Self(inner)),
1819 _ => None,
1820 }
1821 }
1822
1823 fn inner(&self) -> &N {
1824 &self.0
1825 }
1826}
1827
1828#[derive(Clone, Debug, PartialEq, Eq)]
1830pub struct ParameterMetadataSection<N: TreeNode = SyntaxNode>(N);
1831
1832impl<N: TreeNode> ParameterMetadataSection<N> {
1833 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1835 self.children()
1836 }
1837
1838 pub fn parent(&self) -> SectionParent<N> {
1840 SectionParent::cast(self.0.parent().expect("should have a parent"))
1841 .expect("parent should cast")
1842 }
1843}
1844
1845impl<N: TreeNode> AstNode<N> for ParameterMetadataSection<N> {
1846 fn can_cast(kind: SyntaxKind) -> bool {
1847 kind == SyntaxKind::ParameterMetadataSectionNode
1848 }
1849
1850 fn cast(inner: N) -> Option<Self> {
1851 match inner.kind() {
1852 SyntaxKind::ParameterMetadataSectionNode => Some(Self(inner)),
1853 _ => None,
1854 }
1855 }
1856
1857 fn inner(&self) -> &N {
1858 &self.0
1859 }
1860}
1861
1862#[cfg(test)]
1863mod test {
1864 use pretty_assertions::assert_eq;
1865
1866 use super::*;
1867 use crate::Document;
1868
1869 #[test]
1870 fn tasks() {
1871 let (document, diagnostics) = Document::parse(
1872 r#"
1873version 1.2
1874
1875task test {
1876 input {
1877 String name
1878 }
1879
1880 output {
1881 String output = stdout()
1882 }
1883
1884 command <<<
1885 printf "hello, ~{name}!
1886 >>>
1887
1888 requirements {
1889 container: "baz/qux"
1890 }
1891
1892 hints {
1893 foo: "bar"
1894 }
1895
1896 runtime {
1897 container: "foo/bar"
1898 }
1899
1900 meta {
1901 description: "a test"
1902 foo: null
1903 }
1904
1905 parameter_meta {
1906 name: {
1907 help: "a name to greet"
1908 }
1909 }
1910
1911 String x = "private"
1912}
1913"#,
1914 );
1915
1916 assert!(diagnostics.is_empty());
1917 let ast = document.ast();
1918 let ast = ast.as_v1().expect("should be a V1 AST");
1919 let tasks: Vec<_> = ast.tasks().collect();
1920 assert_eq!(tasks.len(), 1);
1921 assert_eq!(tasks[0].name().text(), "test");
1922
1923 let input = tasks[0].input().expect("should have an input section");
1925 assert_eq!(input.parent().unwrap_task().name().text(), "test");
1926 let decls: Vec<_> = input.declarations().collect();
1927 assert_eq!(decls.len(), 1);
1928 assert_eq!(
1929 decls[0].clone().unwrap_unbound_decl().ty().to_string(),
1930 "String"
1931 );
1932 assert_eq!(decls[0].clone().unwrap_unbound_decl().name().text(), "name");
1933
1934 let output = tasks[0].output().expect("should have an output section");
1936 assert_eq!(output.parent().unwrap_task().name().text(), "test");
1937 let decls: Vec<_> = output.declarations().collect();
1938 assert_eq!(decls.len(), 1);
1939 assert_eq!(decls[0].ty().to_string(), "String");
1940 assert_eq!(decls[0].name().text(), "output");
1941 assert_eq!(decls[0].expr().unwrap_call().target().text(), "stdout");
1942
1943 let command = tasks[0].command().expect("should have a command section");
1945 assert_eq!(command.parent().name().text(), "test");
1946 assert!(command.is_heredoc());
1947 let parts: Vec<_> = command.parts().collect();
1948 assert_eq!(parts.len(), 3);
1949 assert_eq!(
1950 parts[0].clone().unwrap_text().text(),
1951 "\n printf \"hello, "
1952 );
1953 assert_eq!(
1954 parts[1]
1955 .clone()
1956 .unwrap_placeholder()
1957 .expr()
1958 .unwrap_name_ref()
1959 .name()
1960 .text(),
1961 "name"
1962 );
1963 assert_eq!(parts[2].clone().unwrap_text().text(), "!\n ");
1964
1965 let requirements = tasks[0]
1967 .requirements()
1968 .expect("should have a requirements section");
1969 assert_eq!(requirements.parent().name().text(), "test");
1970 let items: Vec<_> = requirements.items().collect();
1971 assert_eq!(items.len(), 1);
1972 assert_eq!(items[0].name().text(), TASK_REQUIREMENT_CONTAINER);
1973 assert_eq!(
1974 items[0]
1975 .expr()
1976 .unwrap_literal()
1977 .unwrap_string()
1978 .text()
1979 .unwrap()
1980 .text(),
1981 "baz/qux"
1982 );
1983
1984 let hints = tasks[0].hints().expect("should have a hints section");
1986 assert_eq!(hints.parent().name().text(), "test");
1987 let items: Vec<_> = hints.items().collect();
1988 assert_eq!(items.len(), 1);
1989 assert_eq!(items[0].name().text(), "foo");
1990 assert_eq!(
1991 items[0]
1992 .expr()
1993 .unwrap_literal()
1994 .unwrap_string()
1995 .text()
1996 .unwrap()
1997 .text(),
1998 "bar"
1999 );
2000
2001 let runtime = tasks[0].runtime().expect("should have a runtime section");
2003 assert_eq!(runtime.parent().name().text(), "test");
2004 let items: Vec<_> = runtime.items().collect();
2005 assert_eq!(items.len(), 1);
2006 assert_eq!(items[0].name().text(), TASK_REQUIREMENT_CONTAINER);
2007 assert_eq!(
2008 items[0]
2009 .expr()
2010 .unwrap_literal()
2011 .unwrap_string()
2012 .text()
2013 .unwrap()
2014 .text(),
2015 "foo/bar"
2016 );
2017
2018 let metadata = tasks[0].metadata().expect("should have a metadata section");
2020 assert_eq!(metadata.parent().unwrap_task().name().text(), "test");
2021 let items: Vec<_> = metadata.items().collect();
2022 assert_eq!(items.len(), 2);
2023 assert_eq!(items[0].name().text(), "description");
2024 assert_eq!(
2025 items[0].value().unwrap_string().text().unwrap().text(),
2026 "a test"
2027 );
2028
2029 assert_eq!(items[1].name().text(), "foo");
2031 items[1].value().unwrap_null();
2032
2033 let param_meta = tasks[0]
2035 .parameter_metadata()
2036 .expect("should have a parameter metadata section");
2037 assert_eq!(param_meta.parent().unwrap_task().name().text(), "test");
2038 let items: Vec<_> = param_meta.items().collect();
2039 assert_eq!(items.len(), 1);
2040 assert_eq!(items[0].name().text(), "name");
2041 let items: Vec<_> = items[0].value().unwrap_object().items().collect();
2042 assert_eq!(items.len(), 1);
2043 assert_eq!(items[0].name().text(), "help");
2044 assert_eq!(
2045 items[0].value().unwrap_string().text().unwrap().text(),
2046 "a name to greet"
2047 );
2048
2049 let decls: Vec<_> = tasks[0].declarations().collect();
2051 assert_eq!(decls.len(), 1);
2052
2053 assert_eq!(decls[0].ty().to_string(), "String");
2055 assert_eq!(decls[0].name().text(), "x");
2056 assert_eq!(
2057 decls[0]
2058 .expr()
2059 .unwrap_literal()
2060 .unwrap_string()
2061 .text()
2062 .unwrap()
2063 .text(),
2064 "private"
2065 );
2066 }
2067
2068 #[test]
2069 fn whitespace_stripping_without_interpolation() {
2070 let (document, diagnostics) = Document::parse(
2071 r#"
2072version 1.2
2073
2074task test {
2075 command <<<
2076 echo "hello"
2077 echo "world"
2078 echo \
2079 "goodbye"
2080 >>>
2081}
2082"#,
2083 );
2084
2085 assert!(diagnostics.is_empty());
2086 let ast = document.ast();
2087 let ast = ast.as_v1().expect("should be a V1 AST");
2088 let tasks: Vec<_> = ast.tasks().collect();
2089 assert_eq!(tasks.len(), 1);
2090
2091 let command = tasks[0].command().expect("should have a command section");
2092
2093 let stripped = command.strip_whitespace().unwrap();
2094
2095 assert_eq!(stripped.len(), 1);
2096 let text = match &stripped[0] {
2097 StrippedCommandPart::Text(text) => text,
2098 _ => panic!("expected text"),
2099 };
2100 assert_eq!(
2101 text,
2102 "echo \"hello\"\necho \"world\"\necho \\\n \"goodbye\""
2103 );
2104 }
2105
2106 #[test]
2107 fn whitespace_stripping_with_interpolation() {
2108 let (document, diagnostics) = Document::parse(
2109 r#"
2110version 1.2
2111
2112task test {
2113 input {
2114 String name
2115 Boolean flag
2116 }
2117
2118 command <<<
2119 echo "hello, ~{
2120if flag
2121then name
2122 else "Jerry"
2123 }!"
2124 >>>
2125}
2126 "#,
2127 );
2128
2129 assert!(diagnostics.is_empty());
2130 let ast = document.ast();
2131 let ast = ast.as_v1().expect("should be a V1 AST");
2132 let tasks: Vec<_> = ast.tasks().collect();
2133 assert_eq!(tasks.len(), 1);
2134
2135 let command = tasks[0].command().expect("should have a command section");
2136
2137 let stripped = command.strip_whitespace().unwrap();
2138 assert_eq!(stripped.len(), 3);
2139 let text = match &stripped[0] {
2140 StrippedCommandPart::Text(text) => text,
2141 _ => panic!("expected text"),
2142 };
2143 assert_eq!(text, "echo \"hello, ");
2144
2145 let _placeholder = match &stripped[1] {
2146 StrippedCommandPart::Placeholder(p) => p,
2147 _ => panic!("expected placeholder"),
2148 };
2149 let text = match &stripped[2] {
2152 StrippedCommandPart::Text(text) => text,
2153 _ => panic!("expected text"),
2154 };
2155 assert_eq!(text, "!\"");
2156 }
2157
2158 #[test]
2159 fn whitespace_stripping_when_interpolation_starts_line() {
2160 let (document, diagnostics) = Document::parse(
2161 r#"
2162version 1.2
2163
2164task test {
2165 input {
2166 Int placeholder
2167 }
2168
2169 command <<<
2170 # other weird whitspace
2171 ~{placeholder} "$trailing_pholder" ~{placeholder}
2172 ~{placeholder} somecommand.py "$leading_pholder"
2173 >>>
2174}
2175"#,
2176 );
2177
2178 assert!(diagnostics.is_empty());
2179 let ast = document.ast();
2180 let ast = ast.as_v1().expect("should be a V1 AST");
2181 let tasks: Vec<_> = ast.tasks().collect();
2182 assert_eq!(tasks.len(), 1);
2183
2184 let command = tasks[0].command().expect("should have a command section");
2185
2186 let stripped = command.strip_whitespace().unwrap();
2187 assert_eq!(stripped.len(), 7);
2188 let text = match &stripped[0] {
2189 StrippedCommandPart::Text(text) => text,
2190 _ => panic!("expected text"),
2191 };
2192 assert_eq!(text, " # other weird whitspace\n");
2193
2194 let _placeholder = match &stripped[1] {
2195 StrippedCommandPart::Placeholder(p) => p,
2196 _ => panic!("expected placeholder"),
2197 };
2198 let text = match &stripped[2] {
2201 StrippedCommandPart::Text(text) => text,
2202 _ => panic!("expected text"),
2203 };
2204 assert_eq!(text, " \"$trailing_pholder\" ");
2205
2206 let _placeholder = match &stripped[3] {
2207 StrippedCommandPart::Placeholder(p) => p,
2208 _ => panic!("expected placeholder"),
2209 };
2210 let text = match &stripped[4] {
2213 StrippedCommandPart::Text(text) => text,
2214 _ => panic!("expected text"),
2215 };
2216 assert_eq!(text, "\n");
2217
2218 let _placeholder = match &stripped[5] {
2219 StrippedCommandPart::Placeholder(p) => p,
2220 _ => panic!("expected placeholder"),
2221 };
2222 let text = match &stripped[6] {
2225 StrippedCommandPart::Text(text) => text,
2226 _ => panic!("expected text"),
2227 };
2228 assert_eq!(text, " somecommand.py \"$leading_pholder\"");
2229 }
2230
2231 #[test]
2232 fn whitespace_stripping_when_command_is_empty() {
2233 let (document, diagnostics) = Document::parse(
2234 r#"
2235version 1.2
2236
2237task test {
2238 command <<<>>>
2239}
2240 "#,
2241 );
2242
2243 assert!(diagnostics.is_empty());
2244 let ast = document.ast();
2245 let ast = ast.as_v1().expect("should be a V1 AST");
2246 let tasks: Vec<_> = ast.tasks().collect();
2247 assert_eq!(tasks.len(), 1);
2248
2249 let command = tasks[0].command().expect("should have a command section");
2250
2251 let stripped = command.strip_whitespace().unwrap();
2252 assert_eq!(stripped.len(), 0);
2253 }
2254
2255 #[test]
2256 fn whitespace_stripping_when_command_is_one_line_of_whitespace() {
2257 let (document, diagnostics) = Document::parse(
2258 r#"
2259version 1.2
2260
2261task test {
2262 command <<< >>>
2263}
2264 "#,
2265 );
2266
2267 assert!(diagnostics.is_empty());
2268 let ast = document.ast();
2269 let ast = ast.as_v1().expect("should be a V1 AST");
2270 let tasks: Vec<_> = ast.tasks().collect();
2271 assert_eq!(tasks.len(), 1);
2272
2273 let command = tasks[0].command().expect("should have a command section");
2274
2275 let stripped = command.strip_whitespace().unwrap();
2276 assert_eq!(stripped.len(), 1);
2277 let text = match &stripped[0] {
2278 StrippedCommandPart::Text(text) => text,
2279 _ => panic!("expected text"),
2280 };
2281 assert_eq!(text, "");
2282 }
2283
2284 #[test]
2285 fn whitespace_stripping_when_command_is_one_newline() {
2286 let (document, diagnostics) = Document::parse(
2287 r#"
2288version 1.2
2289
2290task test {
2291 command <<<
2292 >>>
2293}
2294 "#,
2295 );
2296
2297 assert!(diagnostics.is_empty());
2298 let ast = document.ast();
2299 let ast = ast.as_v1().expect("should be a V1 AST");
2300 let tasks: Vec<_> = ast.tasks().collect();
2301 assert_eq!(tasks.len(), 1);
2302
2303 let command = tasks[0].command().expect("should have a command section");
2304
2305 let stripped = command.strip_whitespace().unwrap();
2306 assert_eq!(stripped.len(), 1);
2307 let text = match &stripped[0] {
2308 StrippedCommandPart::Text(text) => text,
2309 _ => panic!("expected text"),
2310 };
2311 assert_eq!(text, "");
2312 }
2313
2314 #[test]
2315 fn whitespace_stripping_when_command_is_a_blank_line() {
2316 let (document, diagnostics) = Document::parse(
2317 r#"
2318version 1.2
2319
2320task test {
2321 command <<<
2322
2323 >>>
2324}
2325 "#,
2326 );
2327
2328 assert!(diagnostics.is_empty());
2329 let ast = document.ast();
2330 let ast = ast.as_v1().expect("should be a V1 AST");
2331 let tasks: Vec<_> = ast.tasks().collect();
2332 assert_eq!(tasks.len(), 1);
2333
2334 let command = tasks[0].command().expect("should have a command section");
2335
2336 let stripped = command.strip_whitespace().unwrap();
2337 assert_eq!(stripped.len(), 1);
2338 let text = match &stripped[0] {
2339 StrippedCommandPart::Text(text) => text,
2340 _ => panic!("expected text"),
2341 };
2342 assert_eq!(text, "");
2343 }
2344
2345 #[test]
2346 fn whitespace_stripping_when_command_is_a_blank_line_with_spaces() {
2347 let (document, diagnostics) = Document::parse(
2348 r#"
2349version 1.2
2350
2351task test {
2352 command <<<
2353
2354 >>>
2355}
2356 "#,
2357 );
2358
2359 assert!(diagnostics.is_empty());
2360 let ast = document.ast();
2361 let ast = ast.as_v1().expect("should be a V1 AST");
2362 let tasks: Vec<_> = ast.tasks().collect();
2363 assert_eq!(tasks.len(), 1);
2364
2365 let command = tasks[0].command().expect("should have a command section");
2366
2367 let stripped = command.strip_whitespace().unwrap();
2368 assert_eq!(stripped.len(), 1);
2369 let text = match &stripped[0] {
2370 StrippedCommandPart::Text(text) => text,
2371 _ => panic!("expected text"),
2372 };
2373 assert_eq!(text, " ");
2374 }
2375
2376 #[test]
2377 fn whitespace_stripping_with_mixed_indentation() {
2378 let (document, diagnostics) = Document::parse(
2379 r#"
2380version 1.2
2381
2382task test {
2383 command <<<
2384 echo "hello"
2385 echo "world"
2386 echo \
2387 "goodbye"
2388 >>>
2389 }"#,
2390 );
2391
2392 assert!(diagnostics.is_empty());
2393 let ast = document.ast();
2394 let ast = ast.as_v1().expect("should be a V1 AST");
2395 let tasks: Vec<_> = ast.tasks().collect();
2396 assert_eq!(tasks.len(), 1);
2397
2398 let command = tasks[0].command().expect("should have a command section");
2399
2400 let stripped = command.strip_whitespace();
2401 assert!(stripped.is_none());
2402 }
2403
2404 #[test]
2405 fn whitespace_stripping_with_funky_indentation() {
2406 let (document, diagnostics) = Document::parse(
2407 r#"
2408version 1.2
2409
2410task test {
2411 command <<<
2412 echo "hello"
2413 echo "world"
2414 echo \
2415 "goodbye"
2416 >>>
2417 }"#,
2418 );
2419
2420 assert!(diagnostics.is_empty());
2421 let ast = document.ast();
2422 let ast = ast.as_v1().expect("should be a V1 AST");
2423 let tasks: Vec<_> = ast.tasks().collect();
2424 assert_eq!(tasks.len(), 1);
2425
2426 let command = tasks[0].command().expect("should have a command section");
2427
2428 let stripped = command.strip_whitespace().unwrap();
2429 assert_eq!(stripped.len(), 1);
2430 let text = match &stripped[0] {
2431 StrippedCommandPart::Text(text) => text,
2432 _ => panic!("expected text"),
2433 };
2434 assert_eq!(
2435 text,
2436 "echo \"hello\"\n echo \"world\"\necho \\\n \"goodbye\""
2437 );
2438 }
2439
2440 #[test]
2442 fn whitespace_stripping_with_content_on_first_line() {
2443 let (document, diagnostics) = Document::parse(
2444 r#"
2445version 1.2
2446
2447task test {
2448 command <<< weird stuff $firstlinelint
2449 # other weird whitespace
2450 somecommand.py $line120 ~{placeholder}
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(), 3);
2465 let text = match &stripped[0] {
2466 StrippedCommandPart::Text(text) => text,
2467 _ => panic!("expected text"),
2468 };
2469 assert_eq!(
2470 text,
2471 "weird stuff $firstlinelint\n # other weird whitespace\nsomecommand.py $line120 "
2472 );
2473
2474 let _placeholder = match &stripped[1] {
2475 StrippedCommandPart::Placeholder(p) => p,
2476 _ => panic!("expected placeholder"),
2477 };
2478 let text = match &stripped[2] {
2481 StrippedCommandPart::Text(text) => text,
2482 _ => panic!("expected text"),
2483 };
2484 assert_eq!(text, "");
2485 }
2486
2487 #[test]
2488 fn whitespace_stripping_on_windows() {
2489 let (document, diagnostics) = Document::parse(
2490 "version 1.2\r\ntask test {\r\n command <<<\r\n echo \"hello\"\r\n \
2491 >>>\r\n}\r\n",
2492 );
2493
2494 assert!(diagnostics.is_empty());
2495 let ast = document.ast();
2496 let ast = ast.as_v1().expect("should be a V1 AST");
2497 let tasks: Vec<_> = ast.tasks().collect();
2498 assert_eq!(tasks.len(), 1);
2499
2500 let command = tasks[0].command().expect("should have a command section");
2501 let stripped = command.strip_whitespace().unwrap();
2502 assert_eq!(stripped.len(), 1);
2503 let text = match &stripped[0] {
2504 StrippedCommandPart::Text(text) => text,
2505 _ => panic!("expected text"),
2506 };
2507 assert_eq!(text, "echo \"hello\"");
2508 }
2509}