ez_impl/
main.rs

1use std::borrow::Cow;
2
3pub trait ExitStatus {
4    fn to_i32(&self) -> i32;
5}
6
7impl ExitStatus for u8 {
8    fn to_i32(&self) -> i32 {
9        i32::from(*self)
10    }
11}
12
13impl ExitStatus for i32 {
14    fn to_i32(&self) -> i32 {
15        *self
16    }
17}
18
19impl ExitStatus for () {
20    fn to_i32(&self) -> i32 {
21        0
22    }
23}
24
25pub fn entry_point<
26    Args: FromIterator<String>,
27    Env: FromIterator<(String, String)>,
28    Return: ExitStatus,
29>(
30    main_package_name: &str,
31    main: fn(Args, Env) -> Result<Return, eyre::Report>,
32) -> Result<(), eyre::Report> {
33    // SAFETY: Modifying environment variables can be risky business in the
34    // presence of other threads. We're relying on the fact that this is the
35    // entry point and no other threads should exist yet, and then pass a
36    // safely-frozen copy of the environment to the main function.
37
38    dotenv::dotenv().ok();
39
40    if std::env::var("RUST_LOG").unwrap_or_default().is_empty() {
41        if cfg!(debug_assertions) {
42            std::env::set_var(
43                "RUST_LOG",
44                format!("warn,{main_package_name}=debug,ez=debug"),
45            );
46        } else {
47            std::env::set_var("RUST_LOG", format!("warn,{main_package_name}=info,ez=info"));
48        }
49    }
50
51    if std::env::var("RUST_SPANTRACE")
52        .unwrap_or_default()
53        .is_empty()
54    {
55        std::env::set_var("RUST_SPANTRACE", "1");
56    }
57
58    color_eyre::install().unwrap();
59
60    tracing_subscriber::util::SubscriberInitExt::init(tracing_subscriber::Layer::with_subscriber(
61        tracing_error::ErrorLayer::default(),
62        tracing_subscriber::fmt()
63            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
64            .with_target(true)
65            .with_span_events(
66                tracing_subscriber::fmt::format::FmtSpan::NEW
67                    | tracing_subscriber::fmt::format::FmtSpan::CLOSE,
68            )
69            .finish(),
70    ));
71
72    let args = std::env::args_os()
73        .skip(1)
74        .map(|s| match s.to_string_lossy() {
75            Cow::Borrowed(lossless) => lossless.to_owned(),
76            Cow::Owned(lossy) => {
77                tracing::warn!(
78                    target: "ez",
79                    "Invalid UTF-8 in command-line argument. Invalid sequences have been replaced \
80                     with '�':\n  {lossy:?}"
81                );
82                lossy
83            },
84        });
85
86    let env = std::env::vars_os().filter_map(|(name, value)| {
87        let name = name
88            .to_str()
89            .or_else(|| {
90                let lossy = name.to_string_lossy();
91                tracing::warn!(
92                    target: "ez",
93                    "Invalid UTF-8 in an environment variable name ({lossy:?}). It has been \
94                     skipped."
95                );
96                None
97            })?
98            .to_owned();
99        let value = value
100            .to_str()
101            .or_else(|| {
102                tracing::warn!(
103                    target: "ez",
104                    "Invalid UTF-8 in the value of the environment variable {name:?}. It has been \
105                     skipped."
106                );
107                None
108            })?
109            .to_owned();
110        Some((name, value))
111    });
112
113    let exit_status = main(args.collect(), env.collect()).map_err(|err| {
114        tracing::error!(target: "ez", "exiting with error status code due to an unhandled error");
115        err
116    })?.to_i32();
117
118    if exit_status != 0 {
119        tracing::debug!(target: "ez", "exiting with error status code {}", exit_status);
120        std::process::exit(exit_status);
121    }
122
123    Ok(())
124}