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 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}