use serde::Serialize;
use crate::config::Config;
use crate::ctx::Ctx;
use crate::errors;
use crate::registry::Registry;
use crate::spec::command::Command;
use crate::spec::{XyzArgs, XyzSchema};
use crate::{dispatch, spec};
pub trait Definable {
fn register(&self, reg: &Registry) -> errors::Result<()>;
}
impl<T, R, F, E> Definable for Command<T, R, F, E>
where
T: XyzArgs,
R: XyzSchema + Serialize,
F: Fn(&Ctx, &T) -> std::result::Result<R, E> + Send + Sync + 'static,
E: std::error::Error + Send + Sync + 'static,
{
fn register(&self, reg: &Registry) -> errors::Result<()> {
self.entry().map(|e| {
let _ = reg.add(std::sync::Arc::new(e));
})
}
}
pub struct Builder<T, R, F, E>
where
T: XyzArgs,
R: XyzSchema + Serialize,
F: Fn(&Ctx, &T) -> std::result::Result<R, E> + Send + Sync + 'static,
E: std::error::Error + Send + Sync + 'static,
{
cmd: Command<T, R, F, E>,
reg: &'static Registry,
committed: bool,
config: Config,
err: Option<errors::Error>,
}
pub fn define<T, R, F, E>(name: &str, h: F) -> Builder<T, R, F, E>
where
T: XyzArgs,
R: XyzSchema + Serialize,
F: Fn(&Ctx, &T) -> std::result::Result<R, E> + Send + Sync + 'static,
E: std::error::Error + Send + Sync + 'static,
{
Builder {
cmd: Command::new(name, h),
reg: Registry::default(),
committed: false,
config: Config::default(),
err: None,
}
}
impl<T, R, F, E> Builder<T, R, F, E>
where
T: XyzArgs,
R: XyzSchema + Serialize,
F: Fn(&Ctx, &T) -> std::result::Result<R, E> + Send + Sync + 'static,
E: std::error::Error + Send + Sync + 'static,
{
pub fn summary(mut self, s: impl Into<String>) -> Self {
self.cmd = self.cmd.summary(s);
self
}
pub fn description(mut self, s: impl Into<String>) -> Self {
self.cmd = self.cmd.description(s);
self
}
pub fn cli(mut self, h: spec::CliHints) -> Self {
self.cmd = self.cmd.cli(h);
self
}
pub fn http(mut self, h: spec::HTTPHints) -> Self {
self.cmd = self.cmd.http(h);
self
}
pub fn mcp(mut self, h: spec::MCPHints) -> Self {
self.cmd = self.cmd.mcp(h);
self
}
pub fn configure(mut self, cfg: Config) -> Self {
self.config = cfg;
self
}
pub fn also(mut self, cmds: &[&dyn Definable]) -> Self {
if self.err.is_none() && !self.committed {
match self.commit_current() {
Ok(()) => self.committed = true,
Err(e) => self.err = Some(e),
}
}
if self.err.is_none() {
for c in cmds {
if let Err(e) = c.register(self.reg) {
self.err = Some(e);
break;
}
}
}
self
}
fn commit_current(&self) -> errors::Result<()> {
self.cmd.entry().map(|entry| {
let _ = self.reg.add(std::sync::Arc::new(entry));
})
}
pub fn run(self) -> ! {
let args: Vec<String> = std::env::args().skip(1).collect();
std::process::exit(self.run_args(args));
}
pub fn run_config(self, cfg: Config) -> ! {
let args: Vec<String> = std::env::args().skip(1).collect();
std::process::exit(self.run_args_config(args, cfg));
}
pub fn run_args(mut self, args: Vec<String>) -> i32 {
let cfg = std::mem::take(&mut self.config);
self.run_args_config(args, cfg)
}
pub fn run_args_config(mut self, args: Vec<String>, cfg: Config) -> i32 {
if self.err.is_none() && !self.committed {
match self.commit_current() {
Ok(()) => self.committed = true,
Err(e) => self.err = Some(e),
}
}
if let Some(e) = self.err {
eprintln!("{e}");
return 2;
}
dispatch::run_config(self.reg, args, cfg)
}
}
impl<T, R, F, E> Definable for Builder<T, R, F, E>
where
T: XyzArgs,
R: XyzSchema + Serialize,
F: Fn(&Ctx, &T) -> std::result::Result<R, E> + Send + Sync + 'static,
E: std::error::Error + Send + Sync + 'static,
{
fn register(&self, reg: &Registry) -> errors::Result<()> {
self.cmd.entry().map(|entry| {
let _ = reg.add(std::sync::Arc::new(entry));
})
}
}