diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml index 0900f7d..9ec6de5 100644 --- a/members/libpt-cli/Cargo.toml +++ b/members/libpt-cli/Cargo.toml @@ -12,6 +12,9 @@ repository.workspace = true keywords.workspace = true categories.workspace = true +[package.metadata.docs.rs] +cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] + [features] default = ["log"] log = ["dep:libpt-log", "dep:log"] @@ -22,6 +25,7 @@ clap = { version = "4.5.7", features = ["derive"] } comfy-table = "7.1.1" console = "0.15.8" dialoguer = { version = "0.11.0", features = ["completion", "history"] } +embed-doc-image = "0.1.4" exitcode = "1.1.2" human-panic = "2.0.0" indicatif = "0.17.8" diff --git a/members/libpt-cli/data/media/repl.png b/members/libpt-cli/data/media/repl.png new file mode 100644 index 0000000..7121227 Binary files /dev/null and b/members/libpt-cli/data/media/repl.png differ diff --git a/members/libpt-cli/src/repl/default.rs b/members/libpt-cli/src/repl/default.rs index 3096167..7e540a7 100644 --- a/members/libpt-cli/src/repl/default.rs +++ b/members/libpt-cli/src/repl/default.rs @@ -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 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 dialoguer::{BasicHistory, Completion}; 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::::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)] -#[command(multicall = true)] +#[command(multicall = true, help_template = REPL_HELP_TEMPLATE)] pub struct DefaultRepl where C: Debug, @@ -31,7 +79,7 @@ where } #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)] -pub struct DefaultReplCompletion +struct DefaultReplCompletion where C: Debug, C: Subcommand, @@ -58,12 +106,6 @@ where fn command(&self) -> &Option { &self.command } - #[allow(refining_impl_trait)] - fn completion() -> DefaultReplCompletion { - DefaultReplCompletion { - commands: std::marker::PhantomData::, - } - } fn step(&mut self) -> Result<(), super::error::ReplError> { self.buf.clear(); diff --git a/members/libpt-cli/src/repl/error.rs b/members/libpt-cli/src/repl/error.rs index c4416a3..d78aca2 100644 --- a/members/libpt-cli/src/repl/error.rs +++ b/members/libpt-cli/src/repl/error.rs @@ -1,3 +1,5 @@ +//! Errors for the Repl module + use thiserror::Error; #[derive(Error, Debug)] diff --git a/members/libpt-cli/src/repl/mod.rs b/members/libpt-cli/src/repl/mod.rs index 87a95eb..5c0d343 100644 --- a/members/libpt-cli/src/repl/mod.rs +++ b/members/libpt-cli/src/repl/mod.rs @@ -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; pub mod error; @@ -6,8 +19,10 @@ mod default; pub use default::*; 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: Parser + Debug where C: Debug, @@ -18,12 +33,12 @@ where fn new() -> Self; /// 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; - /// return all possible commands in this repl - fn completion() -> impl Completion; /// 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>; }