implement a cli module #85

Merged
PlexSheep merged 46 commits from feat/cli into devel 2024-07-09 18:12:24 +02:00
5 changed files with 76 additions and 13 deletions
Showing only changes of commit bfebb5327b - Show all commits

View File

@ -12,6 +12,9 @@ repository.workspace = true
keywords.workspace = true keywords.workspace = true
categories.workspace = true categories.workspace = true
[package.metadata.docs.rs]
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
[features] [features]
default = ["log"] default = ["log"]
log = ["dep:libpt-log", "dep:log"] log = ["dep:libpt-log", "dep:log"]
@ -22,6 +25,7 @@ clap = { version = "4.5.7", features = ["derive"] }
comfy-table = "7.1.1" comfy-table = "7.1.1"
console = "0.15.8" console = "0.15.8"
dialoguer = { version = "0.11.0", features = ["completion", "history"] } dialoguer = { version = "0.11.0", features = ["completion", "history"] }
embed-doc-image = "0.1.4"
exitcode = "1.1.2" exitcode = "1.1.2"
human-panic = "2.0.0" human-panic = "2.0.0"
indicatif = "0.17.8" indicatif = "0.17.8"

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@ -1,13 +1,61 @@
//! This module implements a default repl that fullfills the [Repl] trait
//!
//! You can implement your own [Repl] if you want.
use std::fmt::Debug; use std::fmt::Debug;
use super::Repl; use super::Repl;
use embed_doc_image::embed_doc_image;
/// [clap] help template with only usage and commands/options
pub const REPL_HELP_TEMPLATE: &str = r#"{usage-heading} {usage}
{all-args}{tab}
"#;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use dialoguer::{BasicHistory, Completion}; use dialoguer::{BasicHistory, Completion};
use libpt_log::trace; use libpt_log::trace;
#[allow(clippy::needless_doctest_main)] // It makes the example look better
/// Default implementation for a REPL
///
/// Note that you need to define the commands by yourself with a Subcommands enum.
///
/// # Example
///
/// ```no_run
/// use libpt_cli::repl::{DefaultRepl, Repl};
/// use libpt_cli::clap::Subcommand;
/// use libpt_cli::strum::EnumIter;
///
/// #[derive(Subcommand, Debug, EnumIter, Clone)]
/// enum ReplCommand {
/// /// hello world
/// Hello,
/// /// leave the repl
/// Exit,
/// }
///
/// fn main() {
/// let mut repl = DefaultRepl::<ReplCommand>::default();
/// loop {
/// repl.step().unwrap();
/// match repl.command().to_owned().unwrap() {
/// ReplCommand::Hello => println!("Hello"),
/// ReplCommand::Exit => break,
/// _ => (),
/// }
/// }
/// }
/// ```
/// **Screenshot**
///
/// ![Screenshot of an example program with a REPL][repl_screenshot]
#[embed_doc_image("repl_screenshot", "data/media/repl.png")]
#[derive(Parser)] #[derive(Parser)]
#[command(multicall = true)] #[command(multicall = true, help_template = REPL_HELP_TEMPLATE)]
pub struct DefaultRepl<C> pub struct DefaultRepl<C>
where where
C: Debug, C: Debug,
@ -31,7 +79,7 @@ where
} }
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct DefaultReplCompletion<C> struct DefaultReplCompletion<C>
where where
C: Debug, C: Debug,
C: Subcommand, C: Subcommand,
@ -58,12 +106,6 @@ where
fn command(&self) -> &Option<C> { fn command(&self) -> &Option<C> {
&self.command &self.command
} }
#[allow(refining_impl_trait)]
fn completion() -> DefaultReplCompletion<C> {
DefaultReplCompletion {
commands: std::marker::PhantomData::<C>,
}
}
fn step(&mut self) -> Result<(), super::error::ReplError> { fn step(&mut self) -> Result<(), super::error::ReplError> {
self.buf.clear(); self.buf.clear();

View File

@ -1,3 +1,5 @@
//! Errors for the Repl module
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]

View File

@ -1,3 +1,16 @@
//! Create easy and well defined REPLs
//!
//! A REPL is a [Read-Eval-Print-Loop](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop).
//! Well known examples for REPLs are shells (like bash).
//!
//! This module offers a convenient way to create a well-defined REPL without a lot of complicated
//! code and with a visually pleasing aesthetic. An example REPL implementation can be found in the
//! examples.
//!
//! The basic idea is that the user defines the commands with an enum and uses [claps](clap)
//! `#[derive(Subcommand)]`. A loop is then used to read from the stdin into a buffer, that buffer
//! is put to [clap] for parsing, similar to how [clap] would parse commandline arguments.
use std::fmt::Debug; use std::fmt::Debug;
pub mod error; pub mod error;
@ -6,8 +19,10 @@ mod default;
pub use default::*; pub use default::*;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use dialoguer::Completion;
/// Common Trait for repl objects
///
/// 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,
@ -18,12 +33,12 @@ where
fn new() -> Self; fn new() -> Self;
/// get the command that was parsed from user input /// get the command that was parsed from user input
/// ///
/// Will only be [None] if the repl has not had [step] executed yet. /// Will only be [None] if the repl has not had [step](Repl::step) executed yet.
fn command(&self) -> &Option<C>; fn command(&self) -> &Option<C>;
/// return all possible commands in this repl
fn completion() -> impl Completion;
/// advance the repl to the next iteration of the main loop /// advance the repl to the next iteration of the main loop
/// ///
/// 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]
fn step(&mut self) -> Result<(), ReplError>; fn step(&mut self) -> Result<(), ReplError>;
} }