1#[derive(Copy, Clone)]
2pub struct Shebang<'line> {
3 pub interpreter: &'line str,
4 pub argument: Option<&'line str>,
5}
6
7impl<'line> Shebang<'line> {
8 pub fn new(line: &'line str) -> Option<Self> {
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, [' ', '\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(Self {
28 interpreter,
29 argument,
30 })
31 }
32
33 pub fn interpreter_filename(&self) -> &str {
34 self
35 .interpreter
36 .split(['/', '\\'])
37 .last()
38 .unwrap_or(self.interpreter)
39 }
40
41 pub fn include_shebang_line(&self) -> bool {
42 !(cfg!(windows) || matches!(self.interpreter_filename(), "cmd" | "cmd.exe"))
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::Shebang;
49
50 #[test]
51 fn split_shebang() {
52 fn check(text: &str, expected_split: Option<(&str, Option<&str>)>) {
53 let shebang = Shebang::new(text);
54 assert_eq!(
55 shebang.map(|shebang| (shebang.interpreter, shebang.argument)),
56 expected_split
57 );
58 }
59
60 check("#! ", None);
61 check("#!", None);
62 check("#!/bin/bash", Some(("/bin/bash", None)));
63 check("#!/bin/bash ", Some(("/bin/bash", None)));
64 check(
65 "#!/usr/bin/env python",
66 Some(("/usr/bin/env", Some("python"))),
67 );
68 check(
69 "#!/usr/bin/env python ",
70 Some(("/usr/bin/env", Some("python"))),
71 );
72 check(
73 "#!/usr/bin/env python -x",
74 Some(("/usr/bin/env", Some("python -x"))),
75 );
76 check(
77 "#!/usr/bin/env python -x",
78 Some(("/usr/bin/env", Some("python -x"))),
79 );
80 check(
81 "#!/usr/bin/env python \t-x\t",
82 Some(("/usr/bin/env", Some("python \t-x"))),
83 );
84 check("#/usr/bin/env python \t-x\t", None);
85 check("#! /bin/bash", Some(("/bin/bash", None)));
86 check("#!\t\t/bin/bash ", Some(("/bin/bash", None)));
87 check(
88 "#! \t\t/usr/bin/env python",
89 Some(("/usr/bin/env", Some("python"))),
90 );
91 check(
92 "#! /usr/bin/env python ",
93 Some(("/usr/bin/env", Some("python"))),
94 );
95 check(
96 "#! /usr/bin/env python -x",
97 Some(("/usr/bin/env", Some("python -x"))),
98 );
99 check(
100 "#! /usr/bin/env python -x",
101 Some(("/usr/bin/env", Some("python -x"))),
102 );
103 check(
104 "#! /usr/bin/env python \t-x\t",
105 Some(("/usr/bin/env", Some("python \t-x"))),
106 );
107 check("# /usr/bin/env python \t-x\t", None);
108 }
109
110 #[test]
111 fn interpreter_filename_with_forward_slash() {
112 assert_eq!(
113 Shebang::new("#!/foo/bar/baz")
114 .unwrap()
115 .interpreter_filename(),
116 "baz"
117 );
118 }
119
120 #[test]
121 fn interpreter_filename_with_backslash() {
122 assert_eq!(
123 Shebang::new("#!\\foo\\bar\\baz")
124 .unwrap()
125 .interpreter_filename(),
126 "baz"
127 );
128 }
129
130 #[test]
131 fn dont_include_shebang_line_cmd() {
132 assert!(!Shebang::new("#!cmd").unwrap().include_shebang_line());
133 }
134
135 #[test]
136 fn dont_include_shebang_line_cmd_exe() {
137 assert!(!Shebang::new("#!cmd.exe /C").unwrap().include_shebang_line());
138 }
139
140 #[test]
141 #[cfg(not(windows))]
142 fn include_shebang_line_other_not_windows() {
143 assert!(Shebang::new("#!foo -c").unwrap().include_shebang_line());
144 }
145
146 #[test]
147 #[cfg(windows)]
148 fn include_shebang_line_other_windows() {
149 assert!(!Shebang::new("#!foo -c").unwrap().include_shebang_line());
150 }
151}