implement a cli module #85

Merged
PlexSheep merged 46 commits from feat/cli into devel 2024-07-09 18:12:24 +02:00
6 changed files with 35 additions and 43 deletions
Showing only changes of commit 476efb85c8 - Show all commits

View File

@ -48,7 +48,7 @@ fn main() -> anyhow::Result<()> {
Err(e) => { Err(e) => {
// if the user requested the help, print in blue, otherwise in red as it's just an // if the user requested the help, print in blue, otherwise in red as it's just an
// error // error
if let libpt_cli::repl::error::ReplError::Parsing(e) = &e { if let libpt_cli::repl::error::Error::Parsing(e) = &e {
if e.kind() == clap::error::ErrorKind::DisplayHelp { if e.kind() == clap::error::ErrorKind::DisplayHelp {
println!("{}", style(e).cyan()); println!("{}", style(e).cyan());
continue; continue;
@ -76,7 +76,7 @@ fn main() -> anyhow::Result<()> {
if !fancy { if !fancy {
println!("{}", text.join(" ")) println!("{}", text.join(" "))
} else { } else {
printing::blockprint(text.join(" "), console::Color::Cyan) printing::blockprint(&text.join(" "), console::Color::Cyan)
} }
} }
} }

View File

@ -80,7 +80,6 @@ Author: {author-with-newline}
/// # use libpt_cli::args::VerbosityLevel; /// # use libpt_cli::args::VerbosityLevel;
/// use libpt_log::Level; /// use libpt_log::Level;
/// # use clap::Parser; /// # use clap::Parser;
/// use log;
/// ///
/// # #[derive(Parser, Debug)] /// # #[derive(Parser, Debug)]
/// # pub struct Opts { /// # pub struct Opts {
@ -94,8 +93,6 @@ Author: {author-with-newline}
/// // Level might be None if the user wants no output at all. /// // Level might be None if the user wants no output at all.
/// // for the 'tracing' level: /// // for the 'tracing' level:
/// let level: Level = opts.verbose.level(); /// let level: Level = opts.verbose.level();
/// // for the 'log' level:
/// let llevel: log::Level = opts.verbose.level_for_log_crate();
/// } /// }
/// ``` /// ```
#[derive(Parser, Clone, PartialEq, Eq, Hash)] #[derive(Parser, Clone, PartialEq, Eq, Hash)]

View File

@ -45,10 +45,11 @@ use console::{style, Color};
/// use libpt_cli::console::Color; /// use libpt_cli::console::Color;
/// use libpt_cli::printing::blockprint; /// use libpt_cli::printing::blockprint;
/// # fn main() { /// # fn main() {
/// blockprint("Hello world!".to_string(), Color::Blue); /// blockprint("Hello world!", Color::Blue);
/// # } /// # }
/// ``` /// ```
#[inline] #[inline]
#[allow(clippy::needless_pass_by_value)] // we just take an impl, using a &impl is much less ergonomic
pub fn blockprint(content: impl ToString, color: Color) { pub fn blockprint(content: impl ToString, color: Color) {
println!("{}", blockfmt(content, color)); println!("{}", blockfmt(content, color));
} }
@ -66,11 +67,12 @@ pub fn blockprint(content: impl ToString, color: Color) {
/// use libpt_cli::console::Color; /// use libpt_cli::console::Color;
/// use libpt_cli::printing::blockfmt; /// use libpt_cli::printing::blockfmt;
/// # fn main() { /// # fn main() {
/// let formatted_content = blockfmt("Hello world!".to_string(), Color::Blue); /// let formatted_content = blockfmt("Hello world!", Color::Blue);
/// println!("{}", formatted_content); /// println!("{}", formatted_content);
/// # } /// # }
/// ``` /// ```
#[inline] #[inline]
#[allow(clippy::needless_pass_by_value)] // we just take an impl, using a &impl is much less ergonomic
pub fn blockfmt(content: impl ToString, color: Color) -> String { pub fn blockfmt(content: impl ToString, color: Color) -> String {
blockfmt_advanced( blockfmt_advanced(
content, content,
@ -98,7 +100,7 @@ pub fn blockfmt(content: impl ToString, color: Color) -> String {
/// println!( /// println!(
/// "{}", /// "{}",
/// blockfmt_advanced( /// blockfmt_advanced(
/// "Hello world!".to_string(), /// "Hello world!",
/// Some(Color::Blue), /// Some(Color::Blue),
/// presets::UTF8_FULL, /// presets::UTF8_FULL,
/// ContentArrangement::DynamicFullWidth, /// ContentArrangement::DynamicFullWidth,
@ -120,6 +122,8 @@ pub fn blockfmt(content: impl ToString, color: Color) -> String {
/// - `preset`: The preset style for the border /// - `preset`: The preset style for the border
/// - `arrangement`: The arrangement of the the border (e.g., stretch to sides, wrap around ) /// - `arrangement`: The arrangement of the the border (e.g., stretch to sides, wrap around )
/// - `alignment`: The alignment of the content within the cells (e.g., left, center, right) /// - `alignment`: The alignment of the content within the cells (e.g., left, center, right)
#[allow(clippy::missing_panics_doc)] // we add a row then unwrap it, no panic should be possible
#[allow(clippy::needless_pass_by_value)] // we just take an impl, using a &impl is much less ergonomic
pub fn blockfmt_advanced( pub fn blockfmt_advanced(
content: impl ToString, content: impl ToString,
color: Option<Color>, color: Option<Color>,

View File

@ -56,11 +56,10 @@ use libpt_log::trace;
#[embed_doc_image("repl_screenshot", "data/media/repl.png")] #[embed_doc_image("repl_screenshot", "data/media/repl.png")]
#[derive(Parser)] #[derive(Parser)]
#[command(multicall = true, help_template = REPL_HELP_TEMPLATE)] #[command(multicall = true, help_template = REPL_HELP_TEMPLATE)]
#[allow(clippy::module_name_repetitions)] // we can't just name it `Default`, that's part of std
pub struct DefaultRepl<C> pub struct DefaultRepl<C>
where where
C: Debug, C: Debug + Subcommand + strum::IntoEnumIterator,
C: Subcommand,
C: strum::IntoEnumIterator,
{ {
/// the command you want to execute, along with its arguments /// the command you want to execute, along with its arguments
#[command(subcommand)] #[command(subcommand)]
@ -81,18 +80,14 @@ where
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)]
struct DefaultReplCompletion<C> struct DefaultReplCompletion<C>
where where
C: Debug, C: Debug + Subcommand + strum::IntoEnumIterator,
C: Subcommand,
C: strum::IntoEnumIterator,
{ {
commands: std::marker::PhantomData<C>, commands: std::marker::PhantomData<C>,
} }
impl<C> Repl<C> for DefaultRepl<C> impl<C> Repl<C> for DefaultRepl<C>
where where
C: Debug, C: Debug + Subcommand + strum::IntoEnumIterator,
C: Subcommand,
C: strum::IntoEnumIterator,
{ {
fn new() -> Self { fn new() -> Self {
Self { Self {
@ -106,7 +101,7 @@ where
fn command(&self) -> &Option<C> { fn command(&self) -> &Option<C> {
&self.command &self.command
} }
fn step(&mut self) -> Result<(), super::error::ReplError> { fn step(&mut self) -> Result<(), super::error::Error> {
self.buf.clear(); self.buf.clear();
// NOTE: display::Input requires some kind of lifetime that would be a bother to store in // NOTE: display::Input requires some kind of lifetime that would be a bother to store in
@ -139,9 +134,7 @@ where
impl<C> Default for DefaultRepl<C> impl<C> Default for DefaultRepl<C>
where where
C: Debug, C: Debug + Subcommand + strum::IntoEnumIterator,
C: Subcommand,
C: strum::IntoEnumIterator,
{ {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
@ -150,9 +143,7 @@ where
impl<C> Debug for DefaultRepl<C> impl<C> Debug for DefaultRepl<C>
where where
C: Debug, C: Debug + Subcommand + strum::IntoEnumIterator,
C: Subcommand,
C: strum::IntoEnumIterator,
{ {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DefaultRepl") f.debug_struct("DefaultRepl")
@ -167,16 +158,15 @@ where
impl<C> DefaultReplCompletion<C> impl<C> DefaultReplCompletion<C>
where where
C: Debug, C: Debug + Subcommand + strum::IntoEnumIterator,
C: Subcommand,
C: strum::IntoEnumIterator,
{ {
pub fn new() -> Self { /// Make a new [`DefaultReplCompletion`] for the type `C`
pub const fn new() -> Self {
Self { Self {
commands: std::marker::PhantomData::<C>, commands: std::marker::PhantomData::<C>,
} }
} }
fn commands(&self) -> Vec<String> { fn commands() -> Vec<String> {
let mut buf = Vec::new(); let mut buf = Vec::new();
// every crate has the help command, but it is not part of the enum // every crate has the help command, but it is not part of the enum
buf.push("help".to_string()); buf.push("help".to_string());
@ -199,9 +189,7 @@ where
impl<C> Default for DefaultReplCompletion<C> impl<C> Default for DefaultReplCompletion<C>
where where
C: Debug, C: Debug + Subcommand + strum::IntoEnumIterator,
C: Subcommand,
C: strum::IntoEnumIterator,
{ {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
@ -210,14 +198,11 @@ where
impl<C> Completion for DefaultReplCompletion<C> impl<C> Completion for DefaultReplCompletion<C>
where where
C: Debug, C: Debug + Subcommand + strum::IntoEnumIterator,
C: Subcommand,
C: strum::IntoEnumIterator,
{ {
/// Simple completion implementation based on substring /// Simple completion implementation based on substring
fn get(&self, input: &str) -> Option<String> { fn get(&self, input: &str) -> Option<String> {
let matches = self let matches = Self::commands()
.commands()
.into_iter() .into_iter()
.filter(|option| option.starts_with(input)) .filter(|option| option.starts_with(input))
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@ -3,9 +3,11 @@
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ReplError { pub enum Error {
#[error(transparent)] #[error(transparent)]
Parsing(#[from] clap::Error), Parsing(#[from] clap::Error),
#[error(transparent)] #[error(transparent)]
Input(#[from] dialoguer::Error), Input(#[from] dialoguer::Error),
#[error(transparent)]
Other(#[from] anyhow::Error),
} }

View File

@ -14,7 +14,7 @@
use std::fmt::Debug; use std::fmt::Debug;
pub mod error; pub mod error;
use error::ReplError; use error::Error;
mod default; mod default;
pub use default::*; pub use default::*;
@ -25,9 +25,7 @@ use clap::{Parser, Subcommand};
/// Unless you want to implement custom features (not just commands), just use [`DefaultRepl`]. /// Unless you want to implement custom features (not just commands), just use [`DefaultRepl`].
pub trait Repl<C>: Parser + Debug pub trait Repl<C>: Parser + Debug
where where
C: Debug, C: Debug + Subcommand + strum::IntoEnumIterator,
C: Subcommand,
C: strum::IntoEnumIterator,
{ {
/// create a new repl /// create a new repl
fn new() -> Self; fn new() -> Self;
@ -40,5 +38,11 @@ where
/// This should be used at the start of your loop. /// This should be used at the start of your loop.
/// ///
/// Note that the help menu is an Error: [`clap::error::ErrorKind::DisplayHelp`] /// Note that the help menu is an Error: [`clap::error::ErrorKind::DisplayHelp`]
fn step(&mut self) -> Result<(), ReplError>; ///
/// # Errors
///
/// * [`Error::Input`] [dialoguer] User Input had some kind of I/O Error
/// * [`Error::Parsing`] [clap] could not parse the user input, or user requested help
/// * [`Error::Other`] Any other error with [anyhow], [`DefaultRepl`] does not use this but custom implementations might
fn step(&mut self) -> Result<(), Error>;
} }