sequoia_man/
lib.rs

1/// Generate Unix manual pages for sq from its `clap::Command` value.
2///
3/// A Unix manual page is a document marked up with the
4/// [troff](https://en.wikipedia.org/wiki/Troff) language. The troff
5/// markup is the source code for the page, and is formatted and
6/// displayed using the "man" command.
7///
8/// Troff is a child of the 1970s and is one of the earlier markup
9/// languages. It has little resemblance to markup languages born in
10/// the 21st century, such as Markdown. However, it's not actually
11/// difficult, merely old, and sometimes weird. Some of the design of
12/// the troff language was dictated by the constraints of 1970s
13/// hardware, programming languages, and fashions in programming. Let
14/// not those scare you.
15///
16/// The troff language supports "macros", a way to define new commands
17/// based on built-in commands. There are a number of popular macro
18/// packages for various purposes. One of the most popular ones for
19/// manual pages is called "man", and this module generates manual
20/// pages for that package. It's supported by the "man" command on all
21/// Unix systems.
22///
23/// Note that this module doesn't aim to be a generic manual page
24/// generator. The scope is specifically the Sequoia sq command.
25
26use std::env;
27use std::fs;
28use std::path::Path;
29use std::path::PathBuf;
30
31use anyhow::Context;
32
33pub mod man;
34
35type Result<T, E=anyhow::Error> = std::result::Result<T, E>;
36
37/// Variable name to control the asset out directory with.
38pub const ASSET_OUT_DIR: &str = "ASSET_OUT_DIR";
39
40/// Returns the directory to write the given assets to.
41///
42/// For man pages, this would usually be `man-pages`.
43///
44/// The base directory is takens from the environment variable
45/// [`ASSET_OUT_DIR`] or, if that is not set, cargo's [`OUT_DIR`]:
46///
47/// > OUT_DIR — If the package has a build script, this is set to the
48/// > folder where the build script should place its output. See below
49/// > for more information. (Only set during compilation.)
50///
51/// [`OUT_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
52///
53/// This function panics if neither environment variable is set.
54pub fn asset_out_dir(asset: &str) -> Result<PathBuf> {
55    println!("cargo:rerun-if-env-changed={}", ASSET_OUT_DIR);
56    let outdir: PathBuf =
57        env::var_os(ASSET_OUT_DIR).unwrap_or_else(
58            || env::var_os("OUT_DIR").expect("OUT_DIR not set")).into();
59    if outdir.exists() && ! outdir.is_dir() {
60        return Err(anyhow::anyhow!("{}={:?} is not a directory",
61                                   ASSET_OUT_DIR, outdir));
62    }
63
64    let path = outdir.join(asset);
65    fs::create_dir_all(&path)?;
66    Ok(path)
67}
68
69/// pandoc helper file to convert a man page to HTML.
70pub const MAN_PANDOC_LUA: &[u8] = include_bytes!("man-pandoc.lua");
71
72/// pandoc helper file to convert a man page to HTML.
73pub const MAN_PANDOC_INC_HTML: &[u8] = include_bytes!("man-pandoc.inc.html");
74
75/// Generates man pages.
76///
77/// `asset_dir` is the directory where the man pages will be written.
78///
79/// `version` is the bare version string, which is usually obtained
80/// from `env!("CARGO_PKG_VERSION")`.
81///
82/// If `extra_version` is `Some`, then the version is created `version
83/// (extra_version)`.
84///
85/// The helper files `man-pandoc.lua`, `man-pandoc.inc.html` and
86/// `man2html.sh`, will also be written to the directory.
87///
88/// If you define a data type `Cli`, then you would do:
89///
90/// ```no_run
91/// use clap::CommandFactory;
92/// use clap::Parser;
93/// #
94///
95/// #[derive(Parser, Debug)]
96/// #[clap(
97///    name = "sq",
98///    about = "A command-line frontend for Sequoia, an implementation of OpenPGP")]
99/// struct Cli {
100///     // ...
101/// }
102///
103/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
104/// let dir = sequoia_man::asset_out_dir("man-pages")?;
105/// let cli = Cli::command();
106/// sequoia_man::generate_man_pages(&dir, &cli, env!("CARGO_PKG_VERSION"), None)?;
107/// # Ok(())
108/// # }
109/// ```
110///
111/// To convert the man pages to HTML, run the `man2html.sh` script.
112///
113/// ```shell
114/// bash .../man-pages/man2html.sh
115/// ```
116pub fn generate_man_pages(asset_dir: &Path, cli: &clap::Command,
117                          version: &str, extra_version: Option<&str>)
118    -> Result<()>
119{
120    let mut man2html = String::new();
121
122    man2html.push_str("#! /bin/bash\n");
123    man2html.push_str("# Convert the man pages to HTML using pandoc.\n");
124    man2html.push_str("\n");
125    man2html.push_str("set -e\n\n");
126    man2html.push_str("cd $(dirname $0)\n\n");
127
128    man2html.push_str("for man_page in");
129
130    for man in man::manpages(cli, version, extra_version) {
131        man2html.push_str(&format!(" {}", man.filename().display()));
132        std::fs::write(asset_dir.join(man.filename()), man.troff_source())?;
133    }
134
135    man2html.push_str("\ndo\n");
136    man2html.push_str("    pandoc -s $man_page -L man-pandoc.lua -H man-pandoc.inc.html -o $man_page.html\n");
137    man2html.push_str("done\n");
138
139    let target = asset_dir.join("man-pandoc.lua");
140    std::fs::write(&target, MAN_PANDOC_LUA)
141        .with_context(|| format!("Writing {}", target.display()))?;
142
143    let target = asset_dir.join("man-pandoc.inc.html");
144    std::fs::write(&target, MAN_PANDOC_INC_HTML)
145        .with_context(|| format!("Writing {}", target.display()))?;
146
147    let target = asset_dir.join("man2html.sh");
148    std::fs::write(&target, man2html.as_bytes())
149        .with_context(|| format!("Writing {}", target.display()))?;
150
151    println!("cargo:warning=man pages written to {}", asset_dir.display());
152
153    Ok(())
154}