use std::fmt::Debug;
use super::Repl;
use embed_doc_image::embed_doc_image;
pub const REPL_HELP_TEMPLATE: &str = r"{usage-heading} {usage}
{all-args}{tab}
";
use clap::{Parser, Subcommand};
use dialoguer::{BasicHistory, Completion};
use libpt_log::trace;
#[allow(clippy::needless_doctest_main)] #[embed_doc_image("repl_screenshot", "data/media/repl.png")]
#[derive(Parser)]
#[command(multicall = true, help_template = REPL_HELP_TEMPLATE)]
#[allow(clippy::module_name_repetitions)] pub struct DefaultRepl<C>
where
C: Debug + Subcommand + strum::IntoEnumIterator,
{
#[command(subcommand)]
command: Option<C>,
#[clap(skip)]
buf: String,
#[clap(skip)]
buf_preparsed: Vec<String>,
#[clap(skip)]
completion: DefaultReplCompletion<C>,
#[clap(skip)]
history: BasicHistory,
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)]
struct DefaultReplCompletion<C>
where
C: Debug + Subcommand + strum::IntoEnumIterator,
{
commands: std::marker::PhantomData<C>,
}
impl<C> Repl<C> for DefaultRepl<C>
where
C: Debug + Subcommand + strum::IntoEnumIterator,
{
fn new() -> Self {
Self {
command: None,
buf_preparsed: Vec::new(),
buf: String::new(),
history: BasicHistory::new(),
completion: DefaultReplCompletion::new(),
}
}
fn command(&self) -> &Option<C> {
&self.command
}
fn step(&mut self) -> Result<(), super::error::Error> {
self.buf.clear();
self.buf = dialoguer::Input::with_theme(&dialoguer::theme::ColorfulTheme::default())
.completion_with(&self.completion)
.history_with(&mut self.history)
.interact_text()?;
self.buf_preparsed = Vec::new();
self.buf_preparsed
.extend(shlex::split(&self.buf).unwrap_or_default());
trace!("read input: {:?}", self.buf_preparsed);
trace!("repl after step: {:#?}", self);
let cmds = Self::try_parse_from(&self.buf_preparsed)?;
self.command = cmds.command;
Ok(())
}
}
impl<C> Default for DefaultRepl<C>
where
C: Debug + Subcommand + strum::IntoEnumIterator,
{
fn default() -> Self {
Self::new()
}
}
impl<C> Debug for DefaultRepl<C>
where
C: Debug + Subcommand + strum::IntoEnumIterator,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DefaultRepl")
.field("command", &self.command)
.field("buf", &self.buf)
.field("buf_preparsed", &self.buf_preparsed)
.field("completion", &self.completion)
.field("history", &"(no debug)")
.finish()
}
}
impl<C> DefaultReplCompletion<C>
where
C: Debug + Subcommand + strum::IntoEnumIterator,
{
pub const fn new() -> Self {
Self {
commands: std::marker::PhantomData::<C>,
}
}
fn commands() -> Vec<String> {
let mut buf = Vec::new();
buf.push("help".to_string());
for c in C::iter() {
buf.push(
format!("{c:?}")
.split_whitespace()
.map(str::to_lowercase)
.next()
.unwrap()
.to_string(),
);
}
trace!("commands: {buf:?}");
buf
}
}
impl<C> Default for DefaultReplCompletion<C>
where
C: Debug + Subcommand + strum::IntoEnumIterator,
{
fn default() -> Self {
Self::new()
}
}
impl<C> Completion for DefaultReplCompletion<C>
where
C: Debug + Subcommand + strum::IntoEnumIterator,
{
fn get(&self, input: &str) -> Option<String> {
let matches = Self::commands()
.into_iter()
.filter(|option| option.starts_with(input))
.collect::<Vec<_>>();
trace!("\nmatches: {matches:#?}");
if matches.len() == 1 {
Some(matches[0].to_string())
} else {
None
}
}
}