1 #[derive(Copy, Clone)] 2 pub(crate) struct Shebang<'line> { 3 pub(crate) interpreter: &'line str, 4 pub(crate) argument: Option<&'line str>, 5 } 6 7 impl<'line> Shebang<'line> { new(line: &'line str) -> Option<Shebang<'line>>8 pub(crate) fn new(line: &'line str) -> Option<Shebang<'line>> { 9 if !line.starts_with("#!") { 10 return None; 11 } 12 13 let mut pieces = line[2..] 14 .lines() 15 .next() 16 .unwrap_or("") 17 .trim() 18 .splitn(2, |c| c == ' ' || c == '\t'); 19 20 let interpreter = pieces.next().unwrap_or(""); 21 let argument = pieces.next(); 22 23 if interpreter.is_empty() { 24 return None; 25 } 26 27 Some(Shebang { 28 interpreter, 29 argument, 30 }) 31 } 32 interpreter_filename(&self) -> &str33 fn interpreter_filename(&self) -> &str { 34 self 35 .interpreter 36 .split(|c| matches!(c, '/' | '\\')) 37 .last() 38 .unwrap_or(self.interpreter) 39 } 40 script_filename(&self, recipe: &str) -> String41 pub(crate) fn script_filename(&self, recipe: &str) -> String { 42 match self.interpreter_filename() { 43 "cmd" | "cmd.exe" => format!("{}.bat", recipe), 44 "powershell" | "powershell.exe" | "pwsh" | "pwsh.exe" => format!("{}.ps1", recipe), 45 _ => recipe.to_owned(), 46 } 47 } 48 include_shebang_line(&self) -> bool49 pub(crate) fn include_shebang_line(&self) -> bool { 50 !matches!(self.interpreter_filename(), "cmd" | "cmd.exe") 51 } 52 } 53 54 #[cfg(test)] 55 mod tests { 56 use super::Shebang; 57 58 #[test] split_shebang()59 fn split_shebang() { 60 fn check(text: &str, expected_split: Option<(&str, Option<&str>)>) { 61 let shebang = Shebang::new(text); 62 assert_eq!( 63 shebang.map(|shebang| (shebang.interpreter, shebang.argument)), 64 expected_split 65 ); 66 } 67 68 check("#! ", None); 69 check("#!", None); 70 check("#!/bin/bash", Some(("/bin/bash", None))); 71 check("#!/bin/bash ", Some(("/bin/bash", None))); 72 check( 73 "#!/usr/bin/env python", 74 Some(("/usr/bin/env", Some("python"))), 75 ); 76 check( 77 "#!/usr/bin/env python ", 78 Some(("/usr/bin/env", Some("python"))), 79 ); 80 check( 81 "#!/usr/bin/env python -x", 82 Some(("/usr/bin/env", Some("python -x"))), 83 ); 84 check( 85 "#!/usr/bin/env python -x", 86 Some(("/usr/bin/env", Some("python -x"))), 87 ); 88 check( 89 "#!/usr/bin/env python \t-x\t", 90 Some(("/usr/bin/env", Some("python \t-x"))), 91 ); 92 check("#/usr/bin/env python \t-x\t", None); 93 check("#! /bin/bash", Some(("/bin/bash", None))); 94 check("#!\t\t/bin/bash ", Some(("/bin/bash", None))); 95 check( 96 "#! \t\t/usr/bin/env python", 97 Some(("/usr/bin/env", Some("python"))), 98 ); 99 check( 100 "#! /usr/bin/env python ", 101 Some(("/usr/bin/env", Some("python"))), 102 ); 103 check( 104 "#! /usr/bin/env python -x", 105 Some(("/usr/bin/env", Some("python -x"))), 106 ); 107 check( 108 "#! /usr/bin/env python -x", 109 Some(("/usr/bin/env", Some("python -x"))), 110 ); 111 check( 112 "#! /usr/bin/env python \t-x\t", 113 Some(("/usr/bin/env", Some("python \t-x"))), 114 ); 115 check("# /usr/bin/env python \t-x\t", None); 116 } 117 118 #[test] interpreter_filename_with_forward_slash()119 fn interpreter_filename_with_forward_slash() { 120 assert_eq!( 121 Shebang::new("#!/foo/bar/baz") 122 .unwrap() 123 .interpreter_filename(), 124 "baz" 125 ); 126 } 127 128 #[test] interpreter_filename_with_backslash()129 fn interpreter_filename_with_backslash() { 130 assert_eq!( 131 Shebang::new("#!\\foo\\bar\\baz") 132 .unwrap() 133 .interpreter_filename(), 134 "baz" 135 ); 136 } 137 138 #[test] powershell_script_filename()139 fn powershell_script_filename() { 140 assert_eq!( 141 Shebang::new("#!powershell").unwrap().script_filename("foo"), 142 "foo.ps1" 143 ); 144 } 145 146 #[test] pwsh_script_filename()147 fn pwsh_script_filename() { 148 assert_eq!( 149 Shebang::new("#!pwsh").unwrap().script_filename("foo"), 150 "foo.ps1" 151 ); 152 } 153 154 #[test] powershell_exe_script_filename()155 fn powershell_exe_script_filename() { 156 assert_eq!( 157 Shebang::new("#!powershell.exe") 158 .unwrap() 159 .script_filename("foo"), 160 "foo.ps1" 161 ); 162 } 163 164 #[test] pwsh_exe_script_filename()165 fn pwsh_exe_script_filename() { 166 assert_eq!( 167 Shebang::new("#!pwsh.exe").unwrap().script_filename("foo"), 168 "foo.ps1" 169 ); 170 } 171 172 #[test] cmd_script_filename()173 fn cmd_script_filename() { 174 assert_eq!( 175 Shebang::new("#!cmd").unwrap().script_filename("foo"), 176 "foo.bat" 177 ); 178 } 179 180 #[test] cmd_exe_script_filename()181 fn cmd_exe_script_filename() { 182 assert_eq!( 183 Shebang::new("#!cmd.exe").unwrap().script_filename("foo"), 184 "foo.bat" 185 ); 186 } 187 188 #[test] plain_script_filename()189 fn plain_script_filename() { 190 assert_eq!(Shebang::new("#!bar").unwrap().script_filename("foo"), "foo"); 191 } 192 193 #[test] dont_include_shebang_line_cmd()194 fn dont_include_shebang_line_cmd() { 195 assert!(!Shebang::new("#!cmd").unwrap().include_shebang_line()); 196 } 197 198 #[test] dont_include_shebang_line_cmd_exe()199 fn dont_include_shebang_line_cmd_exe() { 200 assert!(!Shebang::new("#!cmd.exe /C").unwrap().include_shebang_line()); 201 } 202 203 #[test] include_shebang_line_other()204 fn include_shebang_line_other() { 205 assert!(Shebang::new("#!foo -c").unwrap().include_shebang_line()); 206 } 207 } 208