Skip to main content

wiwi_dotenv/
lib.rs

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				// either we override vars, so we set it no matter what
90				// or the var doesn't exists, so we can set it
91				// this took me way too long to figure out. :sifSuffering:
92				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}