Skip to content

Commit

Permalink
feat!: rewrite to be for ROFF, not manual pages
Browse files Browse the repository at this point in the history
While looking at generating manual pages from clap:App (see, for
example, <clap-rs/clap#3174>), I reviewed the
roff crate. I found several things that I think could be improved:

* It mixes concerns. Some of the crate functionality is aimed at
  generating source for manual pages, and not just generic documents
  using the ROFF language. This both makes the crate less useful (it's
  not suitable unless one uses the -man macro package for troff), and
  harder to use (it hard codes some assumptions about manual pages,
  such as what arguments .TH gets).
* The manual section is an integer, which prevents a suffix such as in
  the openssl(1ssl) manual page.
* There's no support for sub-sections.
* There's no escaping of text lines with leading dots. ROFF
  interprets them as control lines. If one is creating ROFF from, say,
  Markdown, one needs to handle the leading dots oneself.
* In general, the user of the crate needs to understand how to quote
  or escape things to avoid invoking ROFF accidentally.
* There's little to no documentation of the API.

This commit introduces a complete rewrite to address my concerns. I'm
afraid I could not come up with a reasonable sequence of small changes
to get where I want the crate to be. The changes are radical, and
break backwards compatibility, so the version number will need to be
bumped.

A summary of the changes:

* The crate now aims at generic ROFF support. There is no trace of
  manual page support anymore. For example,`struct Roff` knows nothing
  about manual page metadata, and there is no longer any support for
  sections.
* Control and text lines are given to a Roff explicitly, and handled
  separately, to get various kinds of escaping and quoting correct.
* If generating manual pages, a section is done by creating an "SH"
  control line, and then text lines for the content. Any higher level
  abstraction should, I think, be done above the level of the roff
  crate.
* Text lines consist of inline elements, to represent text in fonts.
* Text lines with leading periods are handled by inserting an
  invisible glyph.
* The Troffable trait is gone, as it didn't seem useful anymore.
* All public symbols have rudimentary documentation.
* There's some unit testing. The pre-existing manual page rendering
  test is kept, with no change to the expected output, though the code
  to generate is adapted to the new API.
  • Loading branch information
liwfi committed Dec 23, 2021
1 parent 6901966 commit 0102ced
Show file tree
Hide file tree
Showing 3 changed files with 492 additions and 158 deletions.
74 changes: 31 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,75 +12,63 @@
## Examples

```rust
use roff::*;

let page = Roff::new("corrupt", 1)
.section("name", &["corrupt - modify files by randomly changing bits"])
.section("SYNOPSIS", &[
bold("corrupt"), " ".into(),
"[".into(), bold("-n"), " ".into(), italic("BITS"), "]".into(),
" ".into(),
"[".into(), bold("--bits"), " ".into(), italic("BITS"), "]".into(),
" ".into(),
italic("file"), "...".into(),
])
.section("description", &[
bold("corrupt"),
" modifies files by toggling a randomly chosen bit.".into(),
])
.section("options", &[
list(
&[bold("-n"), ", ".into(), bold("--bits"), "=".into(), italic("BITS")],
&[
"Set the number of bits to modify. ",
"Default is one bit.",
]
),
]);
use roff::{bold, italic, roman, RoffBuilder};

fn main() {
let page = RoffBuilder::default()
.control("TH", ["CORRUPT", "1"])
.control("SH", ["NAME"])
.text([roman("corrupt - modify files by randomly changing bits")])
.control("SH", ["SYNOPSIS"])
.text([bold("corrupt"), roman(" ["), bold("-n"), roman(" "), italic("BITS"), roman("] ["),
bold("--bits"), roman(" "), italic("BITS"), roman("] "), italic("FILE"), roman("..."),
])
.control("SH", ["DESCRIPTION"])
.text([bold("corrupt"), roman(" modifies files by toggling a randomly chosen bit.")])
.control("SH", ["OPTIONS"])
.control("TP", [])
.text([bold("-n"), roman(", "), bold("--bits"), roman("="), italic("BITS")])
.text([roman("Set the number of bits to modify. Default is one bit.")])
.build();
print!("{}", page.render());
```

Which outputs:
```troff
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH CORRUPT 1
.SH NAME
corrupt \- modify files by randomly changing bits
.SH SYNOPSIS
.B corrupt
[\fB\-n\fR \fIBITS\fR]
[\fB\-\-bits\fR \fIBITS\fR]
.IR file ...
\fBcorrupt\fR [\fB\-n\fR \fIBITS\fR] [\fB\-\-bits\fR \fIBITS\fR] \fIFILE\fR...
.SH DESCRIPTION
.B corrupt
modifies files by toggling a randomly chosen bit.
\fBcorrupt\fR modifies files by toggling a randomly chosen bit.
.SH OPTIONS
.TP
.BR \-n ", " \-\-bits =\fIBITS\fR
Set the number of bits to modify.
Default is one bit.
\fB\-n\fR, \fB\-\-bits\fR=\fIBITS\fR
Set the number of bits to modify. Default is one bit.
```

Which will be shown by the `man(1)` command as:

```txt
CORRUPT(1) General Commands Manual CORRUPT(1)
CORRUPT(1) General Commands Manual CORRUPT(1)
NAME
corrupt - modify files by randomly
changing bits
corrupt - modify files by randomly changing bits
SYNOPSIS
corrupt [-n BITS] [--bits BITS] file...
corrupt [-n BITS] [--bits BITS] FILE...
DESCRIPTION
corrupt modifies files by toggling a
randomly chosen bit.
corrupt modifies files by toggling a randomly chosen bit.
OPTIONS
-n, --bits=BITS
Set the number of bits to modify.
Default is one bit.
Set the number of bits to modify. Default is one bit.
CORRUPT(1)
CORRUPT(1)
```

## License
Expand Down
Loading

0 comments on commit 0102ced

Please sign in to comment.