1
Fork 0
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:
Sylvestre Ledru 2022-12-02 09:39:33 +01:00 committed by GitHub
commit 3ca6139e0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 131 additions and 56 deletions

View file

@ -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, &section))
.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> {

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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(&section, &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`.