mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-03-23 04:34:39 +02:00
111 lines
3.6 KiB
Rust
111 lines
3.6 KiB
Rust
|
/*!
|
||
|
Provides routines for generating ripgrep's man page in `roff` format.
|
||
|
*/
|
||
|
|
||
|
use std::{collections::BTreeMap, fmt::Write};
|
||
|
|
||
|
use crate::flags::{defs::FLAGS, doc::version, Flag};
|
||
|
|
||
|
const TEMPLATE: &'static str = include_str!("template.rg.1");
|
||
|
|
||
|
/// Wraps `std::write!` and asserts there is no failure.
|
||
|
///
|
||
|
/// We only write to `String` in this module.
|
||
|
macro_rules! write {
|
||
|
($($tt:tt)*) => { std::write!($($tt)*).unwrap(); }
|
||
|
}
|
||
|
|
||
|
/// Wraps `std::writeln!` and asserts there is no failure.
|
||
|
///
|
||
|
/// We only write to `String` in this module.
|
||
|
macro_rules! writeln {
|
||
|
($($tt:tt)*) => { std::writeln!($($tt)*).unwrap(); }
|
||
|
}
|
||
|
|
||
|
/// Returns a `roff` formatted string corresponding to ripgrep's entire man
|
||
|
/// page.
|
||
|
pub(crate) fn generate() -> String {
|
||
|
let mut cats = BTreeMap::new();
|
||
|
for flag in FLAGS.iter().copied() {
|
||
|
let mut cat = cats.entry(flag.doc_category()).or_insert(String::new());
|
||
|
if !cat.is_empty() {
|
||
|
writeln!(cat, ".sp");
|
||
|
}
|
||
|
generate_flag(flag, &mut cat);
|
||
|
}
|
||
|
|
||
|
let mut out = TEMPLATE.replace("!!VERSION!!", &version::generate_digits());
|
||
|
for (cat, value) in cats.iter() {
|
||
|
let var = format!("!!{name}!!", name = cat.as_str());
|
||
|
out = out.replace(&var, value);
|
||
|
}
|
||
|
out
|
||
|
}
|
||
|
|
||
|
/// Writes `roff` formatted documentation for `flag` to `out`.
|
||
|
fn generate_flag(flag: &'static dyn Flag, out: &mut String) {
|
||
|
if let Some(byte) = flag.name_short() {
|
||
|
let name = char::from(byte);
|
||
|
write!(out, r"\fB\-{name}\fP");
|
||
|
if let Some(var) = flag.doc_variable() {
|
||
|
write!(out, r" \fI{var}\fP");
|
||
|
}
|
||
|
write!(out, r", ");
|
||
|
}
|
||
|
|
||
|
let name = flag.name_long();
|
||
|
write!(out, r"\fB\-\-{name}\fP");
|
||
|
if let Some(var) = flag.doc_variable() {
|
||
|
write!(out, r"=\fI{var}\fP");
|
||
|
}
|
||
|
write!(out, "\n");
|
||
|
|
||
|
writeln!(out, ".RS 4");
|
||
|
let doc = flag.doc_long().trim();
|
||
|
// Convert \flag{foo} into something nicer.
|
||
|
let doc = super::render_custom_markup(doc, "flag", |name, out| {
|
||
|
let Some(flag) = crate::flags::parse::lookup(name) else {
|
||
|
unreachable!(r"found unrecognized \flag{{{name}}} in roff docs")
|
||
|
};
|
||
|
out.push_str(r"\fB");
|
||
|
if let Some(name) = flag.name_short() {
|
||
|
write!(out, r"\-{}/", char::from(name));
|
||
|
}
|
||
|
write!(out, r"\-\-{}", flag.name_long());
|
||
|
out.push_str(r"\fP");
|
||
|
});
|
||
|
// Convert \flag-negate{foo} into something nicer.
|
||
|
let doc = super::render_custom_markup(&doc, "flag-negate", |name, out| {
|
||
|
let Some(flag) = crate::flags::parse::lookup(name) else {
|
||
|
unreachable!(
|
||
|
r"found unrecognized \flag-negate{{{name}}} in roff docs"
|
||
|
)
|
||
|
};
|
||
|
let Some(name) = flag.name_negated() else {
|
||
|
let long = flag.name_long();
|
||
|
unreachable!(
|
||
|
"found \\flag-negate{{{long}}} in roff docs but \
|
||
|
{long} does not have a negation"
|
||
|
);
|
||
|
};
|
||
|
out.push_str(r"\fB");
|
||
|
write!(out, r"\-\-{name}");
|
||
|
out.push_str(r"\fP");
|
||
|
});
|
||
|
writeln!(out, "{doc}");
|
||
|
if let Some(negated) = flag.name_negated() {
|
||
|
// Flags that can be negated that aren't switches, like
|
||
|
// --context-separator, are somewhat weird. Because of that, the docs
|
||
|
// for those flags should discuss the semantics of negation explicitly.
|
||
|
// But for switches, the behavior is always the same.
|
||
|
if flag.is_switch() {
|
||
|
writeln!(out, ".sp");
|
||
|
writeln!(
|
||
|
out,
|
||
|
r"This flag can be disabled with \fB\-\-{negated}\fP."
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
writeln!(out, ".RE");
|
||
|
}
|