Rust · 5850 bytes Raw Blame History
1 //! Heredoc content collection
2 //!
3 //! After parsing a command line, if it contains heredoc markers (<<EOF),
4 //! we need to collect subsequent lines until we find the delimiter.
5
6 use rush_parser::ast::{CommandType, CompleteCommand, Redirect, SimpleCommand, Statement};
7
8 /// Check if a statement contains any heredoc redirects that need content
9 pub fn has_heredocs(statement: &Statement) -> bool {
10 match statement {
11 Statement::Empty => false,
12 Statement::Complete(cmd) => command_has_heredocs(cmd),
13 Statement::Script(commands) => commands.iter().any(command_has_heredocs),
14 }
15 }
16
17 fn command_has_heredocs(cmd: &CompleteCommand) -> bool {
18 match &cmd.command {
19 CommandType::Simple(simple_cmd) => simple_command_has_heredocs(simple_cmd),
20 CommandType::Pipeline(pipeline) => {
21 pipeline.commands.iter().any(|elem| {
22 if let rush_parser::ast::PipelineElement::Simple(cmd) = elem {
23 simple_command_has_heredocs(cmd)
24 } else {
25 false // Subshells don't have heredocs at the pipeline level
26 }
27 })
28 }
29 CommandType::AndOrList(list) => {
30 list.first.commands.iter().any(|elem| {
31 if let rush_parser::ast::PipelineElement::Simple(cmd) = elem {
32 simple_command_has_heredocs(cmd)
33 } else {
34 false
35 }
36 }) || list.rest.iter().any(|(_, p)| {
37 p.commands.iter().any(|elem| {
38 if let rush_parser::ast::PipelineElement::Simple(cmd) = elem {
39 simple_command_has_heredocs(cmd)
40 } else {
41 false
42 }
43 })
44 })
45 }
46 _ => false,
47 }
48 }
49
50 fn simple_command_has_heredocs(cmd: &SimpleCommand) -> bool {
51 cmd.redirects.iter().any(|r| matches!(r, Redirect::Heredoc { .. }))
52 }
53
54 /// Get list of all heredoc delimiters that need content
55 pub fn get_heredoc_delimiters(statement: &Statement) -> Vec<String> {
56 let mut delimiters = Vec::new();
57 match statement {
58 Statement::Empty => {}
59 Statement::Complete(cmd) => collect_command_delimiters(cmd, &mut delimiters),
60 Statement::Script(commands) => {
61 for cmd in commands {
62 collect_command_delimiters(cmd, &mut delimiters);
63 }
64 }
65 }
66 delimiters
67 }
68
69 fn collect_command_delimiters(cmd: &CompleteCommand, delimiters: &mut Vec<String>) {
70 match &cmd.command {
71 CommandType::Simple(simple_cmd) => {
72 collect_simple_delimiters(simple_cmd, delimiters);
73 }
74 CommandType::Pipeline(pipeline) => {
75 for elem in &pipeline.commands {
76 if let rush_parser::ast::PipelineElement::Simple(simple_cmd) = elem {
77 collect_simple_delimiters(simple_cmd, delimiters);
78 }
79 }
80 }
81 CommandType::AndOrList(list) => {
82 for elem in &list.first.commands {
83 if let rush_parser::ast::PipelineElement::Simple(simple_cmd) = elem {
84 collect_simple_delimiters(simple_cmd, delimiters);
85 }
86 }
87 for (_, pipeline) in &list.rest {
88 for elem in &pipeline.commands {
89 if let rush_parser::ast::PipelineElement::Simple(simple_cmd) = elem {
90 collect_simple_delimiters(simple_cmd, delimiters);
91 }
92 }
93 }
94 }
95 _ => {}
96 }
97 }
98
99 fn collect_simple_delimiters(cmd: &SimpleCommand, delimiters: &mut Vec<String>) {
100 for redirect in &cmd.redirects {
101 if let Redirect::Heredoc { delimiter, .. } = redirect {
102 delimiters.push(delimiter.clone());
103 }
104 }
105 }
106
107 /// Fill heredoc content into a statement
108 pub fn fill_heredoc_content(statement: &mut Statement, content_map: &std::collections::HashMap<String, Vec<String>>) {
109 match statement {
110 Statement::Empty => {}
111 Statement::Complete(cmd) => fill_command_content(cmd, content_map),
112 Statement::Script(commands) => {
113 for cmd in commands {
114 fill_command_content(cmd, content_map);
115 }
116 }
117 }
118 }
119
120 fn fill_command_content(cmd: &mut CompleteCommand, content_map: &std::collections::HashMap<String, Vec<String>>) {
121 match &mut cmd.command {
122 CommandType::Simple(simple_cmd) => {
123 fill_simple_content(simple_cmd, content_map);
124 }
125 CommandType::Pipeline(pipeline) => {
126 for elem in &mut pipeline.commands {
127 if let rush_parser::ast::PipelineElement::Simple(simple_cmd) = elem {
128 fill_simple_content(simple_cmd, content_map);
129 }
130 }
131 }
132 CommandType::AndOrList(list) => {
133 for elem in &mut list.first.commands {
134 if let rush_parser::ast::PipelineElement::Simple(simple_cmd) = elem {
135 fill_simple_content(simple_cmd, content_map);
136 }
137 }
138 for (_, pipeline) in &mut list.rest {
139 for elem in &mut pipeline.commands {
140 if let rush_parser::ast::PipelineElement::Simple(simple_cmd) = elem {
141 fill_simple_content(simple_cmd, content_map);
142 }
143 }
144 }
145 }
146 _ => {}
147 }
148 }
149
150 fn fill_simple_content(cmd: &mut SimpleCommand, content_map: &std::collections::HashMap<String, Vec<String>>) {
151 for redirect in &mut cmd.redirects {
152 if let Redirect::Heredoc { delimiter, content, .. } = redirect {
153 if let Some(lines) = content_map.get(delimiter) {
154 *content = lines.clone();
155 }
156 }
157 }
158 }
159