From cab0fb2cc9220b0f2e6b620adeb18130d01a1cc4 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 9 Dec 2023 16:51:45 +0100 Subject: [PATCH] add value names for flags in md and man --- complete/src/fish.rs | 42 ++++++++++++++++++++++------------- complete/src/lib.rs | 50 +++++++++++++++++++++++++++++++++--------- complete/src/man.rs | 43 ++++++++++++++++++++++++++++-------- complete/src/md.rs | 24 ++++++++++++++------ complete/src/zsh.rs | 12 +++++----- derive/src/complete.rs | 41 +++++++++++++++++++++++++--------- derive/src/lib.rs | 3 +-- src/lib.rs | 2 +- 8 files changed, 157 insertions(+), 60 deletions(-) diff --git a/complete/src/fish.rs b/complete/src/fish.rs index f86fc93..d6f0f66 100644 --- a/complete/src/fish.rs +++ b/complete/src/fish.rs @@ -1,19 +1,22 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::{Command, ValueHint}; +use crate::{Command, Flag, ValueHint}; /// Create completion script for `fish` +/// +/// Short and long options are combined into single `complete` calls, even if +/// they differ in whether they take arguments or not. pub fn render(c: &Command) -> String { let mut out = String::new(); let name = &c.name; for arg in &c.args { let mut line = format!("complete -c {name}"); - for short in &arg.short { - line.push_str(&format!(" -s {short}")); + for Flag { flag, .. } in &arg.short { + line.push_str(&format!(" -s {flag}")); } - for long in &arg.long { - line.push_str(&format!(" -l {long}")); + for Flag { flag, .. } in &arg.long { + line.push_str(&format!(" -l {flag}")); } line.push_str(&format!(" -d '{}'", arg.help)); if let Some(value) = &arg.value { @@ -42,15 +45,18 @@ fn render_value_hint(value: &ValueHint) -> String { #[cfg(test)] mod test { use super::render; - use crate::{Arg, Command, ValueHint}; + use crate::{Arg, Command, Flag, Value, ValueHint}; #[test] fn short() { let c = Command { - name: "test".into(), + name: "test", args: vec![Arg { - short: vec!["a".into()], - help: "some flag".into(), + short: vec![Flag { + flag: "a", + value: Value::No, + }], + help: "some flag", ..Arg::default() }], ..Command::default() @@ -61,10 +67,13 @@ mod test { #[test] fn long() { let c = Command { - name: "test".into(), + name: "test", args: vec![Arg { - long: vec!["all".into()], - help: "some flag".into(), + long: vec![Flag { + flag: "all", + value: Value::No, + }], + help: "some flag", ..Arg::default() }], ..Command::default() @@ -92,11 +101,14 @@ mod test { ]; for (hint, expected) in args { let c = Command { - name: "test".into(), + name: "test", args: vec![Arg { - short: vec!["a".into()], + short: vec![Flag { + flag: "a", + value: Value::No, + }], long: vec![], - help: "some flag".into(), + help: "some flag", value: Some(hint), }], ..Command::default() diff --git a/complete/src/lib.rs b/complete/src/lib.rs index b703d64..14a3a47 100644 --- a/complete/src/lib.rs +++ b/complete/src/lib.rs @@ -1,28 +1,58 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +//! Generation of completion and documentation +//! +//! All formats use the [`Command`] struct as input, which specifies all +//! information needed. This struct is similar to some structs in the derive +//! crate for uutils-args, but there are some key differences: +//! +//! - This is meant to be more general. +//! - Some information is added (such as fields for the summary) +//! - We have [`ValueHint`] in this crate. +//! - Some information is removed because it is irrelevant for completion and documentation +//! - This struct is meant to exist at runtime of the program +//! mod fish; mod man; mod md; mod zsh; +/// A description of a CLI command +/// +/// The completions and documentation will be generated based on this struct. #[derive(Default)] -pub struct Command { - pub name: String, - pub summary: String, - pub version: String, - pub after_options: String, - pub args: Vec, +pub struct Command<'a> { + pub name: &'a str, + pub summary: &'a str, + pub version: &'a str, + pub after_options: &'a str, + pub args: Vec>, } +/// Description of an argument +/// +/// An argument may consist of several flags. In completions and documentation +/// formats that support it, these flags will be grouped. #[derive(Default)] -pub struct Arg { - pub short: Vec, - pub long: Vec, - pub help: String, +pub struct Arg<'a> { + pub short: Vec>, + pub long: Vec>, + pub help: &'a str, pub value: Option, } +pub struct Flag<'a> { + pub flag: &'a str, + pub value: Value<'a>, +} + +pub enum Value<'a> { + Required(&'a str), + Optional(&'a str), + No, +} + // Modelled after claps ValueHint pub enum ValueHint { Strings(Vec), diff --git a/complete/src/man.rs b/complete/src/man.rs index 34d92aa..494f828 100644 --- a/complete/src/man.rs +++ b/complete/src/man.rs @@ -1,36 +1,61 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::Command; -use roff::{bold, roman, Roff}; +use crate::{Command, Flag, Value}; +use roff::{bold, italic, roman, Roff}; pub fn render(c: &Command) -> String { let mut page = Roff::new(); page.control("TH", [&c.name.to_uppercase(), "1"]); page.control("SH", ["NAME"]); - page.text([roman(&c.name)]); + page.text([roman(c.name)]); page.control("SH", ["DESCRIPTION"]); - page.text([roman(&c.summary)]); + page.text([roman(c.summary)]); page.control("SH", ["OPTIONS"]); for arg in &c.args { page.control("TP", []); let mut flags = Vec::new(); - for l in &arg.long { + for Flag { flag, value } in &arg.long { if !flags.is_empty() { flags.push(roman(", ")); } - flags.push(bold(format!("--{l}"))); + flags.push(bold(format!("--{flag}"))); + match value { + Value::Required(name) => { + flags.push(roman("=")); + flags.push(italic(*name)); + } + Value::Optional(name) => { + flags.push(roman("[")); + flags.push(roman("=")); + flags.push(italic(*name)); + flags.push(roman("]")); + } + Value::No => {} + } } - for s in &arg.short { + for Flag { flag, value } in &arg.short { if !flags.is_empty() { flags.push(roman(", ")); } - flags.push(bold(format!("-{s}"))); + flags.push(bold(format!("-{flag}"))); + match value { + Value::Required(name) => { + flags.push(roman(" ")); + flags.push(italic(*name)); + } + Value::Optional(name) => { + flags.push(roman("[")); + flags.push(italic(*name)); + flags.push(roman("]")); + } + Value::No => {} + } } page.text(flags); - page.text([roman(&arg.help)]); + page.text([roman(arg.help)]); } page.render() } diff --git a/complete/src/md.rs b/complete/src/md.rs index 08f502f..1c2fa1b 100644 --- a/complete/src/md.rs +++ b/complete/src/md.rs @@ -1,18 +1,18 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::Command; +use crate::{Command, Flag, Value}; /// Render command to a markdown file for mdbook pub fn render(c: &Command) -> String { let mut out = String::new(); out.push_str(&title(c)); out.push_str(&additional(c)); - out.push_str(&c.summary); + out.push_str(c.summary); out.push_str("\n\n"); out.push_str(&options(c)); out.push_str("\n\n"); - out.push_str(&c.after_options); + out.push_str(c.after_options); out.push('\n'); out } @@ -40,12 +40,22 @@ fn options(c: &Command) -> String { let mut flags = Vec::new(); - for long in &arg.long { - flags.push(format!("--{long}")); + for Flag { flag, value } in &arg.long { + let value_str = match value { + Value::Required(name) => format!("={name}"), + Value::Optional(name) => format!("[={name}]"), + Value::No => String::new(), + }; + flags.push(format!("--{flag}{value_str}")); } - for short in &arg.short { - flags.push(format!("-{short}")) + for Flag { flag, value } in &arg.short { + let value_str = match value { + Value::Required(name) => format!(" {name}"), + Value::Optional(name) => format!("[{name}]"), + Value::No => String::new(), + }; + flags.push(format!("-{flag}{value_str}")); } out.push_str(&flags.join(", ")); diff --git a/complete/src/zsh.rs b/complete/src/zsh.rs index 814c904..5087697 100644 --- a/complete/src/zsh.rs +++ b/complete/src/zsh.rs @@ -1,11 +1,11 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::{Arg, Command}; +use crate::{Arg, Command, Flag}; /// Create completion script for `zsh` pub fn render(c: &Command) -> String { - template(&c.name, &render_args(&c.args)) + template(c.name, &render_args(&c.args)) } fn render_args(args: &[Arg]) -> String { @@ -13,11 +13,11 @@ fn render_args(args: &[Arg]) -> String { let indent = " ".repeat(8); for arg in args { let help = &arg.help; - for short in &arg.short { - out.push_str(&format!("{indent}'-{short}[{help}]' \\\n")); + for Flag { flag, .. } in &arg.short { + out.push_str(&format!("{indent}'-{flag}[{help}]' \\\n")); } - for long in &arg.long { - out.push_str(&format!("{indent}'--{long}[{help}]' \\\n")); + for Flag { flag, .. } in &arg.long { + out.push_str(&format!("{indent}'--{flag}[{help}]' \\\n")); } } out diff --git a/derive/src/complete.rs b/derive/src/complete.rs index 2d7b365..9e9ea96 100644 --- a/derive/src/complete.rs +++ b/derive/src/complete.rs @@ -3,7 +3,7 @@ use crate::{ argument::{ArgType, Argument}, - flags::{Flag, Flags}, + flags::{Flag, Flags, Value}, }; use proc_macro2::TokenStream; use quote::quote; @@ -40,12 +40,33 @@ pub fn complete(args: &[Argument], file: &Option) -> TokenStream { let short: Vec<_> = short .iter() - .map(|Flag { flag, .. }| quote!(String::from(#flag))) + .map(|Flag { flag, value }| { + let flag = flag.to_string(); + let value = match value { + Value::No => quote!(::uutils_args_complete::Value::No), + Value::Optional(name) => quote!(::uutils_args_complete::Value::Optional(#name)), + Value::Required(name) => quote!(::uutils_args_complete::Value::Optional(#name)), + }; + quote!(::uutils_args_complete::Flag { + flag: #flag, + value: #value + }) + }) .collect(); let long: Vec<_> = long .iter() - .map(|Flag { flag, .. }| quote!(String::from(#flag))) + .map(|Flag { flag, value }| { + let value = match value { + Value::No => quote!(::uutils_args_complete::Value::No), + Value::Optional(name) => quote!(::uutils_args_complete::Value::Optional(#name)), + Value::Required(name) => quote!(::uutils_args_complete::Value::Optional(#name)), + }; + quote!(::uutils_args_complete::Flag { + flag: #flag, + value: #value + }) + }) .collect(); let hint = if let Some(ty) = field { @@ -55,20 +76,20 @@ pub fn complete(args: &[Argument], file: &Option) -> TokenStream { }; arg_specs.push(quote!( - Arg { + ::uutils_args_complete::Arg { short: vec![#(#short),*], long: vec![#(#long),*], - help: String::from(#help), + help: #help, value: #hint, } )) } - quote!(Command { - name: String::from(option_env!("CARGO_BIN_NAME").unwrap_or(env!("CARGO_PKG_NAME"))), - summary: String::from(#summary), - after_options: String::from(#after_options), - version: String::from(env!("CARGO_PKG_VERSION")), + quote!(::uutils_args_complete::Command { + name: option_env!("CARGO_BIN_NAME").unwrap_or(env!("CARGO_PKG_NAME")), + summary: #summary, + after_options: #after_options, + version: env!("CARGO_PKG_VERSION"), args: vec![#(#arg_specs),*] }) } diff --git a/derive/src/lib.rs b/derive/src/lib.rs index e855127..cd8ccbf 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -107,8 +107,7 @@ pub fn arguments(input: TokenStream) -> TokenStream { } #[cfg(feature = "complete")] - fn complete() -> ::uutils_args_complete::Command { - use ::uutils_args_complete::{Command, Arg, ValueHint}; + fn complete() -> ::uutils_args_complete::Command<'static> { use ::uutils_args::Value; #complete_command } diff --git a/src/lib.rs b/src/lib.rs index 22d124b..5ec42d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,7 @@ pub trait Arguments: Sized { } #[cfg(feature = "complete")] - fn complete() -> uutils_args_complete::Command; + fn complete() -> uutils_args_complete::Command<'static>; } /// An iterator over arguments.