Skip to content

Commit 85c3e90

Browse files
committed
mu4e: include man pages in manual
For reference, add the mu man pages as part of the mu4e reference manual. Add a special export script to massage the man orgs into something reasonable. Note, you can create html, pdf using: make mu4e-doc-html make mu4e-doc-pdf
1 parent d588d15 commit 85c3e90

5 files changed

Lines changed: 238 additions & 1 deletion

File tree

‎NEWS.org‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@
9393

9494
- ~mu4e-compose-resend~ now honors ~mu4e-sent-messages-behavior~ (1.14.4)
9595

96+
- include the ~mu~ man-pages as an appendix to the ~mu4e~ manual (1.14.4)
97+
9698
- ~mu4e-view-go-to-url~ and ~mu4e-view-save-url~ now also work when viewing
9799
messages in HTML-mode. See the section info:mu4e#Links in the Mu4e
98100
reference manual (1.14.4)

‎man/meson.build‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,24 @@ foreach src : man_orgs
111111
org,
112112
'--eval', expr])
113113
endforeach
114+
115+
#
116+
# texinfo version of the man-pages, included as an appendix in the
117+
# mu4e manual.
118+
#
119+
man_texi_el = meson.current_source_dir() / 'mu-man-texi.el'
120+
man_orgs_configured = [] # i.e., the configured org-files (see above)
121+
foreach src : man_orgs
122+
man_orgs_configured += meson.current_build_dir() / src
123+
endforeach
124+
man_texi = custom_target('mu-man-pages-texi',
125+
input: man_orgs,
126+
output: 'mu-man-pages.texi',
127+
depend_files: incs + [man_texi_el],
128+
depends: inc_targets,
129+
command: [emacs,
130+
'--no-init-file',
131+
'--batch',
132+
'--load', man_texi_el,
133+
'--funcall', 'mu-man-texi-batch',
134+
'@OUTPUT@'] + man_orgs_configured)

‎man/mu-man-texi.el‎

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
;;; mu-man-texi.el --- Export the mu man-pages to Texinfo -*- lexical-binding: t -*-
2+
3+
;; Copyright (C) 2026 Dirk-Jan C. Binnema
4+
5+
;; This file is not part of GNU Emacs.
6+
7+
;; This program is free software; you can redistribute it and/or modify
8+
;; it under the terms of the GNU General Public License as published by
9+
;; the Free Software Foundation, either version 3 of the License, or
10+
;; (at your option) any later version.
11+
12+
;; This program is distributed in the hope that it will be useful,
13+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
;; GNU General Public License for more details.
16+
17+
;; You should have received a copy of the GNU General Public License
18+
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
20+
;;; Commentary:
21+
22+
;; Turn the org-mode sources of the mu man-pages into a single Texinfo file, for
23+
;; including in the mu4e manual.
24+
;;
25+
;; The plain `texinfo' exporter maps each headline to a node and a sectioning
26+
;; command, which does not work well for man-pages, so re-derive something
27+
;; a bit more reasonable.
28+
29+
;;; Code:
30+
31+
(require 'ox-texinfo)
32+
33+
(defconst mu-man-texi-skip-sections
34+
'("AUTHOR" "REPORTING BUGS" "COPYRIGHT")
35+
"Top-level man-page sections to leave out.")
36+
37+
(defvar mu-man-texi--pages nil
38+
"List of (NAME . SECTION) for the man-pages being exported.")
39+
40+
(defun mu-man-texi--page (file)
41+
"Get (NAME . SECTION) for man-page org-FILE."
42+
(let ((base (file-name-base file))) ;; e.g., "mu-find.1"
43+
(cons (file-name-sans-extension base)
44+
(file-name-extension base))))
45+
46+
(defun mu-man-texi--node (name)
47+
"Get the Texinfo node name for man-page NAME."
48+
(format "%s man-page" name))
49+
50+
(defun mu-man-texi--man-link (name section)
51+
"Return an org-snippet for referring to man-page NAME in SECTION.
52+
Format it like man(1) would."
53+
;; We use @link rather than @ref, since the latter adds
54+
;; \"see\"/\"*note\" to the info-output, which doesn't fit in
55+
;; running text.
56+
;;; man-link is sometimes used with "mu cmd" rather than "mu-cmd".
57+
(let* ((page (string-replace " " "-" (string-trim name)))
58+
(section (string-trim section))
59+
(label (format "@strong{%s}(%s)" name section)))
60+
(format "@@texinfo:%s@@"
61+
(if (member (cons page section) mu-man-texi--pages)
62+
(format "@link{%s,%s}" (mu-man-texi--node page) label)
63+
label))))
64+
65+
(defun mu-man-texi--prepare (_backend)
66+
"Prepare the buffer for export."
67+
(org-export-expand-include-keyword)
68+
(goto-char (point-min))
69+
(while (re-search-forward "^#\\+macro: +man-link .*$" nil t)
70+
(replace-match
71+
"#+MACRO: man-link (eval (mu-man-texi--man-link $1 $2))" t t)))
72+
73+
(defun mu-man-texi--plain-text (text _backend _info)
74+
"Filter for plain TEXT.
75+
Remove zero-width spaces, and keep Texinfo from turning -- into a
76+
dash."
77+
(string-replace "--" "-@w{}-"
78+
(string-replace (string #x200b) "" text)))
79+
80+
(defun mu-man-texi--headline (headline contents info)
81+
"Transcode HEADLINE with CONTENTS into Texinfo; INFO is the plist.
82+
Top-level headlines become headings; second-level ones become
83+
table items. Headlines in `mu-man-texi-skip-sections' are dropped."
84+
(let ((level (org-export-get-relative-level headline info))
85+
(title (org-export-data (org-element-property :title headline) info))
86+
(contents (or contents "")))
87+
(cond
88+
((= level 1)
89+
(unless (member (org-element-property :raw-value headline)
90+
mu-man-texi-skip-sections)
91+
(format "@subheading %s\n\n%s" title contents)))
92+
(t ;; wrap consecutive sibling headlines in a table.
93+
(let ((first (not (eq (org-element-type
94+
(org-export-get-previous-element headline info))
95+
'headline)))
96+
(last (not (eq (org-element-type
97+
(org-export-get-next-element headline info))
98+
'headline))))
99+
(concat (when first "@table @asis\n")
100+
(format "@item %s\n%s" title contents)
101+
(when last "@end table\n\n")))))))
102+
103+
(defun mu-man-texi--section (_section contents _info)
104+
"Transcode a section with CONTENTS.
105+
Unlike the `texinfo' backend, do not add a menu, since our
106+
headlines are not nodes."
107+
contents)
108+
109+
(defun mu-man-texi--smaller (block)
110+
"Turn Texinfo BLOCK (@example/@lisp) into its smaller variant.
111+
This only affects printed (PDF) output, where many of the lines
112+
in the man-page examples would not fit otherwise."
113+
(replace-regexp-in-string
114+
"^@\\(end \\)?\\(example\\|lisp\\)$" "@\\1small\\2" block))
115+
116+
(defun mu-man-texi--example-block (example-block contents info)
117+
"Transcode EXAMPLE-BLOCK with CONTENTS; INFO is the plist."
118+
(mu-man-texi--smaller
119+
(org-texinfo-example-block example-block contents info)))
120+
121+
(defun mu-man-texi--src-block (src-block contents info)
122+
"Transcode SRC-BLOCK with CONTENTS; INFO is the plist."
123+
(mu-man-texi--smaller
124+
(org-texinfo-src-block src-block contents info)))
125+
126+
(defun mu-man-texi--literal-bullets-p (plain-list)
127+
"Non-nil if PLAIN-LIST is ordered, but not numbered 1, 2, 3, ...
128+
E.g., the list of exit codes. Org (and Texinfo) would renumber
129+
those, but man(1) shows them as they are, and so do we."
130+
(and (eq (org-element-property :type plain-list) 'ordered)
131+
(let ((n 0))
132+
(seq-some (lambda (item)
133+
(/= (string-to-number (org-element-property :bullet item))
134+
(setq n (1+ n))))
135+
(org-element-contents plain-list)))))
136+
137+
(defun mu-man-texi--plain-list (plain-list contents info)
138+
"Transcode PLAIN-LIST with CONTENTS; INFO is the plist.
139+
Lists with literal bullets become a table."
140+
(if (mu-man-texi--literal-bullets-p plain-list)
141+
(format "@table @asis\n%s@end table" contents)
142+
(org-texinfo-plain-list plain-list contents info)))
143+
144+
(defun mu-man-texi--item (item contents info)
145+
"Transcode ITEM with CONTENTS; INFO is the plist."
146+
(if (mu-man-texi--literal-bullets-p (org-export-get-parent item))
147+
(format "@item %s\n%s" (string-trim (org-element-property :bullet item))
148+
(or contents ""))
149+
(org-texinfo-item item contents info)))
150+
151+
(org-export-define-derived-backend 'mu-man-texi 'texinfo
152+
:translate-alist '((headline . mu-man-texi--headline)
153+
(section . mu-man-texi--section)
154+
(example-block . mu-man-texi--example-block)
155+
(src-block . mu-man-texi--src-block)
156+
(plain-list . mu-man-texi--plain-list)
157+
(item . mu-man-texi--item))
158+
:filters-alist '((:filter-plain-text . mu-man-texi--plain-text)))
159+
160+
(defun mu-man-texi--export (file)
161+
"Export man-page org-FILE to a Texinfo string, with its own node."
162+
(let* ((page (mu-man-texi--page file))
163+
(name (car page))
164+
(section (cdr page))
165+
(org-export-with-sub-superscripts '{})
166+
(org-export-with-toc nil)
167+
;; man renders underlined text in italics
168+
(org-texinfo-text-markup-alist
169+
(cons '(underline . "@emph{%s}") org-texinfo-text-markup-alist))
170+
(org-export-before-processing-functions
171+
(list #'mu-man-texi--prepare)))
172+
(with-temp-buffer
173+
(insert-file-contents file)
174+
(setq default-directory (file-name-directory (expand-file-name file)))
175+
(org-mode)
176+
(concat
177+
(format "@page\n@node %s\n@appendixsec %s(%s)\n\n"
178+
(mu-man-texi--node name) name section)
179+
(org-export-as 'mu-man-texi nil nil t)
180+
"\n"))))
181+
182+
(defun mu-man-texi-export (output files)
183+
"Export man-page org-FILES into a single Texinfo file OUTPUT.
184+
The result has a menu, followed by a node for each of the FILES."
185+
(let ((mu-man-texi--pages (mapcar #'mu-man-texi--page files)))
186+
(with-temp-file output
187+
(insert "@c generated by mu-man-texi.el; do not edit\n\n"
188+
"@menu\n")
189+
(dolist (page mu-man-texi--pages)
190+
(insert (format "* %s:: %s(%s)\n"
191+
(mu-man-texi--node (car page)) (car page) (cdr page))))
192+
(insert "@end menu\n\n")
193+
(dolist (file files)
194+
(insert (mu-man-texi--export file))))))
195+
196+
(defun mu-man-texi-batch ()
197+
"Batch entry point: OUTPUT FILE... in `command-line-args-left'."
198+
(let ((output (pop command-line-args-left))
199+
(files command-line-args-left))
200+
(setq command-line-args-left nil)
201+
(mu-man-texi-export output files)))
202+
203+
(provide 'mu-man-texi)
204+
;;; mu-man-texi.el ends here

‎mu4e/meson.build‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ if makeinfo.found()
155155
output: 'mu4e.info',
156156
install_dir: infodir,
157157
install: true,
158-
depends: fdl_texi,
158+
depends: [fdl_texi, man_texi],
159159
command: [makeinfo,
160160
'-o', meson.current_build_dir() / 'mu4e.info',
161161
meson.current_source_dir() / 'mu4e.texi',

‎mu4e/mu4e.texi‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
@c %**start of header
55
@setfilename mu4e.info
66
@settitle Mu4e @value{VERSION} user manual
7+
@c don't mark overfull lines with black boxes in printed output
8+
@finalout
79

810
@c Use proper quote and backtick for code sections in PDF output
911
@c Cf. Texinfo manual 14.2
@@ -106,6 +108,7 @@ Appendices
106108
* Tips and Tricks:: Useful tips
107109
* How it works:: Some notes about the implementation of @code{mu4e}
108110
* Debugging:: How to debug problems in @code{mu4e}
111+
* Manual pages:: The @command{mu} manual pages
109112
* GNU Free Documentation License:: The license of this manual
110113
111114
Indices
@@ -5751,6 +5754,13 @@ your @t{systemd} journal, @t{syslog} or in @t{<MUHOME>/mu.log}, on Unix
57515754
typically @t{~/.cache/mu/mu.log}. To run the server in debug-mode, set
57525755
@code{mu4e-mu-debug} to @t{t} (before starting the server).
57535756
5757+
@node Manual pages
5758+
@appendix Manual pages
5759+
5760+
For reference, the manuals for @command{mu} and its sub-commands.
5761+
5762+
@include man/mu-man-pages.texi
5763+
57545764
@node GNU Free Documentation License
57555765
@appendix GNU Free Documentation License
57565766

0 commit comments

Comments
 (0)