use std::error::Error;
use rustyline::Editor;
use clap::Command;
use crate::async_stdout::AsyncStdout;
use crate::builder::{ClapCmdBuilder, ExitError};
use crate::group::{HandlerGroup, HandlerGroupMeta};
use crate::handler::{Callback, ClapCmdResult, Handler};
use crate::helper::ClapCmdHelper;
use crate::shell_parser::split;
pub struct ClapCmd<State = ()>
where
State: Clone + Default,
{
pub(crate) editor: Editor<ClapCmdHelper<State>>,
pub(crate) prompt: String,
pub(crate) about: String,
}
impl<State> Default for ClapCmd<State>
where
State: Clone + Default + Send + Sync + 'static,
{
fn default() -> Self {
let builder = ClapCmdBuilder::default();
builder.build()
}
}
impl<State> ClapCmd<State>
where
State: Clone + Default,
{
pub fn builder() -> ClapCmdBuilder {
ClapCmdBuilder::default()
}
pub fn set_state(&mut self, state: State) {
self.editor
.helper_mut()
.expect("helper is always provided")
.set_state(state);
}
pub fn get_state(&self) -> &State {
self.editor
.helper()
.expect("helper is always provided")
.get_state()
}
pub fn get_state_mut(&mut self) -> &mut State {
self.editor
.helper_mut()
.expect("helper is always provided")
.get_state_mut()
}
pub fn set_prompt(&mut self, prompt: &str) {
self.prompt = prompt.to_owned();
}
pub fn has_command(&self, command: &str) -> bool {
self.editor
.helper()
.expect("helper is always provided")
.dispatcher
.iter()
.any(|c| c.command.get_name() == command)
}
pub fn add_command(&mut self, callback: impl Callback<State>, command: Command) {
self.add_command_from_handler(Handler {
command,
group: None,
callback: callback.clone_box(),
})
}
fn add_command_from_handler(&mut self, handler: Handler<State>) {
if self.has_command(handler.command.get_name()) {
return;
}
self.editor
.helper_mut()
.expect("helper is always provided")
.dispatcher
.push(handler);
}
pub fn remove_command(&mut self, command: &str) {
self.editor
.helper_mut()
.expect("helper is always provided")
.dispatcher
.retain(|c| c.command.get_name() != command);
}
pub fn group(name: &str) -> HandlerGroup<State> {
HandlerGroup {
group: HandlerGroupMeta {
name: name.to_owned(),
description: "".to_owned(),
visible: true,
},
..Default::default()
}
}
pub fn unnamed_group() -> HandlerGroup<State> {
HandlerGroup {
group: HandlerGroupMeta {
name: "".to_owned(),
description: "".to_owned(),
visible: false,
},
..Default::default()
}
}
pub fn add_group(&mut self, groups: &HandlerGroup<State>) {
for handler in &groups.groups {
self.add_command_from_handler(handler.clone());
}
}
pub fn remove_group(&mut self, groups: &HandlerGroup<State>) {
for command in &groups.groups {
if self
.editor
.helper()
.expect("helper is always provided")
.dispatcher
.iter()
.any(|h| {
h.command.get_name() == command.command.get_name()
&& h.group
.as_ref()
.map_or(false, |g| g.name == groups.group.name)
})
{
self.remove_command(command.command.get_name());
}
}
}
pub fn get_async_writer(&mut self) -> Result<impl std::io::Write, Box<dyn Error>> {
let printer = self.editor.create_external_printer()?;
Ok(AsyncStdout { printer })
}
pub fn read_line(&mut self, prompt: &str) -> Option<String> {
self.editor.readline(prompt).ok()
}
pub fn one_cmd(&mut self, line: &str) -> ClapCmdResult {
let words = split(line);
let mut argv = vec![];
for word in words {
argv.push(&line[word.start..word.end]);
}
let argv: Vec<&str> = argv
.into_iter()
.skip_while(|word| word.is_empty())
.collect();
if argv.is_empty() {
return Ok(());
}
let command_to_run = &argv[0].to_owned();
let helper = self.editor.helper().expect("helper is always provided");
let handler = helper
.dispatcher
.iter()
.find(|h| h.command.get_name() == command_to_run);
if handler.is_none() {
println!("unknown command: '{command_to_run}'");
return Ok(());
}
let handler = handler.expect("some type can always be unwrapped");
let matches = handler.command.clone().try_get_matches_from_mut(argv);
if matches.is_ok() {
return handler
.clone()
.callback
.call(self, matches.unwrap_or_default());
}
let err = matches.unwrap_err().kind();
if err == clap::error::ErrorKind::DisplayHelp {
handler.command.clone().print_help().unwrap_or_default();
println!();
return Ok(());
}
println!("error occured while parsing line: {:?}", err);
Ok(())
}
pub fn run_loop(&mut self) {
loop {
let prompt = self.prompt.clone();
let prompt = prompt.as_str();
let input = self.read_line(prompt);
if input.is_none() {
break;
}
let input = input.expect("input needs to be utf-8");
if let Err(err) = self.one_cmd(&input) {
if !err.is::<ExitError>() {
println!(
"received error: {}",
err.source().expect("error source unknown")
);
continue;
}
break;
}
}
}
}