| 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 |