My experience writing an mdoc manpage
This is an account of my experience using the mdoc format to write a manpage, also touching upon the ecosystem around writing manpages in general.
man or mdoc
When writing a manpage, the first thing you must do is choose between two formats: man or mdoc. The code for the two formats look somewhat similar because they are both extensions to the roff macro language, but they use completely different sets of macros.
In the Linux world, the man format is more commonly seen, and the manpage renderer of choice is usually GNU roff (groff). The newer mdoc format is more often used by BSD operating systems, usually rendered with mandoc. Both groff and mandoc support man and mdoc; there are minor differences—more on those later—but for the most part both formats work on both renderers, as well as on others such as Heirloom doctools.
The way the two formats are usually described is that man is more presentation-oriented while mdoc is more semantic-oriented.
Due to its presentational nature, there are countless ways people have written man pages for even very simple programs. Here is just one example:
.TH MYPROG 1 1970-01-01 "MyOS 2.0"
.SH NAME
myprog \- does something
.SH SYNOPSIS
.B myprog
.RB [ \-o
.IR output ]
And here is the equivalent in mdoc:
.Dd January 1, 1970
.Dt MYPROG 1
.Os MyOS 2.0
.Sh NAME
.Nm myprog
.Nd does something
.Sh SYNOPSIS
.Nm myprog
.Op Fl o Ar output
Both may be rendered into:
Volume 1: User commands — MYPROG
NAME
myprog — does something
SYNOPSIS
myprog [-o output]
On being semantic-oriented
mdoc makes you think less about things that don’t matter and lets you just write the stupid manpage and get it over with.
It may not be as flexible as man, but as a result you don’t ever have to think about whether a command-line flag should be written in bold or italics. The renderer will pick one formatting and you just have to trust that it will look acceptable.
mdoc does not always succeed in being semantic-oriented, though; the biggest offender being…
The SYNOPSIS
problem
The traditional “synopsis” manpage section is treated in a special way in mdoc. For this special treatment
to work, the title has to be written exactly SYNOPSIS
, in English, in all-caps.
These two sections will be rendered very differently, despite the bodies being identical in the source:
.Sh SYNOPSIS
.Nm pkg
.Cm install
.Ar package
.Nm pkg
.Cm uninstall
.Op Fl \-purge
.Ar package
.
.Sh Not synopsis
.Nm pkg
.Cm install
.Ar package
.Nm pkg
.Cm uninstall
.Op Fl \-purge
.Ar package
Result:
SYNOPSIS
pkg install package
pkg uninstall [--purge] packageNot synopsis
pkg install package pkg uninstall [--purge] package
Luckily, groff at least accepts Synopsis
, and the mandoc author has said that they want to support
that as well in the future. I’m not sure if either project supports or plans to support non-English section titles.
roff-isms
Despite being not horrible in terms of readability, mdoc is still just a roff extension and you still sometimes need to deal with oddities stemming from the roff syntax.
The \&
dummy escape sequence is one such oddity that you may find in two instances:
-
To prevent a period (
.
) at the end of a word from being considered the end of a sentence. For example, to write “fruits, e.g. apples” you have to writefruits, e.g.\& apples
; otherwise the renderer will put two space characters after “e.g.” when rendering to the terminal. -
To prevent a period (
.
) or a single quote ('
) at the start of a line from being considered the start of a roff command. For example, to write “...” at the start of a paragraph you have to write\&...
.
Other escape sequences often encountered:
-
\e
,\(rs
, or\[rs]
for\
, which is the (default) escape character. -
\(aq
or\[aq]
for'
. The plain'
character itself may be rendered as’
to cater for English contractions such as “can’t”. -
\(dq
or\[dq]
for"
. The plain"
character is used on roff macro lines to quote string arguments. -
\-
for ASCII-
, as opposed to some other dash or minus character that the renderer may decide to turn plain-
into. In practice I don’t think you need this on groff and mandoc.
Editor support
Emacs, Vim, and Neovim support roff syntax highlighting but do not understand mdoc macros. And that’s before even talking about code completion, error checking, or any form of in-editor documentation; none exist as far as I can tell. There are a lot of mdoc macros, all with very terse 2- or 3-character names, some with very particular syntaxes, and without proper editor support I found myself constantly looking things up in the documentation.
To help make things more convenient for myself, and perhaps eventually others, I started writing a (Web-based) mdoc editor with some of these features. But because that ended up being a huge digression from what I was originally trying to do—which was simply to write a manpage for my program—I’ve shelved this editor project for now. It’s in a usable state, though, and it was quite helpful for me when writing my manpage, so maybe I’ll clean it up and publish it one day.
This all seems so annoying; can’t I just use Markdown?
That was my thought at various points in this journey, and I kept going back and forth between “I should write this in mdoc”, “I should write this in man”, and “I should write this in 〈some intermediate format〉”.
There are several projects that let you write manpages in Markdown or similar ‘human-readable’ markup languages and compile them into man or mdoc format. The Git project, for example, uses AsciiDoc, which is not Markdown but has a similar goal.
There are also a few XML-based solutions that you can try if that’s more your style. DocBook would be the most well-known option, although my experience using it to produce a manpage has been very poor.
I’ve looked into most of these manpage transpilers, but for the particular project I was working on I decided to stick with plain mdoc for the time being. Even so, I encourage you to try them and see if there is one that fits your project. The simpler you make the documentation process, the more you and your peers will want to write documentation.