man: Support optional arguments to command line options.

This commit is contained in:
Justus Winter 2024-04-12 16:43:18 +02:00
parent a8f01ef1ae
commit 3e138d4c59
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386

View File

@ -26,6 +26,8 @@
use roff::{bold, italic, roman, Inline, Roff}; use roff::{bold, italic, roman, Inline, Roff};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use clap::builder::ValueRange;
/// The "manual" the manual page is meant for. The full Unix /// The "manual" the manual page is meant for. The full Unix
/// documentation is (or was) divided into separate manuals, some of /// documentation is (or was) divided into separate manuals, some of
/// which don't consist of manual pages. /// which don't consist of manual pages.
@ -507,10 +509,27 @@ struct CommandOption {
index: Option<usize>, index: Option<usize>,
short: Option<String>, short: Option<String>,
long: Option<String>, long: Option<String>,
value_names: Option<Vec<String>>, value_names: Values,
help: Option<String>, help: Option<String>,
} }
/// Formal arguments for a command option.
#[derive(Clone, Debug, PartialEq, Eq)]
enum Values {
/// This option does not take any arguments.
None,
/// This option takes the given arguments.
Some(Vec<String>),
/// This option optionally takes the given arguments.
///
/// XXX: Currently, this is all or nothing, i.e. either all
/// arguments are mandatory, or all are optional. Let's see
/// whether we need something more complicated.
Optional(Vec<String>),
}
impl CommandOption { impl CommandOption {
/// Return a key for sorting a list of options. /// Return a key for sorting a list of options.
/// ///
@ -537,14 +556,23 @@ impl CommandOption {
/// Create a `CommandOption` from a `clap::Arg`. /// Create a `CommandOption` from a `clap::Arg`.
fn from_arg(arg: &clap::Arg) -> Self { fn from_arg(arg: &clap::Arg) -> Self {
let value_names = if arg.get_num_args() let value_names = if arg.get_num_args()
.map(|r| r == clap::builder::ValueRange::EMPTY) .map(|r| r == ValueRange::EMPTY)
.unwrap_or(true) .unwrap_or(true)
{ {
None Values::None
} else if let Some(names) = arg.get_value_names() { } else if let Some(names) = arg.get_value_names() {
Some(names.iter().map(|name| name.to_string()).collect()) let names =
names.iter().map(|name| name.to_string()).collect();
if arg.get_num_args()
.map(|r| r.min_values() == 0).unwrap_or(false)
{
Values::Optional(names)
} else {
Values::Some(names)
}
} else { } else {
None Values::None
}; };
Self { Self {
@ -667,18 +695,29 @@ impl ManualPage {
line.push(bold(long)); line.push(bold(long));
} }
if let Some(values) = &opt.value_names { match &opt.value_names {
if (opt.short.is_some() || opt.long.is_some()) Values::None => (),
&& values.len() == 1 Values::Some(values) | Values::Optional(values) => {
{ if matches!(opt.value_names, Values::Optional(_)) {
line.push(roman("=")); line.push(roman("["));
line.push(italic(&values[0]));
} else {
for value in values {
line.push(roman(" "));
line.push(italic(value));
} }
}
if (opt.short.is_some() || opt.long.is_some())
&& values.len() == 1
{
line.push(roman("="));
line.push(italic(&values[0]));
} else {
for value in values {
line.push(roman(" "));
line.push(italic(value));
}
}
if matches!(opt.value_names, Values::Optional(_)) {
line.push(roman("]"));
}
},
} }
self.tagged_paragraph(line, &opt.help); self.tagged_paragraph(line, &opt.help);