mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +00:00
Merge pull request #4158 from tertsdiepraam/markdown-in-docs-v1
Markdown in docs v1
This commit is contained in:
commit
3ca6139e0f
5 changed files with 131 additions and 56 deletions
|
@ -91,6 +91,15 @@ fn main() -> io::Result<()> {
|
|||
continue;
|
||||
}
|
||||
let p = format!("docs/src/utils/{}.md", name);
|
||||
|
||||
let markdown = File::open(format!("src/uu/{name}/{name}.md"))
|
||||
.and_then(|mut f: File| {
|
||||
let mut s = String::new();
|
||||
f.read_to_string(&mut s)?;
|
||||
Ok(s)
|
||||
})
|
||||
.ok();
|
||||
|
||||
if let Ok(f) = File::create(&p) {
|
||||
MDWriter {
|
||||
w: Box::new(f),
|
||||
|
@ -98,6 +107,7 @@ fn main() -> io::Result<()> {
|
|||
name,
|
||||
tldr_zip: &mut tldr_zip,
|
||||
utils_per_platform: &utils_per_platform,
|
||||
markdown,
|
||||
}
|
||||
.markdown()?;
|
||||
println!("Wrote to '{}'", p);
|
||||
|
@ -115,6 +125,7 @@ struct MDWriter<'a, 'b> {
|
|||
name: &'a str,
|
||||
tldr_zip: &'b mut Option<ZipArchive<File>>,
|
||||
utils_per_platform: &'b HashMap<&'b str, Vec<String>>,
|
||||
markdown: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> MDWriter<'a, 'b> {
|
||||
|
@ -124,6 +135,7 @@ impl<'a, 'b> MDWriter<'a, 'b> {
|
|||
self.usage()?;
|
||||
self.description()?;
|
||||
self.options()?;
|
||||
self.after_help()?;
|
||||
self.examples()
|
||||
}
|
||||
|
||||
|
@ -184,6 +196,10 @@ impl<'a, 'b> MDWriter<'a, 'b> {
|
|||
}
|
||||
|
||||
fn description(&mut self) -> io::Result<()> {
|
||||
if let Some(after_help) = self.markdown_section("about") {
|
||||
return writeln!(self.w, "\n\n{}", after_help);
|
||||
}
|
||||
|
||||
if let Some(about) = self
|
||||
.command
|
||||
.get_long_about()
|
||||
|
@ -195,6 +211,22 @@ impl<'a, 'b> MDWriter<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
fn after_help(&mut self) -> io::Result<()> {
|
||||
if let Some(after_help) = self.markdown_section("after help") {
|
||||
return writeln!(self.w, "\n\n{}", after_help);
|
||||
}
|
||||
|
||||
if let Some(after_help) = self
|
||||
.command
|
||||
.get_after_long_help()
|
||||
.or_else(|| self.command.get_after_help())
|
||||
{
|
||||
writeln!(self.w, "\n\n{}", after_help)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&mut self) -> io::Result<()> {
|
||||
if let Some(zip) = self.tldr_zip {
|
||||
let content = if let Some(f) =
|
||||
|
@ -295,6 +327,32 @@ impl<'a, 'b> MDWriter<'a, 'b> {
|
|||
}
|
||||
writeln!(self.w, "</dl>\n")
|
||||
}
|
||||
|
||||
fn markdown_section(&self, section: &str) -> Option<String> {
|
||||
let md = self.markdown.as_ref()?;
|
||||
let section = section.to_lowercase();
|
||||
|
||||
fn is_section_header(line: &str, section: &str) -> bool {
|
||||
line.strip_prefix("##")
|
||||
.map_or(false, |l| l.trim().to_lowercase() == section)
|
||||
}
|
||||
|
||||
let result = md
|
||||
.lines()
|
||||
.skip_while(|&l| !is_section_header(l, §ion))
|
||||
.skip(1)
|
||||
.take_while(|l| !l.starts_with("##"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
if result != "" {
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_zip_content(archive: &mut ZipArchive<impl Read + Seek>, name: &str) -> Option<String> {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## About
|
||||
|
||||
Print the value of EXPRESSION to standard output
|
||||
Print the value of `EXPRESSION` to standard output
|
||||
|
||||
## Usage
|
||||
```
|
||||
|
@ -12,48 +12,50 @@ expr [OPTIONS]
|
|||
|
||||
## After help
|
||||
|
||||
Print the value of EXPRESSION to standard output. A blank line below
|
||||
separates increasing precedence groups. EXPRESSION may be:
|
||||
Print the value of `EXPRESSION` to standard output. A blank line below
|
||||
separates increasing precedence groups.
|
||||
|
||||
ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2
|
||||
`EXPRESSION` may be:
|
||||
|
||||
ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0
|
||||
ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2
|
||||
|
||||
ARG1 < ARG2 ARG1 is less than ARG2
|
||||
ARG1 <= ARG2 ARG1 is less than or equal to ARG2
|
||||
ARG1 = ARG2 ARG1 is equal to ARG2
|
||||
ARG1 != ARG2 ARG1 is unequal to ARG2
|
||||
ARG1 >= ARG2 ARG1 is greater than or equal to ARG2
|
||||
ARG1 > ARG2 ARG1 is greater than ARG2
|
||||
ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0
|
||||
|
||||
ARG1 + ARG2 arithmetic sum of ARG1 and ARG2
|
||||
ARG1 - ARG2 arithmetic difference of ARG1 and ARG2
|
||||
ARG1 < ARG2 ARG1 is less than ARG2
|
||||
ARG1 <= ARG2 ARG1 is less than or equal to ARG2
|
||||
ARG1 = ARG2 ARG1 is equal to ARG2
|
||||
ARG1 != ARG2 ARG1 is unequal to ARG2
|
||||
ARG1 >= ARG2 ARG1 is greater than or equal to ARG2
|
||||
ARG1 > ARG2 ARG1 is greater than ARG2
|
||||
|
||||
ARG1 * ARG2 arithmetic product of ARG1 and ARG2
|
||||
ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2
|
||||
ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2
|
||||
ARG1 + ARG2 arithmetic sum of ARG1 and ARG2
|
||||
ARG1 - ARG2 arithmetic difference of ARG1 and ARG2
|
||||
|
||||
STRING : REGEXP anchored pattern match of REGEXP in STRING
|
||||
ARG1 * ARG2 arithmetic product of ARG1 and ARG2
|
||||
ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2
|
||||
ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2
|
||||
|
||||
match STRING REGEXP same as STRING : REGEXP
|
||||
substr STRING POS LENGTH substring of STRING, POS counted from 1
|
||||
index STRING CHARS index in STRING where any CHARS is found, or 0
|
||||
length STRING length of STRING
|
||||
+ TOKEN interpret TOKEN as a string, even if it is a
|
||||
keyword like 'match' or an operator like '/'
|
||||
STRING : REGEXP anchored pattern match of REGEXP in STRING
|
||||
|
||||
( EXPRESSION ) value of EXPRESSION
|
||||
match STRING REGEXP same as STRING : REGEXP
|
||||
substr STRING POS LENGTH substring of STRING, POS counted from 1
|
||||
index STRING CHARS index in STRING where any CHARS is found, or 0
|
||||
length STRING length of STRING
|
||||
+ TOKEN interpret TOKEN as a string, even if it is a
|
||||
keyword like 'match' or an operator like '/'
|
||||
|
||||
( EXPRESSION ) value of EXPRESSION
|
||||
|
||||
Beware that many operators need to be escaped or quoted for shells.
|
||||
Comparisons are arithmetic if both ARGs are numbers, else lexicographical.
|
||||
Pattern matches return the string matched between \( and \) or null; if
|
||||
\( and \) are not used, they return the number of characters matched or 0.
|
||||
|
||||
Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null
|
||||
or 0, 2 if EXPRESSION is syntactically invalid, and 3 if an error occurred.
|
||||
Exit status is `0` if `EXPRESSION` is neither null nor `0`, `1` if `EXPRESSION` is null
|
||||
or `0`, `2` if `EXPRESSION` is syntactically invalid, and `3` if an error occurred.
|
||||
|
||||
Environment variables:
|
||||
* EXPR_DEBUG_TOKENS=1 dump expression's tokens
|
||||
* EXPR_DEBUG_RPN=1 dump expression represented in reverse polish notation
|
||||
* EXPR_DEBUG_SYA_STEP=1 dump each parser step
|
||||
* EXPR_DEBUG_AST=1 dump expression represented abstract syntax tree"
|
||||
- `EXPR_DEBUG_TOKENS=1`: dump expression's tokens
|
||||
- `EXPR_DEBUG_RPN=1`: dump expression represented in reverse polish notation
|
||||
- `EXPR_DEBUG_SYA_STEP=1`: dump each parser step
|
||||
- `EXPR_DEBUG_AST=1`: dump expression represented abstract syntax tree
|
|
@ -10,38 +10,38 @@ numfmt [OPTION]... [NUMBER]...
|
|||
|
||||
Convert numbers from/to human-readable strings
|
||||
|
||||
## Long Help
|
||||
## After Help
|
||||
|
||||
UNIT options:
|
||||
none no auto-scaling is done; suffixes will trigger an error
|
||||
`UNIT` options:
|
||||
- `none`: no auto-scaling is done; suffixes will trigger an error
|
||||
- `auto`: accept optional single/two letter suffix:
|
||||
|
||||
auto accept optional single/two letter suffix:
|
||||
1K = 1000, 1Ki = 1024, 1M = 1000000, 1Mi = 1048576,
|
||||
|
||||
1K = 1000, 1Ki = 1024, 1M = 1000000, 1Mi = 1048576,
|
||||
- `si`: accept optional single letter suffix:
|
||||
|
||||
si accept optional single letter suffix:
|
||||
1K = 1000, 1M = 1000000, ...
|
||||
|
||||
1K = 1000, 1M = 1000000, ...
|
||||
- `iec`: accept optional single letter suffix:
|
||||
|
||||
iec accept optional single letter suffix:
|
||||
1K = 1024, 1M = 1048576, ...
|
||||
|
||||
1K = 1024, 1M = 1048576, ...
|
||||
- `iec-i`: accept optional two-letter suffix:
|
||||
|
||||
iec-i accept optional two-letter suffix:
|
||||
1Ki = 1024, 1Mi = 1048576, ...
|
||||
|
||||
1Ki = 1024, 1Mi = 1048576, ...
|
||||
`FIELDS` supports `cut(1)` style field ranges:
|
||||
|
||||
N N'th field, counted from 1
|
||||
N- from N'th field, to end of line
|
||||
N-M from N'th to M'th field (inclusive)
|
||||
-M from first to M'th field (inclusive)
|
||||
- all fields
|
||||
|
||||
FIELDS supports cut(1) style field ranges:
|
||||
N N'th field, counted from 1
|
||||
N- from N'th field, to end of line
|
||||
N-M from N'th to M'th field (inclusive)
|
||||
-M from first to M'th field (inclusive)
|
||||
- all fields
|
||||
Multiple fields/ranges can be separated with commas
|
||||
|
||||
FORMAT must be suitable for printing one floating-point argument '%f'.
|
||||
Optional quote (%'f) will enable --grouping (if supported by current locale).
|
||||
Optional width value (%10f) will pad output. Optional zero (%010f) width
|
||||
will zero pad the number. Optional negative values (%-10f) will left align.
|
||||
Optional precision (%.1f) will override the input determined precision.
|
||||
|
||||
`FORMAT` must be suitable for printing one floating-point argument `%f`.
|
||||
Optional quote (`%'f`) will enable --grouping (if supported by current locale).
|
||||
Optional width value (`%10f`) will pad output. Optional zero (`%010f`) width
|
||||
will zero pad the number. Optional negative values (`%-10f`) will left align.
|
||||
Optional precision (`%.1f`) will override the input determined precision.
|
||||
|
|
|
@ -24,7 +24,7 @@ pub mod options;
|
|||
mod units;
|
||||
|
||||
const ABOUT: &str = help_section!("about", "numfmt.md");
|
||||
const LONG_HELP: &str = help_section!("long help", "numfmt.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "numfmt.md");
|
||||
const USAGE: &str = help_usage!("numfmt.md");
|
||||
|
||||
fn handle_args<'a>(args: impl Iterator<Item = &'a str>, options: &NumfmtOptions) -> UResult<()> {
|
||||
|
@ -262,7 +262,7 @@ pub fn uu_app() -> Command {
|
|||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(AFTER_HELP)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.allow_negative_numbers(true)
|
||||
.infer_long_args(true)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright (C) ~ Roy Ivy III <rivy.dev@gmail.com>; MIT license
|
||||
// spell-checker:ignore backticks
|
||||
|
||||
extern crate proc_macro;
|
||||
use std::{fs::File, io::Read, path::PathBuf};
|
||||
|
@ -37,6 +38,19 @@ pub fn main(_args: TokenStream, stream: TokenStream) -> TokenStream {
|
|||
TokenStream::from(new)
|
||||
}
|
||||
|
||||
// FIXME: This is currently a stub. We could do much more here and could
|
||||
// even pull in a full markdown parser to get better results.
|
||||
/// Render markdown into a format that's easier to read in the terminal.
|
||||
///
|
||||
/// For now, all this function does is remove backticks.
|
||||
/// Some ideas for future improvement:
|
||||
/// - Render headings as bold
|
||||
/// - Convert triple backticks to indented
|
||||
/// - Printing tables in a nice format
|
||||
fn render_markdown(s: &str) -> String {
|
||||
s.replace('`', "")
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
@ -81,7 +95,8 @@ pub fn help_section(input: TokenStream) -> TokenStream {
|
|||
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()
|
||||
let rendered = render_markdown(&text);
|
||||
TokenTree::Literal(Literal::string(&rendered)).into()
|
||||
}
|
||||
|
||||
/// Get an argument from the input vector of `TokenTree`.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue