mirror of
https://github.com/uutils/coreutils
synced 2024-12-13 14:52:41 +00:00
uucore: add filename as argument in help_usage and help_section
uucore: make help_section and help_usage take an argument to select a file
This commit is contained in:
parent
c8e88e1898
commit
a3a69cf919
4 changed files with 176 additions and 59 deletions
|
@ -12,8 +12,8 @@ use uucore::{encoding::Format, error::UResult, help_section, help_usage};
|
|||
|
||||
pub mod base_common;
|
||||
|
||||
const ABOUT: &str = help_section!("about");
|
||||
const USAGE: &str = help_usage!();
|
||||
const ABOUT: &str = help_section!("about", "base32.md");
|
||||
const USAGE: &str = help_usage!("base32.md");
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
|
|
|
@ -13,8 +13,8 @@ use uucore::{encoding::Format, error::UResult, help_section, help_usage};
|
|||
|
||||
use std::io::{stdin, Read};
|
||||
|
||||
const ABOUT: &str = help_section!("about");
|
||||
const USAGE: &str = help_usage!();
|
||||
const ABOUT: &str = help_section!("about", "base64.md");
|
||||
const USAGE: &str = help_usage!("base64.md");
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
|
|
|
@ -22,9 +22,9 @@ pub mod format;
|
|||
pub mod options;
|
||||
mod units;
|
||||
|
||||
const ABOUT: &str = help_section!("about");
|
||||
const LONG_HELP: &str = help_section!("long help");
|
||||
const USAGE: &str = help_usage!();
|
||||
const ABOUT: &str = help_section!("about", "numfmt.md");
|
||||
const LONG_HELP: &str = help_section!("long help", "numfmt.md");
|
||||
const USAGE: &str = help_usage!("numfmt.md");
|
||||
|
||||
fn handle_args<'a>(args: impl Iterator<Item = &'a str>, options: &NumfmtOptions) -> UResult<()> {
|
||||
for l in args {
|
||||
|
|
|
@ -37,35 +37,114 @@ pub fn main(_args: TokenStream, stream: TokenStream) -> TokenStream {
|
|||
TokenStream::from(new)
|
||||
}
|
||||
|
||||
fn parse_help(section: &str) -> String {
|
||||
/// Get the usage from the "Usage" section in the help file.
|
||||
///
|
||||
/// The usage is assumed to be surrounded by markdown code fences. It may span
|
||||
/// multiple lines. The first word of each line is assumed to be the name of
|
||||
/// the util and is replaced by "{}" so that the output of this function can be
|
||||
/// used with `uucore::format_usage`.
|
||||
#[proc_macro]
|
||||
pub fn help_usage(input: TokenStream) -> TokenStream {
|
||||
let input: Vec<TokenTree> = input.into_iter().collect();
|
||||
let filename = get_argument(&input, 0, "filename");
|
||||
let text: String = parse_usage(&parse_help("usage", &filename));
|
||||
TokenTree::Literal(Literal::string(&text)).into()
|
||||
}
|
||||
|
||||
/// Reads a section from a file of the util as a `str` literal.
|
||||
///
|
||||
/// It reads from the file specified as the second argument, relative to the
|
||||
/// crate root. The contents of this file are read verbatim, without parsing or
|
||||
/// escaping. The name of the help file should match the name of the util.
|
||||
/// I.e. numfmt should have a file called `numfmt.md`. By convention, the file
|
||||
/// should start with a top-level section with the name of the util. The other
|
||||
/// sections must start with 2 `#` characters. Capitalization of the sections
|
||||
/// does not matter. Leading and trailing whitespace of each section will be
|
||||
/// removed.
|
||||
///
|
||||
/// Example:
|
||||
/// ```md
|
||||
/// # numfmt
|
||||
/// ## About
|
||||
/// Convert numbers from/to human-readable strings
|
||||
///
|
||||
/// ## Long help
|
||||
/// This text will be the long help
|
||||
/// ```
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// help_section!("about", "numfmt.md");
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn help_section(input: TokenStream) -> TokenStream {
|
||||
let input: Vec<TokenTree> = input.into_iter().collect();
|
||||
let section = get_argument(&input, 0, "section");
|
||||
let filename = get_argument(&input, 1, "filename");
|
||||
let text = parse_help(§ion, &filename);
|
||||
TokenTree::Literal(Literal::string(&text)).into()
|
||||
}
|
||||
|
||||
/// Get an argument from the input vector of `TokenTree`.
|
||||
///
|
||||
/// Asserts that the argument is a string literal and returns the string value,
|
||||
/// otherwise it panics with an error.
|
||||
fn get_argument(input: &[TokenTree], index: usize, name: &str) -> String {
|
||||
// Multiply by two to ignore the `','` in between the arguments
|
||||
let string = match &input.get(index * 2) {
|
||||
Some(TokenTree::Literal(lit)) => lit.to_string(),
|
||||
Some(_) => panic!("Argument {} should be a string literal.", index),
|
||||
None => panic!("Missing argument at index {} for {}", index, name),
|
||||
};
|
||||
|
||||
string
|
||||
.parse::<String>()
|
||||
.unwrap()
|
||||
.strip_prefix('"')
|
||||
.unwrap()
|
||||
.strip_suffix('"')
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Read the help file and extract a section
|
||||
fn parse_help(section: &str, filename: &str) -> String {
|
||||
let section = section.to_lowercase();
|
||||
let section = section.trim_matches('"');
|
||||
let mut content = String::new();
|
||||
let mut path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
|
||||
// The package name will be something like uu_numfmt, hence we split once
|
||||
// on '_' and take the second element. The help section should then be in a
|
||||
// file called numfmt.md
|
||||
path.push(format!(
|
||||
"{}.md",
|
||||
std::env::var("CARGO_PKG_NAME")
|
||||
.unwrap()
|
||||
.split_once('_')
|
||||
.unwrap()
|
||||
.1,
|
||||
));
|
||||
path.push(filename);
|
||||
|
||||
File::open(path)
|
||||
.unwrap()
|
||||
.read_to_string(&mut content)
|
||||
.unwrap();
|
||||
|
||||
parse_help_section(section, &content)
|
||||
}
|
||||
|
||||
/// Get a single section from content
|
||||
///
|
||||
/// The section must be a second level section (i.e. start with `##`).
|
||||
fn parse_help_section(section: &str, content: &str) -> String {
|
||||
fn is_section_header(line: &str, section: &str) -> bool {
|
||||
line.strip_prefix("##")
|
||||
.map_or(false, |l| l.trim().to_lowercase() == section)
|
||||
}
|
||||
|
||||
// We cannot distinguish between an empty or non-existing section below,
|
||||
// so we do a quick test to check whether the section exists to provide
|
||||
// a nice error message.
|
||||
if content.lines().all(|l| !is_section_header(l, section)) {
|
||||
panic!(
|
||||
"The section '{}' could not be found in the help file. Maybe it is spelled wrong?",
|
||||
section
|
||||
)
|
||||
}
|
||||
|
||||
content
|
||||
.lines()
|
||||
.skip_while(|&l| {
|
||||
l.strip_prefix("##")
|
||||
.map_or(true, |l| l.trim().to_lowercase() != section)
|
||||
})
|
||||
.skip_while(|&l| !is_section_header(l, section))
|
||||
.skip(1)
|
||||
.take_while(|l| !l.starts_with("##"))
|
||||
.collect::<Vec<_>>()
|
||||
|
@ -74,15 +153,13 @@ fn parse_help(section: &str) -> String {
|
|||
.to_string()
|
||||
}
|
||||
|
||||
/// Get the usage from the "Usage" section in the help file.
|
||||
/// Parses a markdown code block into a usage string
|
||||
///
|
||||
/// The usage is assumed to be surrounded by markdown code fences. It may span
|
||||
/// multiple lines. The first word of each line is assumed to be the name of
|
||||
/// the util and is replaced by "{}" so that the output of this function can be
|
||||
/// used with `uucore::format_usage`.
|
||||
#[proc_macro]
|
||||
pub fn help_usage(_input: TokenStream) -> TokenStream {
|
||||
let text: String = parse_help("usage")
|
||||
/// The code fences are removed and the name of the util is replaced
|
||||
/// with `{}` so that it can be replaced with the appropriate name
|
||||
/// at runtime.
|
||||
fn parse_usage(content: &str) -> String {
|
||||
content
|
||||
.strip_suffix("```")
|
||||
.unwrap()
|
||||
.lines()
|
||||
|
@ -96,34 +173,74 @@ pub fn help_usage(_input: TokenStream) -> TokenStream {
|
|||
"{}".to_string()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
TokenTree::Literal(Literal::string(&text)).into()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Reads a section from the help file of the util as a `str` literal.
|
||||
///
|
||||
/// It is read verbatim, without parsing or escaping. The name of the help file
|
||||
/// should match the name of the util. I.e. numfmt should have a file called
|
||||
/// `numfmt.md`. By convention, the file should start with a top-level section
|
||||
/// with the name of the util. The other sections must start with 2 `#`
|
||||
/// characters. Capitalization of the sections does not matter. Leading and
|
||||
/// trailing whitespace will be removed. Example:
|
||||
/// ```md
|
||||
/// # numfmt
|
||||
/// ## About
|
||||
/// Convert numbers from/to human-readable strings
|
||||
///
|
||||
/// ## Long help
|
||||
/// This text will be the long help
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn help_section(input: TokenStream) -> TokenStream {
|
||||
let input: Vec<TokenTree> = input.into_iter().collect();
|
||||
let value = match &input.get(0) {
|
||||
Some(TokenTree::Literal(literal)) => literal.to_string(),
|
||||
_ => panic!("Input to help_section should be a string literal!"),
|
||||
};
|
||||
let input_str: String = value.parse().unwrap();
|
||||
let text = parse_help(&input_str);
|
||||
TokenTree::Literal(Literal::string(&text)).into()
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{parse_help_section, parse_usage};
|
||||
|
||||
#[test]
|
||||
fn section_parsing() {
|
||||
let input = "\
|
||||
# ls\n\
|
||||
## some section\n\
|
||||
This is some section\n\
|
||||
\n\
|
||||
## ANOTHER SECTION
|
||||
This is the other section\n\
|
||||
with multiple lines\n";
|
||||
|
||||
assert_eq!(
|
||||
parse_help_section("some section", input),
|
||||
"This is some section"
|
||||
);
|
||||
assert_eq!(
|
||||
parse_help_section("another section", input),
|
||||
"This is the other section\nwith multiple lines"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn section_parsing_panic() {
|
||||
let input = "\
|
||||
# ls\n\
|
||||
## some section\n\
|
||||
This is some section\n\
|
||||
\n\
|
||||
## ANOTHER SECTION
|
||||
This is the other section\n\
|
||||
with multiple lines\n";
|
||||
parse_help_section("non-existent section", input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_parsing() {
|
||||
let input = "\
|
||||
# ls\n\
|
||||
## Usage\n\
|
||||
```\n\
|
||||
ls -l\n\
|
||||
```\n\
|
||||
## some section\n\
|
||||
This is some section\n\
|
||||
\n\
|
||||
## ANOTHER SECTION
|
||||
This is the other section\n\
|
||||
with multiple lines\n";
|
||||
|
||||
assert_eq!(parse_usage(&parse_help_section("usage", input)), "{} -l",);
|
||||
|
||||
assert_eq!(
|
||||
parse_usage(
|
||||
"\
|
||||
```\n\
|
||||
util [some] [options]\n\
|
||||
```\
|
||||
"
|
||||
),
|
||||
"{} [some] [options]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue