1#[cfg(test)]
7#[path = "./converter_test.rs"]
8mod converter_test;
9
10use regex::Regex;
11
12static SHELL2BATCH_PREFIX: &str = "# shell2batch:";
13
14fn replace_flags(arguments: &str, flags_mappings: Vec<(&str, &str)>) -> String {
15 let mut windows_arguments = arguments.to_string();
16
17 for flags in flags_mappings {
18 let (shell_flag, windows_flag) = flags;
19
20 windows_arguments = match Regex::new(shell_flag) {
21 Ok(shell_regex) => {
22 let str_value = &shell_regex.replace_all(&windows_arguments, windows_flag);
23 str_value.to_string()
24 }
25 Err(_) => windows_arguments,
26 };
27 }
28
29 windows_arguments
30}
31
32fn convert_var<'a>(value: &'a str, buffer: &mut Vec<&'a str>) {
33 match value {
36 "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" => {
37 buffer.push("%");
38 buffer.push(value);
39 }
40 "@" => buffer.push("%*"),
41 _ => {
42 buffer.push("%");
43 buffer.push(value);
44 buffer.push("%");
45 }
46 }
47}
48
49fn replace_full_vars(arguments: &str) -> String {
50 let mut parts: Vec<&str> = arguments.split("${").collect();
51 let mut buffer = vec![];
52
53 buffer.push(parts.remove(0));
54
55 for part in parts {
56 let (before, after, found) = match part.find("}") {
57 None => (part, "", false),
58 Some(index) => {
59 let values = part.split_at(index);
60
61 (values.0, &values.1[1..values.1.len()], true)
62 }
63 };
64
65 if found {
66 convert_var(before, &mut buffer);
67 } else {
68 buffer.push(before)
69 }
70
71 if after.len() > 0 {
72 buffer.push(after);
73 }
74 }
75
76 buffer.join("").to_string()
77}
78
79fn replace_partial_vars(arguments: &str) -> String {
80 let mut parts: Vec<&str> = arguments.split('$').collect();
81 let mut buffer = vec![];
82
83 buffer.push(parts.remove(0));
84
85 for part in parts {
86 let (before, after) = match part.find(" ") {
87 None => (part, ""),
88 Some(index) => part.split_at(index),
89 };
90
91 convert_var(before, &mut buffer);
92
93 if after.len() > 0 {
94 buffer.push(after);
95 }
96 }
97
98 buffer.join("").to_string()
99}
100
101fn replace_vars(arguments: &str) -> String {
102 let mut updated_arguments = replace_full_vars(arguments);
103 updated_arguments = replace_partial_vars(&updated_arguments);
104
105 updated_arguments
106}
107
108fn add_arguments(arguments: &str, additional_arguments: Vec<String>, pre: bool) -> String {
109 let mut windows_arguments = if pre {
110 "".to_string()
111 } else {
112 arguments.to_string()
113 };
114
115 for additional_argument in additional_arguments {
116 windows_arguments.push_str(&additional_argument);
117 }
118
119 if pre {
120 if arguments.len() > 0 {
121 windows_arguments.push_str(" ");
122 }
123 windows_arguments.push_str(arguments);
124 }
125
126 windows_arguments.trim_start().to_string()
127}
128
129fn convert_line(line: &str) -> String {
130 if line.contains(SHELL2BATCH_PREFIX) {
131 let index = line.find(SHELL2BATCH_PREFIX).unwrap() + SHELL2BATCH_PREFIX.len();
132 let windows_command = line[index..].trim();
133 windows_command.to_string()
134 } else if line.starts_with("#") {
135 let mut windows_command = String::from(line);
136 windows_command.remove(0);
137 windows_command.insert_str(0, "@REM ");
138
139 windows_command
140 } else {
141 let (shell_command, mut arguments) = match line.find(" ") {
143 None => (line, "".to_string()),
144 Some(index) => {
145 let (shell_command, arguments_str) = line.split_at(index);
146
147 (shell_command, arguments_str.to_string())
148 }
149 };
150
151 arguments = arguments.trim().to_string();
152
153 let (
154 mut windows_command,
155 flags_mappings,
156 pre_arguments,
157 post_arguments,
158 modify_path_separator,
159 ) = match shell_command {
160 "cp" => {
161 let win_cmd = match Regex::new("(^|\\s)-[^ ]*[rR]") {
171 Ok(regex_instance) => {
172 if regex_instance.is_match(&arguments) {
173 "xcopy".to_string()
174 } else {
175 "copy".to_string()
176 }
177 }
178 Err(_) => "copy".to_string(),
179 };
180
181 let flags_mappings = if win_cmd == "xcopy".to_string() {
182 vec![("-[rR]", "/E")]
183 } else {
184 vec![]
185 };
186 (win_cmd, flags_mappings, vec![], vec![], true)
187 }
188 "mv" => ("move".to_string(), vec![], vec![], vec![], true),
189 "ls" => ("dir".to_string(), vec![], vec![], vec![], true),
190 "rm" => {
191 let win_cmd = match Regex::new("-[a-zA-Z]*[rR][a-zA-Z]* ") {
192 Ok(regex_instance) => {
193 if regex_instance.is_match(&arguments) {
194 "rmdir".to_string()
195 } else {
196 "del".to_string()
197 }
198 }
199 Err(_) => "del".to_string(),
200 };
201
202 let flags_mappings = if win_cmd == "rmdir".to_string() {
203 vec![("-([rR][fF]|[fF][rR]) ", "/S /Q "), ("-[rR]+ ", "/S ")]
204 } else {
205 vec![("-[fF] ", "/Q ")]
206 };
207
208 (win_cmd, flags_mappings, vec![], vec![], true)
209 }
210 "mkdir" => (
211 "mkdir".to_string(),
212 vec![("-[pP]", "")],
213 vec![],
214 vec![],
215 true,
216 ),
217 "clear" => ("cls".to_string(), vec![], vec![], vec![], false),
218 "grep" => ("find".to_string(), vec![], vec![], vec![], false),
219 "pwd" => ("chdir".to_string(), vec![], vec![], vec![], false),
220 "export" => ("set".to_string(), vec![], vec![], vec![], false),
221 "unset" => (
222 "set".to_string(),
223 vec![],
224 vec![],
225 vec!["=".to_string()],
226 false,
227 ),
228 "touch" => {
229 let mut file_arg = arguments.replace("/", "\\").to_string();
230 file_arg.push_str("+,,");
231
232 (
233 "copy".to_string(),
234 vec![],
235 vec!["/B ".to_string(), file_arg.clone()],
236 vec![],
237 true,
238 )
239 }
240 "set" => (
241 "@echo".to_string(),
242 vec![("-x", "on"), ("\\+x", "off")],
243 vec![],
244 vec![],
245 false,
246 ),
247 _ => (shell_command.to_string(), vec![], vec![], vec![], false),
248 };
249
250 if modify_path_separator {
252 arguments = arguments.replace("/", "\\");
253 }
254 windows_command = windows_command.replace("/", "\\");
255
256 let mut windows_arguments = arguments.to_string();
257
258 windows_arguments = if pre_arguments.len() > 0 {
260 add_arguments(&windows_arguments, pre_arguments, true)
261 } else {
262 windows_arguments
263 };
264
265 windows_arguments = if flags_mappings.len() > 0 {
267 replace_flags(&arguments, flags_mappings)
268 } else {
269 windows_arguments
270 };
271
272 windows_arguments = if windows_arguments.len() > 0 {
274 replace_vars(&windows_arguments)
275 } else {
276 windows_arguments
277 };
278 windows_command = replace_vars(&windows_command);
279
280 windows_arguments = if post_arguments.len() > 0 {
282 add_arguments(&windows_arguments, post_arguments, false)
283 } else {
284 windows_arguments
285 };
286
287 if windows_arguments.len() > 0 {
288 windows_command.push_str(" ");
289 windows_command.push_str(&windows_arguments);
290 }
291
292 windows_command
293 }
294}
295
296pub(crate) fn run(script: &str) -> String {
298 let lines: Vec<&str> = script.split('\n').collect();
299 let mut windows_batch = vec![];
300
301 for mut line in lines {
302 line = line.trim();
303 let mut line_string = line.to_string();
304
305 let converted_line = if line_string.len() == 0 {
307 line_string
308 } else {
309 convert_line(&mut line_string)
310 };
311
312 windows_batch.push(converted_line);
313 }
314
315 windows_batch.join("\n")
316}