use std::marker::PhantomData;
use std::process::{ExitCode, Termination};
use clap::Parser;
use super::error::{format_clap_error, CliErrorHandler};
use crate::error::CliError;
use crate::params::CollectedArgs;
mod _private {
pub struct Build;
pub struct Ready;
pub struct Finished;
}
use _private::*;
#[doc(hidden)]
pub trait Run: Send + Sync {
fn call<'a>(
&'a self,
args: &'a mut CollectedArgs,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = Result<(), CliError>> + Send + 'a>,
>;
}
type ClingReady<T> = Cling<T, Ready>;
pub type ClingFinished<T> = Cling<T, Finished>;
pub struct Cling<T, S = Build> {
settings: Settings,
_status: PhantomData<S>,
inner: ClingInner<T>,
}
#[allow(dead_code)]
#[derive(Default, Clone)]
struct Settings {}
enum ClingInner<T> {
Ready {
parsed: T,
collected_params: CollectedArgs,
},
Finished {
result: Result<(), CliError>,
collected_params: CollectedArgs,
_parsed_type: PhantomData<T>,
},
}
impl<T: Run + Parser> Cling<T, Finished> {
pub fn success() -> ClingFinished<T> {
ClingFinished {
settings: Settings::default(),
_status: PhantomData,
inner: ClingInner::Finished {
result: Ok(()),
collected_params: CollectedArgs::new(),
_parsed_type: PhantomData,
},
}
}
pub fn failed(e: impl Into<CliError>) -> ClingFinished<T> {
ClingFinished {
settings: Settings::default(),
_status: PhantomData,
inner: ClingInner::Finished {
result: Err(e.into()),
collected_params: CollectedArgs::new(),
_parsed_type: PhantomData,
},
}
}
}
impl<T: Run + Parser> Cling<T, Build> {
pub fn new(parsed: T) -> ClingReady<T> {
ClingReady {
settings: Settings::default(),
_status: PhantomData,
inner: ClingInner::Ready {
parsed,
collected_params: CollectedArgs::new(),
},
}
}
#[allow(dead_code)]
fn with_settings(parsed: T, settings: Settings) -> ClingReady<T> {
ClingReady {
settings,
_status: PhantomData,
inner: ClingInner::Ready {
parsed,
collected_params: CollectedArgs::new(),
},
}
}
pub async fn parse_and_run() -> ClingFinished<T> {
let parsed =
<T as clap::Parser>::try_parse().map_err(format_clap_error::<T>);
match parsed {
| Ok(parsed) => Cling::new(parsed).run().await,
| Err(e) => {
ClingFinished {
settings: Settings::default(),
_status: PhantomData,
inner: ClingInner::Finished {
result: Err(e.into()),
collected_params: CollectedArgs::new(),
_parsed_type: PhantomData,
},
}
}
}
}
pub fn parse() -> ClingReady<T> {
ClingReady {
settings: Settings::default(),
_status: PhantomData,
inner: ClingInner::Ready {
parsed: <T as clap::Parser>::parse(),
collected_params: CollectedArgs::new(),
},
}
}
pub fn try_parse() -> Result<ClingReady<T>, CliError> {
Ok(ClingReady {
settings: Settings::default(),
_status: PhantomData,
inner: ClingInner::Ready {
parsed: <T as clap::Parser>::try_parse()
.map_err(format_clap_error::<T>)?,
collected_params: CollectedArgs::new(),
},
})
}
pub fn try_parse_from<I, B>(itr: I) -> Result<ClingReady<T>, CliError>
where
I: IntoIterator<Item = B>,
B: Into<std::ffi::OsString> + Clone,
{
Ok(ClingReady {
settings: Settings::default(),
_status: PhantomData,
inner: ClingInner::Ready {
parsed: <T as clap::Parser>::try_parse_from(itr)
.map_err(format_clap_error::<T>)?,
collected_params: CollectedArgs::new(),
},
})
}
#[cfg(feature = "shlex")]
pub fn try_parse_str(input: &str) -> Result<ClingReady<T>, CliError> {
let bin_name = std::env::current_exe()
.ok()
.and_then(|pb| pb.file_name().map(|s| s.to_os_string()))
.and_then(|s| s.into_string().ok())
.unwrap();
Self::try_parse_str_with_bin_name(&bin_name, input)
}
#[cfg(feature = "shlex")]
pub fn try_parse_str_with_bin_name(
bin_name: &str,
input: &str,
) -> Result<ClingReady<T>, CliError> {
let input = format!("{bin_name} {input}");
let args = shlex::split(&input).ok_or(CliError::InputString)?;
let parsed = <T as clap::Parser>::try_parse_from(args)
.map_err(format_clap_error::<T>)?;
Ok(ClingReady {
settings: Settings::default(),
_status: PhantomData,
inner: ClingInner::Ready {
parsed,
collected_params: CollectedArgs::new(),
},
})
}
pub fn parse_or_exit() -> ClingReady<T> {
ClingReady {
settings: Settings::default(),
_status: PhantomData,
inner: ClingInner::Ready {
parsed: <T as clap::Parser>::try_parse()
.map_err(format_clap_error::<T>)
.unwrap_or_exit(),
collected_params: CollectedArgs::new(),
},
}
}
pub async fn default_run_and_exit() -> ! {
Self::parse_or_exit().run_and_exit().await
}
}
impl<T: Run + Parser> Cling<T, Ready> {
pub async fn run_and_exit(self) -> ! {
let res = self.run().await;
res.result().then_exit()
}
pub async fn run_with_state_and_exit<S>(self, state: S) -> !
where
S: Clone + Send + Sync + 'static,
{
self.run_with_state(state).await.result().then_exit()
}
pub async fn run(self) -> ClingFinished<T> {
let ClingInner::Ready {
parsed,
mut collected_params,
} = self.inner
else {
unreachable!()
};
let result = <T as Run>::call(&parsed, &mut collected_params).await;
ClingFinished {
settings: self.settings,
_status: PhantomData,
inner: ClingInner::Finished {
collected_params,
result,
_parsed_type: PhantomData,
},
}
}
pub async fn run_with_state<S>(mut self, state: S) -> ClingFinished<T>
where
S: Clone + Send + Sync + 'static,
{
let ClingInner::Ready {
ref mut collected_params,
..
} = self.inner
else {
unreachable!()
};
collected_params.insert(
crate::extractors::State(state),
true,
);
Self::run(self).await
}
}
impl<T: Run + Parser> Cling<T, Finished> {
pub fn result_ref(&self) -> &Result<(), CliError> {
let ClingInner::Finished { ref result, .. } = self.inner else {
unreachable!()
};
result
}
pub fn result(self) -> Result<(), CliError> {
let ClingInner::Finished { result, .. } = self.inner else {
unreachable!()
};
result
}
pub fn is_success(&self) -> bool {
self.result_ref().is_ok()
}
pub fn is_failure(&self) -> bool {
self.result_ref().is_err()
}
pub fn collected_parameters(&self) -> &CollectedArgs {
let ClingInner::Finished {
ref collected_params,
..
} = self.inner
else {
unreachable!()
};
collected_params
}
pub fn collected_arguments_mut(&mut self) -> &mut CollectedArgs {
let ClingInner::Finished {
ref mut collected_params,
..
} = self.inner
else {
unreachable!()
};
collected_params
}
}
pub trait ClapClingExt: Sized {
fn into_cling(self) -> ClingReady<Self>;
}
impl<T> ClapClingExt for T
where
T: Run + Parser + Sync + Send + 'static,
{
fn into_cling(self) -> ClingReady<Self> {
Cling::<T>::new(self)
}
}
impl<T: Run + Parser> Termination for ClingFinished<T> {
fn report(self) -> ExitCode {
if let Err(e) = self.result() {
let _ = e.print();
return ExitCode::from(e.exit_code());
}
ExitCode::SUCCESS
}
}
impl<T: Run + Parser> From<CliError> for ClingFinished<T> {
fn from(value: CliError) -> Self {
Cling::failed(value)
}
}