pub_just/
shebang.rs

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}