diff options
Diffstat (limited to 'build.rs')
-rw-r--r-- | build.rs | 128 |
1 files changed, 124 insertions, 4 deletions
@@ -4,11 +4,15 @@ extern crate clap; extern crate lazy_static; use std::env; -use std::fs; +use std::fs::{self, File}; +use std::io::{self, Read, Write}; +use std::path::Path; use std::process; use clap::Shell; +use app::{RGArg, RGArgKind}; + #[allow(dead_code)] #[path = "src/app.rs"] mod app; @@ -27,6 +31,9 @@ fn main() { } }; fs::create_dir_all(&outdir).unwrap(); + if let Err(err) = generate_man_page(&outdir) { + eprintln!("failed to generate man page: {}", err); + } // Use clap to build completion files. let mut app = app::app(); @@ -37,11 +44,124 @@ fn main() { // are manually maintained in `complete/_rg`. // Make the current git hash available to the build. + if let Some(rev) = git_revision_hash() { + println!("cargo:rustc-env=RIPGREP_BUILD_GIT_HASH={}", rev); + } +} + +fn git_revision_hash() -> Option<String> { let result = process::Command::new("git") .args(&["rev-parse", "--short=10", "HEAD"]) .output(); - if let Ok(output) = result { - let hash = String::from_utf8_lossy(&output.stdout); - println!("cargo:rustc-env=RIPGREP_BUILD_GIT_HASH={}", hash); + result.ok().map(|output| { + String::from_utf8_lossy(&output.stdout).trim().to_string() + }) +} + +fn generate_man_page<P: AsRef<Path>>(outdir: P) -> io::Result<()> { + // If asciidoc isn't installed, then don't do anything. + if let Err(err) = process::Command::new("a2x").output() { + eprintln!("Could not run 'a2x' binary, skipping man page generation."); + eprintln!("Error from running 'a2x': {}", err); + return Ok(()); + } + let outdir = outdir.as_ref(); + let cwd = env::current_dir()?; + let tpl_path = cwd.join("doc").join("rg.1.txt.tpl"); + let txt_path = outdir.join("rg.1.txt"); + + let mut tpl = String::new(); + File::open(&tpl_path)?.read_to_string(&mut tpl)?; + tpl = tpl.replace("{OPTIONS}", &formatted_options()?); + + let githash = git_revision_hash(); + let githash = githash.as_ref().map(|x| &**x); + tpl = tpl.replace("{VERSION}", &app::long_version(githash)); + + File::create(&txt_path)?.write_all(tpl.as_bytes())?; + let result = process::Command::new("a2x") + .arg("--no-xmllint") + .arg("--doctype").arg("manpage") + .arg("--format").arg("manpage") + .arg(&txt_path) + .spawn()? + .wait()?; + if !result.success() { + let msg = format!("'a2x' failed with exit code {:?}", result.code()); + return Err(ioerr(msg)); + } + Ok(()) +} + +fn formatted_options() -> io::Result<String> { + let mut args = app::all_args_and_flags(); + args.sort_by(|x1, x2| x1.name.cmp(&x2.name)); + + let mut formatted = vec![]; + for arg in args { + // ripgrep only has two positional arguments, and probably will only + // ever have two positional arguments, so we just hardcode them into + // the template. + if let app::RGArgKind::Positional{..} = arg.kind { + continue; + } + formatted.push(formatted_arg(&arg)?); } + Ok(formatted.join("\n\n")) +} + +fn formatted_arg(arg: &RGArg) -> io::Result<String> { + match arg.kind { + RGArgKind::Positional{..} => panic!("unexpected positional argument"), + RGArgKind::Switch { long, short, multiple } => { + let mut out = vec![]; + + let mut header = format!("--{}", long); + if let Some(short) = short { + header = format!("-{}, {}", short, header); + } + if multiple { + header = format!("*{}* ...::", header); + } else { + header = format!("*{}*::", header); + } + writeln!(out, "{}", header)?; + writeln!(out, "{}", formatted_doc_txt(arg)?)?; + + Ok(String::from_utf8(out).unwrap()) + } + RGArgKind::Flag { long, short, value_name, multiple, .. } => { + let mut out = vec![]; + + let mut header = format!("--{}", long); + if let Some(short) = short { + header = format!("-{}, {}", short, header); + } + if multiple { + header = format!("*{}* _{}_ ...::", header, value_name); + } else { + header = format!("*{}* _{}_::", header, value_name); + } + writeln!(out, "{}", header)?; + writeln!(out, "{}", formatted_doc_txt(arg)?)?; + + Ok(String::from_utf8(out).unwrap()) + } + } +} + +fn formatted_doc_txt(arg: &RGArg) -> io::Result<String> { + let paragraphs: Vec<&str> = arg.doc_long.split("\n\n").collect(); + if paragraphs.is_empty() { + return Err(ioerr(format!("missing docs for --{}", arg.name))); + } + let first = format!(" {}", paragraphs[0].replace("\n", "\n ")); + if paragraphs.len() == 1 { + return Ok(first); + } + Ok(format!("{}\n+\n{}", first, paragraphs[1..].join("\n+\n"))) +} + +fn ioerr(msg: String) -> io::Error { + io::Error::new(io::ErrorKind::Other, msg) } |