Skip to content

Commit

Permalink
add value names for flags in md and man
Browse files Browse the repository at this point in the history
  • Loading branch information
tertsdiepraam committed Dec 9, 2023
1 parent c4cb26c commit cab0fb2
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 60 deletions.
42 changes: 27 additions & 15 deletions complete/src/fish.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
50 changes: 40 additions & 10 deletions complete/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Arg>,
pub struct Command<'a> {
pub name: &'a str,
pub summary: &'a str,
pub version: &'a str,
pub after_options: &'a str,
pub args: Vec<Arg<'a>>,
}

/// 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<String>,
pub long: Vec<String>,
pub help: String,
pub struct Arg<'a> {
pub short: Vec<Flag<'a>>,
pub long: Vec<Flag<'a>>,
pub help: &'a str,
pub value: Option<ValueHint>,
}

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<String>),
Expand Down
43 changes: 34 additions & 9 deletions complete/src/man.rs
Original file line number Diff line number Diff line change
@@ -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()
}
24 changes: 17 additions & 7 deletions complete/src/md.rs
Original file line number Diff line number Diff line change
@@ -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
}
Expand Down Expand Up @@ -40,12 +40,22 @@ fn options(c: &Command) -> String {

let mut flags = Vec::new();

for long in &arg.long {
flags.push(format!("<code>--{long}</code>"));
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!("<code>--{flag}{value_str}</code>"));
}

for short in &arg.short {
flags.push(format!("<code>-{short}</code>"))
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!("<code>-{flag}{value_str}</code>"));
}

out.push_str(&flags.join(", "));
Expand Down
12 changes: 6 additions & 6 deletions complete/src/zsh.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
// 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 {
let mut out = String::new();
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
Expand Down
41 changes: 31 additions & 10 deletions derive/src/complete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use crate::{
argument::{ArgType, Argument},
flags::{Flag, Flags},
flags::{Flag, Flags, Value},
};
use proc_macro2::TokenStream;
use quote::quote;
Expand Down Expand Up @@ -40,12 +40,33 @@ pub fn complete(args: &[Argument], file: &Option<String>) -> 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 {
Expand All @@ -55,20 +76,20 @@ pub fn complete(args: &[Argument], file: &Option<String>) -> 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),*]
})
}
3 changes: 1 addition & 2 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit cab0fb2

Please sign in to comment.