1use std::env;
2use std::os::unix::process::CommandExt;
3use std::process::Command;
4
5#[inline]
6pub fn bin() {
7 let mut args = env::args();
8 let _ = args.next();
9 let mut command;
10 let mut invocation_type = InvocationType::None;
11 let mut override_vars = false;
12
13 macro_rules! require_next_arg {
14 ($($err_fmt:tt)*) => {
15 {
16 let Some(arg) = args.next() else {
17 eprintln!($($err_fmt)*);
18 return
19 };
20
21 arg
22 }
23 }
24 }
25
26 loop {
27 command = args.next();
28 match command.as_deref() {
29 Some("--help" | "-h") => {
30 print_help(true);
31 return
32 }
33 Some("--version" | "-v") => {
34 println!(concat!(env!("CARGO_PKG_NAME"), " v", env!("CARGO_PKG_VERSION")));
35 return
36 }
37 Some(arg @ "--filename" | arg @ "-f") => {
38 let arg = require_next_arg!("expected filename argument for {arg}");
39
40 if !invocation_type.new_invocation(InvocationType::Filename(arg)) {
41 return
42 }
43 }
44 Some(arg @ "--path" | arg @ "-p") => {
45 let arg = require_next_arg!("expected path argument for {arg}");
46
47 if !invocation_type.new_invocation(InvocationType::Path(arg)) {
48 return
49 }
50 }
51 Some("--override" | "-o") => { override_vars = true }
52 Some("--") => {
53 command = args.next();
54 break
55 }
56 Some(arg) if arg.starts_with("-") => {
57 println!("unknown arg {arg}");
58 println!();
59 println!("If you're meaning to run the command `{arg}`, try adding `--` before the command");
60 return
61 }
62 Some(_) | None => { break }
63 }
64 }
65
66 let env_vars = match invocation_type {
67 InvocationType::None => {
68 dotenvy::dotenv_iter()
69 .unwrap_or_else(|err| panic!("error reading .env file: {err}"))
70 }
71 InvocationType::Filename(filename) => {
72 dotenvy::from_filename_iter(&*filename)
73 .unwrap_or_else(|err| panic!("error reading {filename}: {err}"))
74 }
75 InvocationType::Path(path) => {
76 dotenvy::from_path_iter(&*path)
77 .unwrap_or_else(|err| panic!("error reading {path}: {err}"))
78 }
79 };
80
81 match command {
82 Some(command) => {
83 let mut cmd = Command::new(command);
84 cmd.args(args);
85
86 for var in env_vars {
87 let (k, v) = var.unwrap_or_else(|err| panic!("error getting var: {err}"));
88
89 if override_vars || env::var_os(&*k).is_none() {
93 cmd.env(k, v);
94 }
95 }
96
97 let err = cmd.exec();
98 panic!("errored running the command: {err}");
99 }
100 None => {
101 eprintln!("command not found");
102 eprintln!();
103 print_help(false);
104 }
105 }
106}
107
108enum InvocationType {
109 Filename(String),
110 Path(String),
111 None
112}
113
114impl InvocationType {
115 pub fn as_str(&self) -> &'static str {
116 use InvocationType::*;
117
118 match self {
119 Filename(_) => { "filename" }
120 Path(_) => { "path" }
121 None => { "none" }
122 }
123 }
124
125 pub fn new_invocation(&mut self, new: Self) -> bool {
126 use InvocationType::*;
127
128 match (&*self, &new) {
129 (None, _) => {
130 *self = new;
131 return true
132 }
133 (Filename(_), Filename(_)) | (Path(_), Path(_)) => {
134 println!("cannot specify {} more than once", self.as_str());
135 }
136 (Filename(_), Path(_)) | (Path(_), Filename(_))=> {
137 println!("cannot specify both {} and {}", self.as_str(), new.as_str());
138 }
139 (_, None) => {
140 println!("squints at you funny how did you manage this");
141 }
142 }
143
144 false
145 }
146}
147
148fn print_help(success: bool) {
149 let err_msg = "\
150 pretend this is a useful help command\n\
151 man rust multiline strings are not ergonomic whatsoever\
152 ";
153
154 if success {
155 println!("{err_msg}");
156 } else {
157 eprintln!("{err_msg}");
158 }
159}