1use rowan::NodeOrToken;
4
5use super::BoundDecl;
6use super::Decl;
7use super::Expr;
8use super::LiteralBoolean;
9use super::LiteralFloat;
10use super::LiteralInteger;
11use super::LiteralString;
12use super::OpenHeredoc;
13use super::Placeholder;
14use super::StructDefinition;
15use super::WorkflowDefinition;
16use crate::AstNode;
17use crate::AstToken;
18use crate::Ident;
19use crate::SyntaxKind;
20use crate::SyntaxNode;
21use crate::SyntaxToken;
22use crate::TreeNode;
23use crate::TreeToken;
24
25pub mod common;
26pub mod requirements;
27pub mod runtime;
28
29pub const TASK_FIELD_NAME: &str = "name";
31pub const TASK_FIELD_ID: &str = "id";
33pub const TASK_FIELD_CONTAINER: &str = "container";
35pub const TASK_FIELD_CPU: &str = "cpu";
37pub const TASK_FIELD_MEMORY: &str = "memory";
39pub const TASK_FIELD_ATTEMPT: &str = "attempt";
41pub const TASK_FIELD_GPU: &str = "gpu";
43pub const TASK_FIELD_FPGA: &str = "fpga";
45pub const TASK_FIELD_DISKS: &str = "disks";
47pub const TASK_FIELD_END_TIME: &str = "end_time";
49pub const TASK_FIELD_RETURN_CODE: &str = "return_code";
51pub const TASK_FIELD_META: &str = "meta";
53pub const TASK_FIELD_PARAMETER_META: &str = "parameter_meta";
55pub const TASK_FIELD_EXT: &str = "ext";
57
58pub const TASK_REQUIREMENT_CONTAINER: &str = "container";
60pub const TASK_REQUIREMENT_CONTAINER_ALIAS: &str = "docker";
62pub const TASK_REQUIREMENT_CPU: &str = "cpu";
64pub const TASK_REQUIREMENT_DISKS: &str = "disks";
66pub const TASK_REQUIREMENT_GPU: &str = "gpu";
68pub const TASK_REQUIREMENT_FPGA: &str = "fpga";
70pub const TASK_REQUIREMENT_MAX_RETRIES: &str = "max_retries";
72pub const TASK_REQUIREMENT_MAX_RETRIES_ALIAS: &str = "maxRetries";
74pub const TASK_REQUIREMENT_MEMORY: &str = "memory";
76pub const TASK_REQUIREMENT_RETURN_CODES: &str = "return_codes";
78pub const TASK_REQUIREMENT_RETURN_CODES_ALIAS: &str = "returnCodes";
80
81pub const TASK_HINT_DISKS: &str = "disks";
83pub const TASK_HINT_GPU: &str = "gpu";
85pub const TASK_HINT_FPGA: &str = "fpga";
87pub const TASK_HINT_INPUTS: &str = "inputs";
89pub const TASK_HINT_LOCALIZATION_OPTIONAL: &str = "localization_optional";
91pub const TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS: &str = "localizationOptional";
94pub const TASK_HINT_MAX_CPU: &str = "max_cpu";
96pub const TASK_HINT_MAX_CPU_ALIAS: &str = "maxCpu";
98pub const TASK_HINT_MAX_MEMORY: &str = "max_memory";
100pub const TASK_HINT_MAX_MEMORY_ALIAS: &str = "maxMemory";
102pub const TASK_HINT_OUTPUTS: &str = "outputs";
104pub const TASK_HINT_SHORT_TASK: &str = "short_task";
106pub const TASK_HINT_SHORT_TASK_ALIAS: &str = "shortTask";
108
109fn unescape_command_text(s: &str, heredoc: bool, buffer: &mut String) {
111 let mut chars = s.chars().peekable();
112 while let Some(c) = chars.next() {
113 match c {
114 '\\' => match chars.peek() {
115 Some('\\') | Some('~') => {
116 buffer.push(chars.next().unwrap());
117 }
118 Some('>') if heredoc => {
119 buffer.push(chars.next().unwrap());
120 }
121 Some('$') | Some('}') if !heredoc => {
122 buffer.push(chars.next().unwrap());
123 }
124 _ => {
125 buffer.push('\\');
126 }
127 },
128 _ => {
129 buffer.push(c);
130 }
131 }
132 }
133}
134
135#[derive(Clone, Debug, PartialEq, Eq)]
137pub struct TaskDefinition<N: TreeNode = SyntaxNode>(N);
138
139impl<N: TreeNode> TaskDefinition<N> {
140 pub fn name(&self) -> Ident<N::Token> {
142 self.token().expect("task should have a name")
143 }
144
145 pub fn items(&self) -> impl Iterator<Item = TaskItem<N>> + use<'_, N> {
147 TaskItem::children(&self.0)
148 }
149
150 pub fn input(&self) -> Option<InputSection<N>> {
152 self.child()
153 }
154
155 pub fn output(&self) -> Option<OutputSection<N>> {
157 self.child()
158 }
159
160 pub fn command(&self) -> Option<CommandSection<N>> {
162 self.child()
163 }
164
165 pub fn requirements(&self) -> Option<RequirementsSection<N>> {
167 self.child()
168 }
169
170 pub fn hints(&self) -> Option<TaskHintsSection<N>> {
172 self.child()
173 }
174
175 pub fn runtime(&self) -> Option<RuntimeSection<N>> {
177 self.child()
178 }
179
180 pub fn metadata(&self) -> Option<MetadataSection<N>> {
182 self.child()
183 }
184
185 pub fn parameter_metadata(&self) -> Option<ParameterMetadataSection<N>> {
187 self.child()
188 }
189
190 pub fn declarations(&self) -> impl Iterator<Item = BoundDecl<N>> + use<'_, N> {
192 self.children()
193 }
194}
195
196impl<N: TreeNode> AstNode<N> for TaskDefinition<N> {
197 fn can_cast(kind: SyntaxKind) -> bool {
198 kind == SyntaxKind::TaskDefinitionNode
199 }
200
201 fn cast(inner: N) -> Option<Self> {
202 match inner.kind() {
203 SyntaxKind::TaskDefinitionNode => Some(Self(inner)),
204 _ => None,
205 }
206 }
207
208 fn inner(&self) -> &N {
209 &self.0
210 }
211}
212
213#[derive(Clone, Debug, PartialEq, Eq)]
215pub enum TaskItem<N: TreeNode = SyntaxNode> {
216 Input(InputSection<N>),
218 Output(OutputSection<N>),
220 Command(CommandSection<N>),
222 Requirements(RequirementsSection<N>),
224 Hints(TaskHintsSection<N>),
226 Runtime(RuntimeSection<N>),
228 Metadata(MetadataSection<N>),
230 ParameterMetadata(ParameterMetadataSection<N>),
232 Declaration(BoundDecl<N>),
234}
235
236impl<N: TreeNode> TaskItem<N> {
237 pub fn can_cast(kind: SyntaxKind) -> bool {
240 matches!(
241 kind,
242 SyntaxKind::InputSectionNode
243 | SyntaxKind::OutputSectionNode
244 | SyntaxKind::CommandSectionNode
245 | SyntaxKind::RequirementsSectionNode
246 | SyntaxKind::TaskHintsSectionNode
247 | SyntaxKind::RuntimeSectionNode
248 | SyntaxKind::MetadataSectionNode
249 | SyntaxKind::ParameterMetadataSectionNode
250 | SyntaxKind::BoundDeclNode
251 )
252 }
253
254 pub fn cast(inner: N) -> Option<Self> {
258 match inner.kind() {
259 SyntaxKind::InputSectionNode => Some(Self::Input(
260 InputSection::cast(inner).expect("input section to cast"),
261 )),
262 SyntaxKind::OutputSectionNode => Some(Self::Output(
263 OutputSection::cast(inner).expect("output section to cast"),
264 )),
265 SyntaxKind::CommandSectionNode => Some(Self::Command(
266 CommandSection::cast(inner).expect("command section to cast"),
267 )),
268 SyntaxKind::RequirementsSectionNode => Some(Self::Requirements(
269 RequirementsSection::cast(inner).expect("requirements section to cast"),
270 )),
271 SyntaxKind::RuntimeSectionNode => Some(Self::Runtime(
272 RuntimeSection::cast(inner).expect("runtime section to cast"),
273 )),
274 SyntaxKind::MetadataSectionNode => Some(Self::Metadata(
275 MetadataSection::cast(inner).expect("metadata section to cast"),
276 )),
277 SyntaxKind::ParameterMetadataSectionNode => Some(Self::ParameterMetadata(
278 ParameterMetadataSection::cast(inner).expect("parameter metadata section to cast"),
279 )),
280 SyntaxKind::TaskHintsSectionNode => Some(Self::Hints(
281 TaskHintsSection::cast(inner).expect("task hints section to cast"),
282 )),
283 SyntaxKind::BoundDeclNode => Some(Self::Declaration(
284 BoundDecl::cast(inner).expect("bound decl to cast"),
285 )),
286 _ => None,
287 }
288 }
289
290 pub fn inner(&self) -> &N {
292 match self {
293 Self::Input(element) => element.inner(),
294 Self::Output(element) => element.inner(),
295 Self::Command(element) => element.inner(),
296 Self::Requirements(element) => element.inner(),
297 Self::Hints(element) => element.inner(),
298 Self::Runtime(element) => element.inner(),
299 Self::Metadata(element) => element.inner(),
300 Self::ParameterMetadata(element) => element.inner(),
301 Self::Declaration(element) => element.inner(),
302 }
303 }
304
305 pub fn as_input_section(&self) -> Option<&InputSection<N>> {
311 match self {
312 Self::Input(s) => Some(s),
313 _ => None,
314 }
315 }
316
317 pub fn into_input_section(self) -> Option<InputSection<N>> {
323 match self {
324 Self::Input(s) => Some(s),
325 _ => None,
326 }
327 }
328
329 pub fn as_output_section(&self) -> Option<&OutputSection<N>> {
335 match self {
336 Self::Output(s) => Some(s),
337 _ => None,
338 }
339 }
340
341 pub fn into_output_section(self) -> Option<OutputSection<N>> {
347 match self {
348 Self::Output(s) => Some(s),
349 _ => None,
350 }
351 }
352
353 pub fn as_command_section(&self) -> Option<&CommandSection<N>> {
359 match self {
360 Self::Command(s) => Some(s),
361 _ => None,
362 }
363 }
364
365 pub fn into_command_section(self) -> Option<CommandSection<N>> {
371 match self {
372 Self::Command(s) => Some(s),
373 _ => None,
374 }
375 }
376
377 pub fn as_requirements_section(&self) -> Option<&RequirementsSection<N>> {
383 match self {
384 Self::Requirements(s) => Some(s),
385 _ => None,
386 }
387 }
388
389 pub fn into_requirements_section(self) -> Option<RequirementsSection<N>> {
396 match self {
397 Self::Requirements(s) => Some(s),
398 _ => None,
399 }
400 }
401
402 pub fn as_hints_section(&self) -> Option<&TaskHintsSection<N>> {
408 match self {
409 Self::Hints(s) => Some(s),
410 _ => None,
411 }
412 }
413
414 pub fn into_hints_section(self) -> Option<TaskHintsSection<N>> {
420 match self {
421 Self::Hints(s) => Some(s),
422 _ => None,
423 }
424 }
425
426 pub fn as_runtime_section(&self) -> Option<&RuntimeSection<N>> {
432 match self {
433 Self::Runtime(s) => Some(s),
434 _ => None,
435 }
436 }
437
438 pub fn into_runtime_section(self) -> Option<RuntimeSection<N>> {
444 match self {
445 Self::Runtime(s) => Some(s),
446 _ => None,
447 }
448 }
449
450 pub fn as_metadata_section(&self) -> Option<&MetadataSection<N>> {
456 match self {
457 Self::Metadata(s) => Some(s),
458 _ => None,
459 }
460 }
461
462 pub fn into_metadata_section(self) -> Option<MetadataSection<N>> {
468 match self {
469 Self::Metadata(s) => Some(s),
470 _ => None,
471 }
472 }
473
474 pub fn as_parameter_metadata_section(&self) -> Option<&ParameterMetadataSection<N>> {
481 match self {
482 Self::ParameterMetadata(s) => Some(s),
483 _ => None,
484 }
485 }
486
487 pub fn into_parameter_metadata_section(self) -> Option<ParameterMetadataSection<N>> {
494 match self {
495 Self::ParameterMetadata(s) => Some(s),
496 _ => None,
497 }
498 }
499
500 pub fn as_declaration(&self) -> Option<&BoundDecl<N>> {
506 match self {
507 Self::Declaration(d) => Some(d),
508 _ => None,
509 }
510 }
511
512 pub fn into_declaration(self) -> Option<BoundDecl<N>> {
518 match self {
519 Self::Declaration(d) => Some(d),
520 _ => None,
521 }
522 }
523
524 pub fn child(node: &N) -> Option<Self> {
526 node.children().find_map(Self::cast)
527 }
528
529 pub fn children(node: &N) -> impl Iterator<Item = Self> + use<'_, N> {
531 node.children().filter_map(Self::cast)
532 }
533}
534
535#[derive(Clone, Debug, PartialEq, Eq)]
537pub enum SectionParent<N: TreeNode = SyntaxNode> {
538 Task(TaskDefinition<N>),
540 Workflow(WorkflowDefinition<N>),
542 Struct(StructDefinition<N>),
544}
545
546impl<N: TreeNode> SectionParent<N> {
547 pub fn can_cast(kind: SyntaxKind) -> bool {
550 matches!(
551 kind,
552 SyntaxKind::TaskDefinitionNode
553 | SyntaxKind::WorkflowDefinitionNode
554 | SyntaxKind::StructDefinitionNode
555 )
556 }
557
558 pub fn cast(inner: N) -> Option<Self> {
562 match inner.kind() {
563 SyntaxKind::TaskDefinitionNode => Some(Self::Task(
564 TaskDefinition::cast(inner).expect("task definition to cast"),
565 )),
566 SyntaxKind::WorkflowDefinitionNode => Some(Self::Workflow(
567 WorkflowDefinition::cast(inner).expect("workflow definition to cast"),
568 )),
569 SyntaxKind::StructDefinitionNode => Some(Self::Struct(
570 StructDefinition::cast(inner).expect("struct definition to cast"),
571 )),
572 _ => None,
573 }
574 }
575
576 pub fn inner(&self) -> &N {
578 match self {
579 Self::Task(element) => element.inner(),
580 Self::Workflow(element) => element.inner(),
581 Self::Struct(element) => element.inner(),
582 }
583 }
584
585 pub fn name(&self) -> Ident<N::Token> {
587 match self {
588 Self::Task(t) => t.name(),
589 Self::Workflow(w) => w.name(),
590 Self::Struct(s) => s.name(),
591 }
592 }
593
594 pub fn as_task(&self) -> Option<&TaskDefinition<N>> {
600 match self {
601 Self::Task(task) => Some(task),
602 _ => None,
603 }
604 }
605
606 pub fn into_task(self) -> Option<TaskDefinition<N>> {
612 match self {
613 Self::Task(task) => Some(task),
614 _ => None,
615 }
616 }
617
618 pub fn unwrap_task(self) -> TaskDefinition<N> {
624 match self {
625 Self::Task(task) => task,
626 _ => panic!("not a task definition"),
627 }
628 }
629
630 pub fn as_workflow(&self) -> Option<&WorkflowDefinition<N>> {
636 match self {
637 Self::Workflow(workflow) => Some(workflow),
638 _ => None,
639 }
640 }
641
642 pub fn into_workflow(self) -> Option<WorkflowDefinition<N>> {
648 match self {
649 Self::Workflow(workflow) => Some(workflow),
650 _ => None,
651 }
652 }
653
654 pub fn unwrap_workflow(self) -> WorkflowDefinition<N> {
660 match self {
661 Self::Workflow(workflow) => workflow,
662 _ => panic!("not a workflow definition"),
663 }
664 }
665
666 pub fn as_struct(&self) -> Option<&StructDefinition<N>> {
672 match self {
673 Self::Struct(r#struct) => Some(r#struct),
674 _ => None,
675 }
676 }
677
678 pub fn into_struct(self) -> Option<StructDefinition<N>> {
684 match self {
685 Self::Struct(r#struct) => Some(r#struct),
686 _ => None,
687 }
688 }
689
690 pub fn unwrap_struct(self) -> StructDefinition<N> {
696 match self {
697 Self::Struct(def) => def,
698 _ => panic!("not a struct definition"),
699 }
700 }
701
702 pub fn child(node: &N) -> Option<Self> {
704 node.children().find_map(Self::cast)
705 }
706
707 pub fn children(node: &N) -> impl Iterator<Item = Self> + use<'_, N> {
709 node.children().filter_map(Self::cast)
710 }
711}
712
713#[derive(Clone, Debug, PartialEq, Eq)]
715pub struct InputSection<N: TreeNode = SyntaxNode>(N);
716
717impl<N: TreeNode> InputSection<N> {
718 pub fn declarations(&self) -> impl Iterator<Item = Decl<N>> + use<'_, N> {
720 Decl::children(&self.0)
721 }
722
723 pub fn parent(&self) -> SectionParent<N> {
725 SectionParent::cast(self.0.parent().expect("should have a parent"))
726 .expect("parent should cast")
727 }
728}
729
730impl<N: TreeNode> AstNode<N> for InputSection<N> {
731 fn can_cast(kind: SyntaxKind) -> bool {
732 kind == SyntaxKind::InputSectionNode
733 }
734
735 fn cast(inner: N) -> Option<Self> {
736 match inner.kind() {
737 SyntaxKind::InputSectionNode => Some(Self(inner)),
738 _ => None,
739 }
740 }
741
742 fn inner(&self) -> &N {
743 &self.0
744 }
745}
746
747#[derive(Clone, Debug, PartialEq, Eq)]
749pub struct OutputSection<N: TreeNode = SyntaxNode>(N);
750
751impl<N: TreeNode> OutputSection<N> {
752 pub fn declarations(&self) -> impl Iterator<Item = BoundDecl<N>> + use<'_, N> {
754 self.children()
755 }
756
757 pub fn parent(&self) -> SectionParent<N> {
759 SectionParent::cast(self.0.parent().expect("should have a parent"))
760 .expect("parent should cast")
761 }
762}
763
764impl<N: TreeNode> AstNode<N> for OutputSection<N> {
765 fn can_cast(kind: SyntaxKind) -> bool {
766 kind == SyntaxKind::OutputSectionNode
767 }
768
769 fn cast(inner: N) -> Option<Self> {
770 match inner.kind() {
771 SyntaxKind::OutputSectionNode => Some(Self(inner)),
772 _ => None,
773 }
774 }
775
776 fn inner(&self) -> &N {
777 &self.0
778 }
779}
780
781#[derive(Clone, Debug, PartialEq, Eq)]
785pub enum StrippedCommandPart<N: TreeNode = SyntaxNode> {
786 Text(String),
788 Placeholder(Placeholder<N>),
790}
791
792#[derive(Clone, Debug, PartialEq, Eq)]
794pub struct CommandSection<N: TreeNode = SyntaxNode>(N);
795
796impl<N: TreeNode> CommandSection<N> {
797 pub fn is_heredoc(&self) -> bool {
799 self.token::<OpenHeredoc<N::Token>>().is_some()
800 }
801
802 pub fn parts(&self) -> impl Iterator<Item = CommandPart<N>> + use<'_, N> {
804 self.0.children_with_tokens().filter_map(CommandPart::cast)
805 }
806
807 pub fn text(&self) -> Option<CommandText<N::Token>> {
813 let mut parts = self.parts();
814 if let Some(CommandPart::Text(text)) = parts.next() {
815 if parts.next().is_none() {
816 return Some(text);
817 }
818 }
819
820 None
821 }
822
823 pub fn count_whitespace(&self) -> Option<usize> {
827 let mut min_leading_spaces = usize::MAX;
828 let mut min_leading_tabs = usize::MAX;
829 let mut parsing_leading_whitespace = false; let mut leading_spaces = 0;
832 let mut leading_tabs = 0;
833 for part in self.parts() {
834 match part {
835 CommandPart::Text(text) => {
836 for c in text.text().chars() {
837 match c {
838 ' ' if parsing_leading_whitespace => {
839 leading_spaces += 1;
840 }
841 '\t' if parsing_leading_whitespace => {
842 leading_tabs += 1;
843 }
844 '\n' => {
845 parsing_leading_whitespace = true;
846 leading_spaces = 0;
847 leading_tabs = 0;
848 }
849 '\r' => {}
850 _ => {
851 if parsing_leading_whitespace {
852 parsing_leading_whitespace = false;
853 if leading_spaces == 0 && leading_tabs == 0 {
854 min_leading_spaces = 0;
855 min_leading_tabs = 0;
856 continue;
857 }
858 if leading_spaces < min_leading_spaces && leading_spaces > 0 {
859 min_leading_spaces = leading_spaces;
860 }
861 if leading_tabs < min_leading_tabs && leading_tabs > 0 {
862 min_leading_tabs = leading_tabs;
863 }
864 }
865 }
866 }
867 }
868 }
870 CommandPart::Placeholder(_) => {
871 if parsing_leading_whitespace {
872 parsing_leading_whitespace = false;
873 if leading_spaces == 0 && leading_tabs == 0 {
874 min_leading_spaces = 0;
875 min_leading_tabs = 0;
876 continue;
877 }
878 if leading_spaces < min_leading_spaces && leading_spaces > 0 {
879 min_leading_spaces = leading_spaces;
880 }
881 if leading_tabs < min_leading_tabs && leading_tabs > 0 {
882 min_leading_tabs = leading_tabs;
883 }
884 }
885 }
886 }
887 }
888
889 if (min_leading_spaces == 0 && min_leading_tabs == 0)
891 || (min_leading_spaces == usize::MAX && min_leading_tabs == usize::MAX)
892 {
893 return Some(0);
894 }
895
896 if (min_leading_spaces > 0 && min_leading_spaces != usize::MAX)
898 && (min_leading_tabs > 0 && min_leading_tabs != usize::MAX)
899 {
900 return None;
901 }
902
903 let final_leading_whitespace = if min_leading_spaces < min_leading_tabs {
906 min_leading_spaces
907 } else {
908 min_leading_tabs
909 };
910
911 Some(final_leading_whitespace)
912 }
913
914 pub fn strip_whitespace(&self) -> Option<Vec<StrippedCommandPart<N>>> {
918 let mut result = Vec::new();
919 let heredoc = self.is_heredoc();
920 for part in self.parts() {
921 match part {
922 CommandPart::Text(text) => {
923 let mut s = String::new();
924 unescape_command_text(text.text(), heredoc, &mut s);
925 result.push(StrippedCommandPart::Text(s));
926 }
927 CommandPart::Placeholder(p) => {
928 result.push(StrippedCommandPart::Placeholder(p));
929 }
930 }
931 }
932
933 let mut whole_first_line_trimmed = false;
935 if let Some(StrippedCommandPart::Text(text)) = result.first_mut() {
936 let end_of_first_line = text.find('\n').map(|p| p + 1).unwrap_or(text.len());
937 let line = &text[..end_of_first_line];
938 let len = line.len() - line.trim_start().len();
939 whole_first_line_trimmed = len == line.len();
940 text.replace_range(..len, "");
941 }
942
943 if let Some(StrippedCommandPart::Text(text)) = result.last_mut() {
945 if let Some(index) = text.rfind(|c| !matches!(c, ' ' | '\t')) {
946 text.truncate(index + 1);
947 } else {
948 text.clear();
949 }
950
951 if text.ends_with('\n') {
952 text.pop();
953 }
954
955 if text.ends_with('\r') {
956 text.pop();
957 }
958 }
959
960 let num_stripped_chars = self.count_whitespace()?;
962
963 if num_stripped_chars == 0 {
965 return Some(result);
966 }
967
968 let mut strip_leading_whitespace = whole_first_line_trimmed;
972 for part in &mut result {
973 match part {
974 StrippedCommandPart::Text(text) => {
975 let mut offset = 0;
976 while let Some(next) = text[offset..].find('\n') {
977 let next = next + offset;
978 if offset > 0 {
979 strip_leading_whitespace = true;
980 }
981
982 if !strip_leading_whitespace {
983 offset = next + 1;
984 continue;
985 }
986
987 let line = &text[offset..next];
988 let line = line.strip_suffix('\r').unwrap_or(line);
989 let len = line.len().min(num_stripped_chars);
990 text.replace_range(offset..offset + len, "");
991 offset = next + 1 - len;
992 }
993
994 if strip_leading_whitespace || offset > 0 {
996 let line = &text[offset..];
997 let line = line.strip_suffix('\r').unwrap_or(line);
998 let len = line.len().min(num_stripped_chars);
999 text.replace_range(offset..offset + len, "");
1000 }
1001 }
1002 StrippedCommandPart::Placeholder(_) => {
1003 strip_leading_whitespace = false;
1004 }
1005 }
1006 }
1007
1008 Some(result)
1009 }
1010
1011 pub fn parent(&self) -> SectionParent<N> {
1013 SectionParent::cast(self.0.parent().expect("should have a parent"))
1014 .expect("parent should cast")
1015 }
1016}
1017
1018impl<N: TreeNode> AstNode<N> for CommandSection<N> {
1019 fn can_cast(kind: SyntaxKind) -> bool {
1020 kind == SyntaxKind::CommandSectionNode
1021 }
1022
1023 fn cast(inner: N) -> Option<Self> {
1024 match inner.kind() {
1025 SyntaxKind::CommandSectionNode => Some(Self(inner)),
1026 _ => None,
1027 }
1028 }
1029
1030 fn inner(&self) -> &N {
1031 &self.0
1032 }
1033}
1034
1035#[derive(Clone, Debug, PartialEq, Eq)]
1037pub struct CommandText<T: TreeToken = SyntaxToken>(T);
1038
1039impl<T: TreeToken> CommandText<T> {
1040 pub fn unescape_to(&self, heredoc: bool, buffer: &mut String) {
1046 unescape_command_text(self.text(), heredoc, buffer);
1047 }
1048}
1049
1050impl<T: TreeToken> AstToken<T> for CommandText<T> {
1051 fn can_cast(kind: SyntaxKind) -> bool {
1052 kind == SyntaxKind::LiteralCommandText
1053 }
1054
1055 fn cast(inner: T) -> Option<Self> {
1056 match inner.kind() {
1057 SyntaxKind::LiteralCommandText => Some(Self(inner)),
1058 _ => None,
1059 }
1060 }
1061
1062 fn inner(&self) -> &T {
1063 &self.0
1064 }
1065}
1066
1067#[derive(Clone, Debug, PartialEq, Eq)]
1069pub enum CommandPart<N: TreeNode = SyntaxNode> {
1070 Text(CommandText<N::Token>),
1072 Placeholder(Placeholder<N>),
1074}
1075
1076impl<N: TreeNode> CommandPart<N> {
1077 pub fn unwrap_text(self) -> CommandText<N::Token> {
1083 match self {
1084 Self::Text(text) => text,
1085 _ => panic!("not string text"),
1086 }
1087 }
1088
1089 pub fn unwrap_placeholder(self) -> Placeholder<N> {
1095 match self {
1096 Self::Placeholder(p) => p,
1097 _ => panic!("not a placeholder"),
1098 }
1099 }
1100
1101 fn cast(element: NodeOrToken<N, N::Token>) -> Option<Self> {
1105 match element {
1106 NodeOrToken::Node(n) => Some(Self::Placeholder(Placeholder::cast(n)?)),
1107 NodeOrToken::Token(t) => Some(Self::Text(CommandText::cast(t)?)),
1108 }
1109 }
1110}
1111
1112#[derive(Clone, Debug, PartialEq, Eq)]
1114pub struct RequirementsSection<N: TreeNode = SyntaxNode>(N);
1115
1116impl<N: TreeNode> RequirementsSection<N> {
1117 pub fn items(&self) -> impl Iterator<Item = RequirementsItem<N>> + use<'_, N> {
1119 self.children()
1120 }
1121
1122 pub fn parent(&self) -> SectionParent<N> {
1124 SectionParent::cast(self.0.parent().expect("should have a parent"))
1125 .expect("parent should cast")
1126 }
1127
1128 pub fn container(&self) -> Option<requirements::item::Container<N>> {
1131 self.child()
1134 }
1135}
1136
1137impl<N: TreeNode> AstNode<N> for RequirementsSection<N> {
1138 fn can_cast(kind: SyntaxKind) -> bool {
1139 kind == SyntaxKind::RequirementsSectionNode
1140 }
1141
1142 fn cast(inner: N) -> Option<Self> {
1143 match inner.kind() {
1144 SyntaxKind::RequirementsSectionNode => Some(Self(inner)),
1145 _ => None,
1146 }
1147 }
1148
1149 fn inner(&self) -> &N {
1150 &self.0
1151 }
1152}
1153
1154#[derive(Clone, Debug, PartialEq, Eq)]
1156pub struct RequirementsItem<N: TreeNode = SyntaxNode>(N);
1157
1158impl<N: TreeNode> RequirementsItem<N> {
1159 pub fn name(&self) -> Ident<N::Token> {
1161 self.token().expect("expected an item name")
1162 }
1163
1164 pub fn expr(&self) -> Expr<N> {
1166 Expr::child(&self.0).expect("expected an item expression")
1167 }
1168
1169 pub fn into_container(self) -> Option<requirements::item::Container<N>> {
1172 requirements::item::Container::try_from(self).ok()
1173 }
1174}
1175
1176impl<N: TreeNode> AstNode<N> for RequirementsItem<N> {
1177 fn can_cast(kind: SyntaxKind) -> bool {
1178 kind == SyntaxKind::RequirementsItemNode
1179 }
1180
1181 fn cast(inner: N) -> Option<Self> {
1182 match inner.kind() {
1183 SyntaxKind::RequirementsItemNode => Some(Self(inner)),
1184 _ => None,
1185 }
1186 }
1187
1188 fn inner(&self) -> &N {
1189 &self.0
1190 }
1191}
1192
1193#[derive(Clone, Debug, PartialEq, Eq)]
1195pub struct TaskHintsSection<N: TreeNode = SyntaxNode>(N);
1196
1197impl<N: TreeNode> TaskHintsSection<N> {
1198 pub fn items(&self) -> impl Iterator<Item = TaskHintsItem<N>> + use<'_, N> {
1200 self.children()
1201 }
1202
1203 pub fn parent(&self) -> TaskDefinition<N> {
1205 TaskDefinition::cast(self.0.parent().expect("should have a parent"))
1206 .expect("parent should cast")
1207 }
1208}
1209
1210impl<N: TreeNode> AstNode<N> for TaskHintsSection<N> {
1211 fn can_cast(kind: SyntaxKind) -> bool {
1212 kind == SyntaxKind::TaskHintsSectionNode
1213 }
1214
1215 fn cast(inner: N) -> Option<Self> {
1216 match inner.kind() {
1217 SyntaxKind::TaskHintsSectionNode => Some(Self(inner)),
1218 _ => None,
1219 }
1220 }
1221
1222 fn inner(&self) -> &N {
1223 &self.0
1224 }
1225}
1226
1227#[derive(Clone, Debug, PartialEq, Eq)]
1229pub struct TaskHintsItem<N: TreeNode = SyntaxNode>(N);
1230
1231impl<N: TreeNode> TaskHintsItem<N> {
1232 pub fn name(&self) -> Ident<N::Token> {
1234 self.token().expect("expected an item name")
1235 }
1236
1237 pub fn expr(&self) -> Expr<N> {
1239 Expr::child(&self.0).expect("expected an item expression")
1240 }
1241}
1242
1243impl<N: TreeNode> AstNode<N> for TaskHintsItem<N> {
1244 fn can_cast(kind: SyntaxKind) -> bool {
1245 kind == SyntaxKind::TaskHintsItemNode
1246 }
1247
1248 fn cast(inner: N) -> Option<Self> {
1249 match inner.kind() {
1250 SyntaxKind::TaskHintsItemNode => Some(Self(inner)),
1251 _ => None,
1252 }
1253 }
1254
1255 fn inner(&self) -> &N {
1256 &self.0
1257 }
1258}
1259
1260#[derive(Clone, Debug, PartialEq, Eq)]
1262pub struct RuntimeSection<N: TreeNode = SyntaxNode>(N);
1263
1264impl<N: TreeNode> RuntimeSection<N> {
1265 pub fn items(&self) -> impl Iterator<Item = RuntimeItem<N>> + use<'_, N> {
1267 self.children()
1268 }
1269
1270 pub fn parent(&self) -> SectionParent<N> {
1272 SectionParent::cast(self.0.parent().expect("should have a parent"))
1273 .expect("parent should cast")
1274 }
1275
1276 pub fn container(&self) -> Option<runtime::item::Container<N>> {
1279 self.child()
1282 }
1283}
1284
1285impl<N: TreeNode> AstNode<N> for RuntimeSection<N> {
1286 fn can_cast(kind: SyntaxKind) -> bool {
1287 kind == SyntaxKind::RuntimeSectionNode
1288 }
1289
1290 fn cast(inner: N) -> Option<Self> {
1291 match inner.kind() {
1292 SyntaxKind::RuntimeSectionNode => Some(Self(inner)),
1293 _ => None,
1294 }
1295 }
1296
1297 fn inner(&self) -> &N {
1298 &self.0
1299 }
1300}
1301
1302#[derive(Clone, Debug, PartialEq, Eq)]
1304pub struct RuntimeItem<N: TreeNode = SyntaxNode>(N);
1305
1306impl<N: TreeNode> RuntimeItem<N> {
1307 pub fn name(&self) -> Ident<N::Token> {
1309 self.token().expect("expected an item name")
1310 }
1311
1312 pub fn expr(&self) -> Expr<N> {
1314 Expr::child(&self.0).expect("expected an item expression")
1315 }
1316
1317 pub fn into_container(self) -> Option<runtime::item::Container<N>> {
1320 runtime::item::Container::try_from(self).ok()
1321 }
1322}
1323
1324impl<N: TreeNode> AstNode<N> for RuntimeItem<N> {
1325 fn can_cast(kind: SyntaxKind) -> bool {
1326 kind == SyntaxKind::RuntimeItemNode
1327 }
1328
1329 fn cast(inner: N) -> Option<Self> {
1330 match inner.kind() {
1331 SyntaxKind::RuntimeItemNode => Some(Self(inner)),
1332 _ => None,
1333 }
1334 }
1335
1336 fn inner(&self) -> &N {
1337 &self.0
1338 }
1339}
1340
1341#[derive(Clone, Debug, PartialEq, Eq)]
1343pub struct MetadataSection<N: TreeNode = SyntaxNode>(N);
1344
1345impl<N: TreeNode> MetadataSection<N> {
1346 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1348 self.children()
1349 }
1350
1351 pub fn parent(&self) -> SectionParent<N> {
1353 SectionParent::cast(self.0.parent().expect("should have a parent"))
1354 .expect("parent should cast")
1355 }
1356}
1357
1358impl<N: TreeNode> AstNode<N> for MetadataSection<N> {
1359 fn can_cast(kind: SyntaxKind) -> bool {
1360 kind == SyntaxKind::MetadataSectionNode
1361 }
1362
1363 fn cast(inner: N) -> Option<Self> {
1364 match inner.kind() {
1365 SyntaxKind::MetadataSectionNode => Some(Self(inner)),
1366 _ => None,
1367 }
1368 }
1369
1370 fn inner(&self) -> &N {
1371 &self.0
1372 }
1373}
1374
1375#[derive(Clone, Debug, PartialEq, Eq)]
1377pub struct MetadataObjectItem<N: TreeNode = SyntaxNode>(N);
1378
1379impl<N: TreeNode> MetadataObjectItem<N> {
1380 pub fn name(&self) -> Ident<N::Token> {
1382 self.token().expect("expected a name")
1383 }
1384
1385 pub fn value(&self) -> MetadataValue<N> {
1387 self.child().expect("expected a value")
1388 }
1389}
1390
1391impl<N: TreeNode> AstNode<N> for MetadataObjectItem<N> {
1392 fn can_cast(kind: SyntaxKind) -> bool {
1393 kind == SyntaxKind::MetadataObjectItemNode
1394 }
1395
1396 fn cast(inner: N) -> Option<Self> {
1397 match inner.kind() {
1398 SyntaxKind::MetadataObjectItemNode => Some(Self(inner)),
1399 _ => None,
1400 }
1401 }
1402
1403 fn inner(&self) -> &N {
1404 &self.0
1405 }
1406}
1407
1408#[derive(Clone, Debug, PartialEq, Eq)]
1410pub enum MetadataValue<N: TreeNode = SyntaxNode> {
1411 Boolean(LiteralBoolean<N>),
1413 Integer(LiteralInteger<N>),
1415 Float(LiteralFloat<N>),
1417 String(LiteralString<N>),
1419 Null(LiteralNull<N>),
1421 Object(MetadataObject<N>),
1423 Array(MetadataArray<N>),
1425}
1426
1427impl<N: TreeNode> MetadataValue<N> {
1428 pub fn unwrap_boolean(self) -> LiteralBoolean<N> {
1434 match self {
1435 Self::Boolean(b) => b,
1436 _ => panic!("not a boolean"),
1437 }
1438 }
1439
1440 pub fn unwrap_integer(self) -> LiteralInteger<N> {
1446 match self {
1447 Self::Integer(i) => i,
1448 _ => panic!("not an integer"),
1449 }
1450 }
1451
1452 pub fn unwrap_float(self) -> LiteralFloat<N> {
1458 match self {
1459 Self::Float(f) => f,
1460 _ => panic!("not a float"),
1461 }
1462 }
1463
1464 pub fn unwrap_string(self) -> LiteralString<N> {
1470 match self {
1471 Self::String(s) => s,
1472 _ => panic!("not a string"),
1473 }
1474 }
1475
1476 pub fn unwrap_null(self) -> LiteralNull<N> {
1482 match self {
1483 Self::Null(n) => n,
1484 _ => panic!("not a null"),
1485 }
1486 }
1487
1488 pub fn unwrap_object(self) -> MetadataObject<N> {
1494 match self {
1495 Self::Object(o) => o,
1496 _ => panic!("not an object"),
1497 }
1498 }
1499
1500 pub fn unwrap_array(self) -> MetadataArray<N> {
1506 match self {
1507 Self::Array(a) => a,
1508 _ => panic!("not an array"),
1509 }
1510 }
1511}
1512
1513impl<N: TreeNode> AstNode<N> for MetadataValue<N> {
1514 fn can_cast(kind: SyntaxKind) -> bool {
1515 matches!(
1516 kind,
1517 SyntaxKind::LiteralBooleanNode
1518 | SyntaxKind::LiteralIntegerNode
1519 | SyntaxKind::LiteralFloatNode
1520 | SyntaxKind::LiteralStringNode
1521 | SyntaxKind::LiteralNullNode
1522 | SyntaxKind::MetadataObjectNode
1523 | SyntaxKind::MetadataArrayNode
1524 )
1525 }
1526
1527 fn cast(inner: N) -> Option<Self> {
1528 match inner.kind() {
1529 SyntaxKind::LiteralBooleanNode => Some(Self::Boolean(LiteralBoolean(inner))),
1530 SyntaxKind::LiteralIntegerNode => Some(Self::Integer(LiteralInteger(inner))),
1531 SyntaxKind::LiteralFloatNode => Some(Self::Float(LiteralFloat(inner))),
1532 SyntaxKind::LiteralStringNode => Some(Self::String(LiteralString(inner))),
1533 SyntaxKind::LiteralNullNode => Some(Self::Null(LiteralNull(inner))),
1534 SyntaxKind::MetadataObjectNode => Some(Self::Object(MetadataObject(inner))),
1535 SyntaxKind::MetadataArrayNode => Some(Self::Array(MetadataArray(inner))),
1536 _ => None,
1537 }
1538 }
1539
1540 fn inner(&self) -> &N {
1541 match self {
1542 Self::Boolean(b) => &b.0,
1543 Self::Integer(i) => &i.0,
1544 Self::Float(f) => &f.0,
1545 Self::String(s) => &s.0,
1546 Self::Null(n) => &n.0,
1547 Self::Object(o) => &o.0,
1548 Self::Array(a) => &a.0,
1549 }
1550 }
1551}
1552
1553#[derive(Clone, Debug, PartialEq, Eq)]
1555pub struct LiteralNull<N: TreeNode = SyntaxNode>(N);
1556
1557impl<N: TreeNode> AstNode<N> for LiteralNull<N> {
1558 fn can_cast(kind: SyntaxKind) -> bool {
1559 kind == SyntaxKind::LiteralNullNode
1560 }
1561
1562 fn cast(inner: N) -> Option<Self> {
1563 match inner.kind() {
1564 SyntaxKind::LiteralNullNode => Some(Self(inner)),
1565 _ => None,
1566 }
1567 }
1568
1569 fn inner(&self) -> &N {
1570 &self.0
1571 }
1572}
1573
1574#[derive(Clone, Debug, PartialEq, Eq)]
1576pub struct MetadataObject<N: TreeNode = SyntaxNode>(N);
1577
1578impl<N: TreeNode> MetadataObject<N> {
1579 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1581 self.children()
1582 }
1583}
1584
1585impl<N: TreeNode> AstNode<N> for MetadataObject<N> {
1586 fn can_cast(kind: SyntaxKind) -> bool {
1587 kind == SyntaxKind::MetadataObjectNode
1588 }
1589
1590 fn cast(inner: N) -> Option<Self> {
1591 match inner.kind() {
1592 SyntaxKind::MetadataObjectNode => Some(Self(inner)),
1593 _ => None,
1594 }
1595 }
1596
1597 fn inner(&self) -> &N {
1598 &self.0
1599 }
1600}
1601
1602#[derive(Clone, Debug, PartialEq, Eq)]
1604pub struct MetadataArray<N: TreeNode = SyntaxNode>(N);
1605
1606impl<N: TreeNode> MetadataArray<N> {
1607 pub fn elements(&self) -> impl Iterator<Item = MetadataValue<N>> + use<'_, N> {
1609 self.children()
1610 }
1611}
1612
1613impl<N: TreeNode> AstNode<N> for MetadataArray<N> {
1614 fn can_cast(kind: SyntaxKind) -> bool {
1615 kind == SyntaxKind::MetadataArrayNode
1616 }
1617
1618 fn cast(inner: N) -> Option<Self> {
1619 match inner.kind() {
1620 SyntaxKind::MetadataArrayNode => Some(Self(inner)),
1621 _ => None,
1622 }
1623 }
1624
1625 fn inner(&self) -> &N {
1626 &self.0
1627 }
1628}
1629
1630#[derive(Clone, Debug, PartialEq, Eq)]
1632pub struct ParameterMetadataSection<N: TreeNode = SyntaxNode>(N);
1633
1634impl<N: TreeNode> ParameterMetadataSection<N> {
1635 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1637 self.children()
1638 }
1639
1640 pub fn parent(&self) -> SectionParent<N> {
1642 SectionParent::cast(self.0.parent().expect("should have a parent"))
1643 .expect("parent should cast")
1644 }
1645}
1646
1647impl<N: TreeNode> AstNode<N> for ParameterMetadataSection<N> {
1648 fn can_cast(kind: SyntaxKind) -> bool {
1649 kind == SyntaxKind::ParameterMetadataSectionNode
1650 }
1651
1652 fn cast(inner: N) -> Option<Self> {
1653 match inner.kind() {
1654 SyntaxKind::ParameterMetadataSectionNode => Some(Self(inner)),
1655 _ => None,
1656 }
1657 }
1658
1659 fn inner(&self) -> &N {
1660 &self.0
1661 }
1662}
1663
1664#[cfg(test)]
1665mod test {
1666 use pretty_assertions::assert_eq;
1667
1668 use super::*;
1669 use crate::Document;
1670
1671 #[test]
1672 fn tasks() {
1673 let (document, diagnostics) = Document::parse(
1674 r#"
1675version 1.2
1676
1677task test {
1678 input {
1679 String name
1680 }
1681
1682 output {
1683 String output = stdout()
1684 }
1685
1686 command <<<
1687 printf "hello, ~{name}!
1688 >>>
1689
1690 requirements {
1691 container: "baz/qux"
1692 }
1693
1694 hints {
1695 foo: "bar"
1696 }
1697
1698 runtime {
1699 container: "foo/bar"
1700 }
1701
1702 meta {
1703 description: "a test"
1704 foo: null
1705 }
1706
1707 parameter_meta {
1708 name: {
1709 help: "a name to greet"
1710 }
1711 }
1712
1713 String x = "private"
1714}
1715"#,
1716 );
1717
1718 assert!(diagnostics.is_empty());
1719 let ast = document.ast();
1720 let ast = ast.as_v1().expect("should be a V1 AST");
1721 let tasks: Vec<_> = ast.tasks().collect();
1722 assert_eq!(tasks.len(), 1);
1723 assert_eq!(tasks[0].name().text(), "test");
1724
1725 let input = tasks[0].input().expect("should have an input section");
1727 assert_eq!(input.parent().unwrap_task().name().text(), "test");
1728 let decls: Vec<_> = input.declarations().collect();
1729 assert_eq!(decls.len(), 1);
1730 assert_eq!(
1731 decls[0].clone().unwrap_unbound_decl().ty().to_string(),
1732 "String"
1733 );
1734 assert_eq!(decls[0].clone().unwrap_unbound_decl().name().text(), "name");
1735
1736 let output = tasks[0].output().expect("should have an output section");
1738 assert_eq!(output.parent().unwrap_task().name().text(), "test");
1739 let decls: Vec<_> = output.declarations().collect();
1740 assert_eq!(decls.len(), 1);
1741 assert_eq!(decls[0].ty().to_string(), "String");
1742 assert_eq!(decls[0].name().text(), "output");
1743 assert_eq!(decls[0].expr().unwrap_call().target().text(), "stdout");
1744
1745 let command = tasks[0].command().expect("should have a command section");
1747 assert_eq!(command.parent().name().text(), "test");
1748 assert!(command.is_heredoc());
1749 let parts: Vec<_> = command.parts().collect();
1750 assert_eq!(parts.len(), 3);
1751 assert_eq!(
1752 parts[0].clone().unwrap_text().text(),
1753 "\n printf \"hello, "
1754 );
1755 assert_eq!(
1756 parts[1]
1757 .clone()
1758 .unwrap_placeholder()
1759 .expr()
1760 .unwrap_name_ref()
1761 .name()
1762 .text(),
1763 "name"
1764 );
1765 assert_eq!(parts[2].clone().unwrap_text().text(), "!\n ");
1766
1767 let requirements = tasks[0]
1769 .requirements()
1770 .expect("should have a requirements section");
1771 assert_eq!(requirements.parent().name().text(), "test");
1772 let items: Vec<_> = requirements.items().collect();
1773 assert_eq!(items.len(), 1);
1774 assert_eq!(items[0].name().text(), TASK_REQUIREMENT_CONTAINER);
1775 assert_eq!(
1776 items[0]
1777 .expr()
1778 .unwrap_literal()
1779 .unwrap_string()
1780 .text()
1781 .unwrap()
1782 .text(),
1783 "baz/qux"
1784 );
1785
1786 let hints = tasks[0].hints().expect("should have a hints section");
1788 assert_eq!(hints.parent().name().text(), "test");
1789 let items: Vec<_> = hints.items().collect();
1790 assert_eq!(items.len(), 1);
1791 assert_eq!(items[0].name().text(), "foo");
1792 assert_eq!(
1793 items[0]
1794 .expr()
1795 .unwrap_literal()
1796 .unwrap_string()
1797 .text()
1798 .unwrap()
1799 .text(),
1800 "bar"
1801 );
1802
1803 let runtime = tasks[0].runtime().expect("should have a runtime section");
1805 assert_eq!(runtime.parent().name().text(), "test");
1806 let items: Vec<_> = runtime.items().collect();
1807 assert_eq!(items.len(), 1);
1808 assert_eq!(items[0].name().text(), TASK_REQUIREMENT_CONTAINER);
1809 assert_eq!(
1810 items[0]
1811 .expr()
1812 .unwrap_literal()
1813 .unwrap_string()
1814 .text()
1815 .unwrap()
1816 .text(),
1817 "foo/bar"
1818 );
1819
1820 let metadata = tasks[0].metadata().expect("should have a metadata section");
1822 assert_eq!(metadata.parent().unwrap_task().name().text(), "test");
1823 let items: Vec<_> = metadata.items().collect();
1824 assert_eq!(items.len(), 2);
1825 assert_eq!(items[0].name().text(), "description");
1826 assert_eq!(
1827 items[0].value().unwrap_string().text().unwrap().text(),
1828 "a test"
1829 );
1830
1831 assert_eq!(items[1].name().text(), "foo");
1833 items[1].value().unwrap_null();
1834
1835 let param_meta = tasks[0]
1837 .parameter_metadata()
1838 .expect("should have a parameter metadata section");
1839 assert_eq!(param_meta.parent().unwrap_task().name().text(), "test");
1840 let items: Vec<_> = param_meta.items().collect();
1841 assert_eq!(items.len(), 1);
1842 assert_eq!(items[0].name().text(), "name");
1843 let items: Vec<_> = items[0].value().unwrap_object().items().collect();
1844 assert_eq!(items.len(), 1);
1845 assert_eq!(items[0].name().text(), "help");
1846 assert_eq!(
1847 items[0].value().unwrap_string().text().unwrap().text(),
1848 "a name to greet"
1849 );
1850
1851 let decls: Vec<_> = tasks[0].declarations().collect();
1853 assert_eq!(decls.len(), 1);
1854
1855 assert_eq!(decls[0].ty().to_string(), "String");
1857 assert_eq!(decls[0].name().text(), "x");
1858 assert_eq!(
1859 decls[0]
1860 .expr()
1861 .unwrap_literal()
1862 .unwrap_string()
1863 .text()
1864 .unwrap()
1865 .text(),
1866 "private"
1867 );
1868 }
1869
1870 #[test]
1871 fn whitespace_stripping_without_interpolation() {
1872 let (document, diagnostics) = Document::parse(
1873 r#"
1874version 1.2
1875
1876task test {
1877 command <<<
1878 echo "hello"
1879 echo "world"
1880 echo \
1881 "goodbye"
1882 >>>
1883}
1884"#,
1885 );
1886
1887 assert!(diagnostics.is_empty());
1888 let ast = document.ast();
1889 let ast = ast.as_v1().expect("should be a V1 AST");
1890 let tasks: Vec<_> = ast.tasks().collect();
1891 assert_eq!(tasks.len(), 1);
1892
1893 let command = tasks[0].command().expect("should have a command section");
1894
1895 let stripped = command.strip_whitespace().unwrap();
1896
1897 assert_eq!(stripped.len(), 1);
1898 let text = match &stripped[0] {
1899 StrippedCommandPart::Text(text) => text,
1900 _ => panic!("expected text"),
1901 };
1902 assert_eq!(
1903 text,
1904 "echo \"hello\"\necho \"world\"\necho \\\n \"goodbye\""
1905 );
1906 }
1907
1908 #[test]
1909 fn whitespace_stripping_with_interpolation() {
1910 let (document, diagnostics) = Document::parse(
1911 r#"
1912version 1.2
1913
1914task test {
1915 input {
1916 String name
1917 Boolean flag
1918 }
1919
1920 command <<<
1921 echo "hello, ~{
1922if flag
1923then name
1924 else "Jerry"
1925 }!"
1926 >>>
1927}
1928 "#,
1929 );
1930
1931 assert!(diagnostics.is_empty());
1932 let ast = document.ast();
1933 let ast = ast.as_v1().expect("should be a V1 AST");
1934 let tasks: Vec<_> = ast.tasks().collect();
1935 assert_eq!(tasks.len(), 1);
1936
1937 let command = tasks[0].command().expect("should have a command section");
1938
1939 let stripped = command.strip_whitespace().unwrap();
1940 assert_eq!(stripped.len(), 3);
1941 let text = match &stripped[0] {
1942 StrippedCommandPart::Text(text) => text,
1943 _ => panic!("expected text"),
1944 };
1945 assert_eq!(text, "echo \"hello, ");
1946
1947 let _placeholder = match &stripped[1] {
1948 StrippedCommandPart::Placeholder(p) => p,
1949 _ => panic!("expected placeholder"),
1950 };
1951 let text = match &stripped[2] {
1954 StrippedCommandPart::Text(text) => text,
1955 _ => panic!("expected text"),
1956 };
1957 assert_eq!(text, "!\"");
1958 }
1959
1960 #[test]
1961 fn whitespace_stripping_when_interpolation_starts_line() {
1962 let (document, diagnostics) = Document::parse(
1963 r#"
1964version 1.2
1965
1966task test {
1967 input {
1968 Int placeholder
1969 }
1970
1971 command <<<
1972 # other weird whitspace
1973 ~{placeholder} "$trailing_pholder" ~{placeholder}
1974 ~{placeholder} somecommand.py "$leading_pholder"
1975 >>>
1976}
1977"#,
1978 );
1979
1980 assert!(diagnostics.is_empty());
1981 let ast = document.ast();
1982 let ast = ast.as_v1().expect("should be a V1 AST");
1983 let tasks: Vec<_> = ast.tasks().collect();
1984 assert_eq!(tasks.len(), 1);
1985
1986 let command = tasks[0].command().expect("should have a command section");
1987
1988 let stripped = command.strip_whitespace().unwrap();
1989 assert_eq!(stripped.len(), 7);
1990 let text = match &stripped[0] {
1991 StrippedCommandPart::Text(text) => text,
1992 _ => panic!("expected text"),
1993 };
1994 assert_eq!(text, " # other weird whitspace\n");
1995
1996 let _placeholder = match &stripped[1] {
1997 StrippedCommandPart::Placeholder(p) => p,
1998 _ => panic!("expected placeholder"),
1999 };
2000 let text = match &stripped[2] {
2003 StrippedCommandPart::Text(text) => text,
2004 _ => panic!("expected text"),
2005 };
2006 assert_eq!(text, " \"$trailing_pholder\" ");
2007
2008 let _placeholder = match &stripped[3] {
2009 StrippedCommandPart::Placeholder(p) => p,
2010 _ => panic!("expected placeholder"),
2011 };
2012 let text = match &stripped[4] {
2015 StrippedCommandPart::Text(text) => text,
2016 _ => panic!("expected text"),
2017 };
2018 assert_eq!(text, "\n");
2019
2020 let _placeholder = match &stripped[5] {
2021 StrippedCommandPart::Placeholder(p) => p,
2022 _ => panic!("expected placeholder"),
2023 };
2024 let text = match &stripped[6] {
2027 StrippedCommandPart::Text(text) => text,
2028 _ => panic!("expected text"),
2029 };
2030 assert_eq!(text, " somecommand.py \"$leading_pholder\"");
2031 }
2032
2033 #[test]
2034 fn whitespace_stripping_when_command_is_empty() {
2035 let (document, diagnostics) = Document::parse(
2036 r#"
2037version 1.2
2038
2039task test {
2040 command <<<>>>
2041}
2042 "#,
2043 );
2044
2045 assert!(diagnostics.is_empty());
2046 let ast = document.ast();
2047 let ast = ast.as_v1().expect("should be a V1 AST");
2048 let tasks: Vec<_> = ast.tasks().collect();
2049 assert_eq!(tasks.len(), 1);
2050
2051 let command = tasks[0].command().expect("should have a command section");
2052
2053 let stripped = command.strip_whitespace().unwrap();
2054 assert_eq!(stripped.len(), 0);
2055 }
2056
2057 #[test]
2058 fn whitespace_stripping_when_command_is_one_line_of_whitespace() {
2059 let (document, diagnostics) = Document::parse(
2060 r#"
2061version 1.2
2062
2063task test {
2064 command <<< >>>
2065}
2066 "#,
2067 );
2068
2069 assert!(diagnostics.is_empty());
2070 let ast = document.ast();
2071 let ast = ast.as_v1().expect("should be a V1 AST");
2072 let tasks: Vec<_> = ast.tasks().collect();
2073 assert_eq!(tasks.len(), 1);
2074
2075 let command = tasks[0].command().expect("should have a command section");
2076
2077 let stripped = command.strip_whitespace().unwrap();
2078 assert_eq!(stripped.len(), 1);
2079 let text = match &stripped[0] {
2080 StrippedCommandPart::Text(text) => text,
2081 _ => panic!("expected text"),
2082 };
2083 assert_eq!(text, "");
2084 }
2085
2086 #[test]
2087 fn whitespace_stripping_when_command_is_one_newline() {
2088 let (document, diagnostics) = Document::parse(
2089 r#"
2090version 1.2
2091
2092task test {
2093 command <<<
2094 >>>
2095}
2096 "#,
2097 );
2098
2099 assert!(diagnostics.is_empty());
2100 let ast = document.ast();
2101 let ast = ast.as_v1().expect("should be a V1 AST");
2102 let tasks: Vec<_> = ast.tasks().collect();
2103 assert_eq!(tasks.len(), 1);
2104
2105 let command = tasks[0].command().expect("should have a command section");
2106
2107 let stripped = command.strip_whitespace().unwrap();
2108 assert_eq!(stripped.len(), 1);
2109 let text = match &stripped[0] {
2110 StrippedCommandPart::Text(text) => text,
2111 _ => panic!("expected text"),
2112 };
2113 assert_eq!(text, "");
2114 }
2115
2116 #[test]
2117 fn whitespace_stripping_when_command_is_a_blank_line() {
2118 let (document, diagnostics) = Document::parse(
2119 r#"
2120version 1.2
2121
2122task test {
2123 command <<<
2124
2125 >>>
2126}
2127 "#,
2128 );
2129
2130 assert!(diagnostics.is_empty());
2131 let ast = document.ast();
2132 let ast = ast.as_v1().expect("should be a V1 AST");
2133 let tasks: Vec<_> = ast.tasks().collect();
2134 assert_eq!(tasks.len(), 1);
2135
2136 let command = tasks[0].command().expect("should have a command section");
2137
2138 let stripped = command.strip_whitespace().unwrap();
2139 assert_eq!(stripped.len(), 1);
2140 let text = match &stripped[0] {
2141 StrippedCommandPart::Text(text) => text,
2142 _ => panic!("expected text"),
2143 };
2144 assert_eq!(text, "");
2145 }
2146
2147 #[test]
2148 fn whitespace_stripping_when_command_is_a_blank_line_with_spaces() {
2149 let (document, diagnostics) = Document::parse(
2150 r#"
2151version 1.2
2152
2153task test {
2154 command <<<
2155
2156 >>>
2157}
2158 "#,
2159 );
2160
2161 assert!(diagnostics.is_empty());
2162 let ast = document.ast();
2163 let ast = ast.as_v1().expect("should be a V1 AST");
2164 let tasks: Vec<_> = ast.tasks().collect();
2165 assert_eq!(tasks.len(), 1);
2166
2167 let command = tasks[0].command().expect("should have a command section");
2168
2169 let stripped = command.strip_whitespace().unwrap();
2170 assert_eq!(stripped.len(), 1);
2171 let text = match &stripped[0] {
2172 StrippedCommandPart::Text(text) => text,
2173 _ => panic!("expected text"),
2174 };
2175 assert_eq!(text, " ");
2176 }
2177
2178 #[test]
2179 fn whitespace_stripping_with_mixed_indentation() {
2180 let (document, diagnostics) = Document::parse(
2181 r#"
2182version 1.2
2183
2184task test {
2185 command <<<
2186 echo "hello"
2187 echo "world"
2188 echo \
2189 "goodbye"
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();
2203 assert!(stripped.is_none());
2204 }
2205
2206 #[test]
2207 fn whitespace_stripping_with_funky_indentation() {
2208 let (document, diagnostics) = Document::parse(
2209 r#"
2210version 1.2
2211
2212task test {
2213 command <<<
2214 echo "hello"
2215 echo "world"
2216 echo \
2217 "goodbye"
2218 >>>
2219 }"#,
2220 );
2221
2222 assert!(diagnostics.is_empty());
2223 let ast = document.ast();
2224 let ast = ast.as_v1().expect("should be a V1 AST");
2225 let tasks: Vec<_> = ast.tasks().collect();
2226 assert_eq!(tasks.len(), 1);
2227
2228 let command = tasks[0].command().expect("should have a command section");
2229
2230 let stripped = command.strip_whitespace().unwrap();
2231 assert_eq!(stripped.len(), 1);
2232 let text = match &stripped[0] {
2233 StrippedCommandPart::Text(text) => text,
2234 _ => panic!("expected text"),
2235 };
2236 assert_eq!(
2237 text,
2238 "echo \"hello\"\n echo \"world\"\necho \\\n \"goodbye\""
2239 );
2240 }
2241
2242 #[test]
2244 fn whitespace_stripping_with_content_on_first_line() {
2245 let (document, diagnostics) = Document::parse(
2246 r#"
2247version 1.2
2248
2249task test {
2250 command <<< weird stuff $firstlinelint
2251 # other weird whitespace
2252 somecommand.py $line120 ~{placeholder}
2253 >>>
2254 }"#,
2255 );
2256
2257 assert!(diagnostics.is_empty());
2258 let ast = document.ast();
2259 let ast = ast.as_v1().expect("should be a V1 AST");
2260 let tasks: Vec<_> = ast.tasks().collect();
2261 assert_eq!(tasks.len(), 1);
2262
2263 let command = tasks[0].command().expect("should have a command section");
2264
2265 let stripped = command.strip_whitespace().unwrap();
2266 assert_eq!(stripped.len(), 3);
2267 let text = match &stripped[0] {
2268 StrippedCommandPart::Text(text) => text,
2269 _ => panic!("expected text"),
2270 };
2271 assert_eq!(
2272 text,
2273 "weird stuff $firstlinelint\n # other weird whitespace\nsomecommand.py $line120 "
2274 );
2275
2276 let _placeholder = match &stripped[1] {
2277 StrippedCommandPart::Placeholder(p) => p,
2278 _ => panic!("expected placeholder"),
2279 };
2280 let text = match &stripped[2] {
2283 StrippedCommandPart::Text(text) => text,
2284 _ => panic!("expected text"),
2285 };
2286 assert_eq!(text, "");
2287 }
2288
2289 #[test]
2290 fn whitespace_stripping_on_windows() {
2291 let (document, diagnostics) = Document::parse(
2292 "version 1.2\r\ntask test {\r\n command <<<\r\n echo \"hello\"\r\n \
2293 >>>\r\n}\r\n",
2294 );
2295
2296 assert!(diagnostics.is_empty());
2297 let ast = document.ast();
2298 let ast = ast.as_v1().expect("should be a V1 AST");
2299 let tasks: Vec<_> = ast.tasks().collect();
2300 assert_eq!(tasks.len(), 1);
2301
2302 let command = tasks[0].command().expect("should have a command section");
2303 let stripped = command.strip_whitespace().unwrap();
2304 assert_eq!(stripped.len(), 1);
2305 let text = match &stripped[0] {
2306 StrippedCommandPart::Text(text) => text,
2307 _ => panic!("expected text"),
2308 };
2309 assert_eq!(text, "echo \"hello\"");
2310 }
2311}