use std::error::Error;
use ansi_term::Color;
use rustyline::Editor;
#[cfg(feature = "test-runner")]
use std::sync::{Arc, Mutex};
use clap::Command;
use crate::async_stdout::AsyncStdout;
use crate::builder::ClapCmdBuilder;
use crate::errors::ExitError;
use crate::group::{HandlerGroup, HandlerGroupMeta};
use crate::handler::{Callback, ClapCmdResult, Handler};
use crate::helper::ClapCmdHelper;
use crate::shell_parser::{split, unescape_word, ShlexRunType};
pub struct ClapCmd<State = (), History = rustyline::history::MemHistory>
where
State: Clone,
History: rustyline::history::History,
{
pub(crate) editor: Editor<ClapCmdHelper<State>, History>,
pub(crate) prompt: String,
pub(crate) continuation_prompt: String,
pub(crate) about: String,
#[cfg(feature = "test-runner")]
pub output: String,
#[cfg(feature = "test-runner")]
pub info: String,
#[cfg(feature = "test-runner")]
pub warn: String,
#[cfg(feature = "test-runner")]
pub error: String,
#[cfg(feature = "test-runner")]
pub success: String,
#[cfg(feature = "test-runner")]
pub async_output: Arc<Mutex<String>>,
}
impl<State> Default for ClapCmd<State>
where
State: Clone + Default + Send + Sync + 'static,
{
fn default() -> Self {
let builder = ClapCmdBuilder::<State>::default();
builder.build()
}
}
impl<State> ClapCmd<State>
where
State: Clone,
{
pub fn with_state(state: State) -> Self
where
State: Send + Sync + 'static,
{
ClapCmdBuilder::default().state(Some(state)).build()
}
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(Some(state));
}
pub fn unset_state(&mut self) {
self.editor
.helper_mut()
.expect("helper is always provided")
.set_state(None);
}
pub fn get_state(&self) -> Option<&State> {
self.editor
.helper()
.expect("helper is always provided")
.get_state()
}
pub fn get_state_mut(&mut self) -> Option<&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 set_continuation_prompt(&mut self, continuation_prompt: &str) {
self.continuation_prompt = continuation_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,
},
groups: vec![],
}
}
pub fn unnamed_group() -> HandlerGroup<State> {
HandlerGroup {
group: HandlerGroupMeta {
name: "".to_owned(),
description: "".to_owned(),
visible: false,
},
groups: vec![],
}
}
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,
#[cfg(feature = "test-runner")]
output: self.async_output.clone(),
})
}
pub fn output(&mut self, output: impl Into<String>) {
let output = output.into();
#[cfg(feature = "test-runner")]
self.output.push_str(&output);
println!("{}", output);
}
pub fn warn(&mut self, output: impl Into<String>) {
let output = output.into();
#[cfg(feature = "test-runner")]
self.warn.push_str(&output);
let output = format!("[-] {}", output);
println!("{}", Color::Yellow.bold().paint(output));
}
pub fn error(&mut self, output: impl Into<String>) {
let output = output.into();
#[cfg(feature = "test-runner")]
self.error.push_str(&output);
let output = format!("[!] {}", output);
println!("{}", Color::Red.bold().paint(output));
}
pub fn success(&mut self, output: impl Into<String>) {
let output = output.into();
#[cfg(feature = "test-runner")]
self.success.push_str(&output);
let output = format!("[+] {}", output);
println!("{}", Color::Green.bold().paint(output));
}
pub fn info(&mut self, output: impl Into<String>) {
let output = output.into();
#[cfg(feature = "test-runner")]
self.info.push_str(&output);
let output = format!("[*] {}", output);
println!("{}", Color::Cyan.bold().paint(output));
}
#[cfg(feature = "test-runner")]
fn clear_all(&mut self) {
self.output.clear();
self.info.clear();
self.warn.clear();
self.success.clear();
self.error.clear();
}
pub fn read_line(&mut self, prompt: &str) -> Option<String> {
let line = self.editor.readline(prompt).ok()?;
if !line.ends_with('\\') {
return Some(line);
}
let line = &line[..line.len() - 1];
let continuation = self.read_line(&self.continuation_prompt.clone());
let line = format!("{}\n{}", line, continuation.unwrap_or_default());
Some(line)
}
pub fn one_cmd(&mut self, line: &str) -> ClapCmdResult {
#[cfg(feature = "test-runner")]
self.clear_all();
let statements = split(line);
let mut last_result = Ok(());
for statement in statements {
match statement.run_type {
ShlexRunType::Unconditional => {}
ShlexRunType::ConditionalAnd => {
if last_result.is_err() {
break;
}
}
ShlexRunType::ConditionalOr => {
if last_result.is_ok() {
break;
}
}
}
let mut argv = vec![];
for word in statement.iter() {
argv.push(unescape_word(line, word));
}
let argv: Vec<String> = argv
.into_iter()
.skip_while(|word| word.is_empty())
.collect();
if argv.is_empty() {
continue;
}
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() {
self.error(format!("unknown command: '{command_to_run}'"));
continue;
}
let handler = handler.expect("some type can always be unwrapped");
let matches = handler.command.clone().try_get_matches_from_mut(argv);
match matches {
Ok(matches) => {
last_result = handler.clone().callback.call(self, matches);
}
Err(err) => {
self.output(format!("{}", err.render()));
}
}
}
last_result
}
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>() {
self.error(format!(
"Error: {}",
err.source().expect("error source unknown")
));
continue;
}
break;
}
}
}
}