docs: use clap_mangen and roff to generate manpage (#231)

This commit is contained in:
Blair Noctis 2023-10-03 06:44:47 +08:00 committed by GitHub
parent 05ee953299
commit 4ed733834f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 101 additions and 82 deletions

29
Cargo.lock generated
View file

@ -183,6 +183,16 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
[[package]]
name = "clap_mangen"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b44f35c514163027542f7147797ff930523eea288e03642727348ef1a9666f6b"
dependencies = [
"clap",
"roff",
]
[[package]]
name = "colorchoice"
version = "1.0.0"
@ -397,15 +407,6 @@ version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "man"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebf5fa795187a80147b1ac10aaedcf5ffd3bbeb1838bda61801a1c9ad700a1c9"
dependencies = [
"roff",
]
[[package]]
name = "memchr"
version = "2.6.3"
@ -554,9 +555,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "roff"
version = "0.1.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33e4fb37ba46888052c763e4ec2acfedd8f00f62897b630cadb6298b833675e"
checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
[[package]]
name = "rustix"
@ -608,6 +609,7 @@ dependencies = [
"anyhow",
"assert_cmd",
"clap",
"clap_mangen",
"globwalk",
"ignore",
"is-terminal",
@ -837,9 +839,10 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "xtask"
version = "0.1.0"
version = "0.7.6"
dependencies = [
"clap",
"clap_complete",
"man",
"clap_mangen",
"roff",
]

View file

@ -1,4 +1,5 @@
[workspace]
package.version = "0.7.6"
members = [
".",
"xtask",
@ -6,7 +7,7 @@ members = [
[package]
name = "sd"
version = "0.7.6"
version.workspace = true
edition = "2018"
authors = ["Gregory <gregory.mkv@gmail.com>"]
description = "An intuitive find & replace CLI"
@ -33,6 +34,7 @@ clap = { version = "4.4.3", features = ["derive", "deprecated", "wrap_help"] }
[dev-dependencies]
assert_cmd = "2.0.12"
anyhow = "1.0.75"
clap_mangen = "0.2.14"
[profile.release]
opt-level = 3

View file

@ -2,6 +2,7 @@ use clap::Parser;
#[derive(Parser, Debug)]
#[command(
name = "sd",
author,
version,
about,
@ -40,10 +41,15 @@ pub struct Options {
/** Regex flags. May be combined (like `-f mc`).
c - case-sensitive
e - disable multi-line matching
i - case-insensitive
m - multi-line matching
s - make `.` match newlines
w - match full words only
*/
pub flags: Option<String>,
@ -56,7 +62,8 @@ w - match full words only
pub replace_with: String,
/// The path to file(s). This is optional - sd can also read from STDIN.
///{n}{n}Note: sd modifies files in-place by default. See documentation for
///
/// Note: sd modifies files in-place by default. See documentation for
/// examples.
pub files: Vec<std::path::PathBuf>,
}

View file

@ -1,10 +1,11 @@
[package]
name = "xtask"
version = "0.1.0"
version.workspace = true
edition = "2021"
publish = false
[dependencies]
clap = "4.4.3"
clap_complete = "4.4.1"
man = "0.3.0"
clap_mangen = "0.2.14"
roff = "0.2.1"

View file

@ -1,10 +1,13 @@
include!("../../src/cli.rs");
mod sd {
include!("../../src/cli.rs");
}
use sd::Options;
use std::{fs, path::Path};
use clap::{CommandFactory, ValueEnum};
use clap_complete::{generate_to, Shell};
use man::prelude::*;
use roff::{bold, roman, Roff};
pub fn gen() {
let gen_dir = Path::new("gen");
@ -24,70 +27,73 @@ fn gen_shell(base_dir: &Path) {
fn gen_man(base_dir: &Path) {
let man_path = base_dir.join("sd.1");
let cmd = Options::command();
let mut buffer: Vec<u8> = Vec::new();
let page = Manual::new("sd")
.flag(
Flag::new()
.short("-p")
.long("--preview")
.help("Emit the replacement to STDOUT"),
)
.flag(
Flag::new()
.short("-s")
.long("--string-mode")
.help("Treat expressions as non-regex strings."),
)
.flag(Flag::new().short("-f").long("--flags").help(
r#"Regex flags. May be combined (like `-f mc`).
let man = clap_mangen::Man::new(cmd);
man.render_title(&mut buffer)
.expect("failed to render title section");
man.render_name_section(&mut buffer)
.expect("failed to render name section");
man.render_synopsis_section(&mut buffer)
.expect("failed to render synopsis section");
man.render_description_section(&mut buffer)
.expect("failed to render description section");
man.render_options_section(&mut buffer)
.expect("failed to render options section");
c - case-sensitive
i - case-insensitive
m - multi-line matching
w - match full words only
"#,
))
.arg(Arg::new("find"))
.arg(Arg::new("replace_with"))
.arg(Arg::new("[FILES]"))
.example(
Example::new()
.text("String-literal mode")
.command(
"echo 'lots((([]))) of special chars' | sd -s '((([])))' \
''",
)
.output("lots of special chars"),
)
.example(
Example::new()
.text("Regex use. Let's trim some trailing whitespace")
.command("echo 'lorem ipsum 23 ' | sd '\\s+$' ''")
.output("lorem ipsum 23"),
)
.example(
Example::new()
.text("Indexed capture groups")
.command(r#"echo 'cargo +nightly watch' | sd '(\w+)\s+\+(\w+)\s+(\w+)' 'cmd: $1, channel: $2, subcmd: $3'"#)
.output("cmd: cargo, channel: nightly, subcmd: watch")
)
.example(
Example::new()
.text("Named capture groups")
.command(r#"echo "123.45" | sd '(?P<dollars>\d+)\.(?P<cents>\d+)' '$dollars dollars and $cents cents'"#)
.output("123 dollars and 45 cents")
)
.example(
Example::new()
.text("Find & replace in file")
.command(r#"sd 'window.fetch' 'fetch' http.js"#)
)
.example(
Example::new()
.text("Find & replace from STDIN an emit to STDOUT")
.command(r#"sd 'window.fetch' 'fetch' < http.js"#)
)
.render();
let statuses = [
("0", "Successful program execution."),
("1", "Unsuccessful program execution."),
("101", "The program panicked."),
];
let mut sect = Roff::new();
sect.control("SH", ["EXIT STATUS"]);
for (code, reason) in statuses {
sect.control("IP", [code]).text([roman(reason)]);
}
sect.to_writer(&mut buffer)
.expect("failed to render exit status section");
std::fs::write(man_path, page).unwrap();
let examples = [
// (description, command, result), result can be empty
(
"String-literal mode",
"echo 'lots((([]))) of special chars' | sd -s '((([])))'",
"lots of special chars",
),
(
"Regex use. Let's trim some trailing whitespace",
"echo 'lorem ipsum 23 ' | sd '\\s+$' ''",
"lorem ipsum 23",
),
(
"Indexed capture groups",
r"echo 'cargo +nightly watch' | sd '(\w+)\s+\+(\w+)\s+(\w+)' 'cmd: $1, channel: $2, subcmd: $3'",
"123 dollars and 45 cents",
),
(
"Find & replace in file",
r#"sd 'window.fetch' 'fetch' http.js"#,
"",
),
(
"Find & replace from STDIN an emit to STDOUT",
r#"sd 'window.fetch' 'fetch' < http.js"#,
"",
),
];
let mut sect = Roff::new();
sect.control("SH", ["EXAMPLES"]);
for (desc, command, result) in examples {
sect.control("TP", [])
.text([roman(desc)])
.text([bold(format!("$ {}", command))])
.control("br", [])
.text([roman(result)]);
}
sect.to_writer(&mut buffer)
.expect("failed to render example section");
std::fs::write(man_path, buffer).expect("failed to write manpage");
}