1use std::{env, ffi::OsStr, ops::Deref};
10
11use crate::Capturable;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
16#[cfg_attr(
17 feature = "serde",
18 derive(serde::Serialize, serde::Deserialize),
19 serde(transparent)
20)]
21pub struct CurrentDirOpt(Option<std::path::PathBuf>);
22
23impl std::fmt::Display for CurrentDirOpt {
24 #[inline]
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 match &self.0 {
27 Some(path) => path.display().fmt(f),
28 None => write!(f, "<current dir error>"),
29 }
30 }
31}
32
33impl Deref for CurrentDirOpt {
34 type Target = Option<std::path::PathBuf>;
35
36 #[inline]
37 fn deref(&self) -> &Self::Target {
38 &self.0
39 }
40}
41
42impl CurrentDirOpt {
43 #[inline]
45 #[must_use]
46 pub fn into_inner(self) -> Option<std::path::PathBuf> {
47 self.0
48 }
49}
50
51impl Capturable for CurrentDirOpt {
52 #[inline]
53 fn capture() -> Self {
54 Self(env::current_dir().ok())
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
61#[cfg_attr(
62 feature = "serde",
63 derive(serde::Serialize, serde::Deserialize),
64 serde(transparent)
65)]
66pub struct HomeDirOpt(Option<std::path::PathBuf>);
67
68impl std::fmt::Display for HomeDirOpt {
69 #[inline]
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 match &self.0 {
72 Some(path) => path.display().fmt(f),
73 None => write!(f, "<home dir error>"),
74 }
75 }
76}
77
78impl Deref for HomeDirOpt {
79 type Target = Option<std::path::PathBuf>;
80
81 #[inline]
82 fn deref(&self) -> &Self::Target {
83 &self.0
84 }
85}
86
87impl HomeDirOpt {
88 #[inline]
90 #[must_use]
91 pub fn into_inner(self) -> Option<std::path::PathBuf> {
92 self.0
93 }
94}
95
96impl Capturable for HomeDirOpt {
97 #[inline]
98 fn capture() -> Self {
99 Self(env::home_dir())
100 }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
105#[cfg_attr(
106 feature = "serde",
107 derive(serde::Serialize, serde::Deserialize),
108 serde(transparent)
109)]
110pub struct EnvArgs(Box<[Box<OsStr>]>);
111
112impl std::fmt::Display for EnvArgs {
113 #[inline]
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 write!(f, "{:?}", self.0)
116 }
117}
118
119impl Deref for EnvArgs {
120 type Target = [Box<OsStr>];
121
122 #[inline]
123 fn deref(&self) -> &Self::Target {
124 &self.0
125 }
126}
127
128impl EnvArgs {
129 #[inline]
131 #[must_use]
132 pub fn into_inner(self) -> Box<[Box<OsStr>]> {
133 self.0
134 }
135}
136
137impl Capturable for EnvArgs {
138 #[inline]
139 fn capture() -> Self {
140 Self(
141 env::args_os()
142 .map(Into::into)
143 .collect::<Box<[Box<OsStr>]>>(),
144 )
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
151#[cfg_attr(
152 feature = "serde",
153 derive(serde::Serialize, serde::Deserialize),
154 serde(transparent)
155)]
156pub struct EnvVarOpt<E: EnvVarName>(Option<Box<str>>, std::marker::PhantomData<E>);
157
158pub trait EnvVarName {
162 const NAME: &'static str;
164}
165
166impl<E: EnvVarName> Capturable for EnvVarOpt<E> {
167 #[inline]
168 fn capture() -> Self {
169 Self(
170 env::var(E::NAME).ok().map(Into::into),
171 std::marker::PhantomData,
172 )
173 }
174}
175
176impl<E: EnvVarName> std::fmt::Display for EnvVarOpt<E> {
177 #[inline]
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 match &self.0 {
180 Some(value) => value.fmt(f),
181 None => write!(f, "<env var {} unset>", E::NAME),
182 }
183 }
184}
185
186impl<E: EnvVarName> Deref for EnvVarOpt<E> {
187 type Target = Option<Box<str>>;
188
189 #[inline]
190 fn deref(&self) -> &Self::Target {
191 &self.0
192 }
193}
194
195impl<E: EnvVarName> EnvVarOpt<E> {
196 #[inline]
198 #[must_use]
199 pub fn into_inner(self) -> Option<Box<str>> {
200 self.0
201 }
202}
203
204pub mod env_vars {
207 #[doc(hidden)]
208 pub mod __private {
209 pub use pastey;
210 }
211
212 #[doc(hidden)]
213 #[macro_export]
214 macro_rules! __make_env_var {
215 ($($name:ident),+ $(,)?) => { $crate::extras::env_vars::__private::pastey::paste! { $(
216 #[doc = "Marker type for the `" $name "` environment variable."]
217 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
218 pub struct [< $name:camel VarName>];
219
220 impl $crate::extras::EnvVarName for [< $name:camel VarName>] {
221 const NAME: &'static str = stringify!($name);
222 }
223
224 #[doc = "Captures the `" $name "` environment variable."]
225 pub type [< $name:camel Opt >] = $crate::extras::EnvVarOpt<[< $name:camel VarName>]>;
226 )+ }};
227 }
228 pub use crate::__make_env_var as make_env_var;
234
235 make_env_var!(PATH, HOME, USER, RUST_BACKTRACE, RUST_LOG,);
236}
237
238#[cfg(test)]
239mod tests {
240 use super::env_vars::make_env_var;
241
242 use super::*;
243
244 #[test]
245 fn current_dir_opt_captures_current_dir() {
246 let captured = CurrentDirOpt::capture();
247
248 assert_eq!(
249 captured.as_ref().unwrap(),
250 &std::env::current_dir().unwrap(),
251 "captured current dir should match actual current dir"
252 );
253 }
254
255 #[test]
256 fn env_args_opt_captures_args() {
257 let captured = EnvArgs::capture();
258 let actual_args = std::env::args_os()
259 .map(Into::into)
260 .collect::<Vec<Box<OsStr>>>();
261
262 assert_eq!(
263 captured.as_ref(),
264 &actual_args,
265 "captured env args should match actual env args"
266 );
267 }
268
269 #[test]
270 fn env_var_opt_captures_env_var() {
271 #[expect(
272 unsafe_code,
273 reason = "test setup requires setting an env var at runtime"
274 )]
275 unsafe {
276 std::env::set_var("__OOPSIE_TEST_ENV_VAR", std::process::id().to_string());
277 };
278 make_env_var!(__OOPSIE_TEST_ENV_VAR);
279
280 let captured = OopsieTestEnvVarOpt::capture();
281 assert_eq!(
282 captured.as_deref().unwrap(),
283 &std::process::id().to_string(),
284 "captured env var should match actual env var value"
285 );
286 }
287}