2 * man page backend for Halibut
16 wchar_t *bullet
, *lquote
, *rquote
;
19 static void man_text(FILE *, word
*,
20 int newline
, int quote_props
, manconfig
*conf
);
21 static void man_codepara(FILE *, word
*, int charset
);
22 static int man_convert(wchar_t const *s
, int maxlen
,
23 char **result
, int quote_props
,
24 int charset
, charset_state
*state
);
26 static manconfig
man_configure(paragraph
*source
) {
34 ret
.headnumbers
= FALSE
;
36 ret
.filename
= dupstr("output.1");
37 ret
.charset
= CS_ASCII
;
38 ret
.bullet
= L
"\x2022\0o\0\0";
39 ret
.lquote
= L
"\x2018\0\x2019\0\"\0\"\0\0";
40 ret
.rquote
= uadv(ret
.lquote
);
43 * Two-pass configuration so that we can pick up global config
44 * (e.g. `quotes') before having it overridden by specific
45 * config (`man-quotes'), irrespective of the order in which
48 for (p
= source
; p
; p
= p
->next
) {
49 if (p
->type
== para_Config
) {
50 if (!ustricmp(p
->keyword
, L
"quotes")) {
51 if (*uadv(p
->keyword
) && *uadv(uadv(p
->keyword
))) {
52 ret
.lquote
= uadv(p
->keyword
);
53 ret
.rquote
= uadv(ret
.lquote
);
59 for (p
= source
; p
; p
= p
->next
) {
60 if (p
->type
== para_Config
) {
61 if (!ustricmp(p
->keyword
, L
"man-identity")) {
64 wp
= uadv(p
->keyword
);
69 ret
.th
= snewn(ep
- wp
+ 1, wchar_t);
70 memcpy(ret
.th
, wp
, (ep
- wp
+ 1) * sizeof(wchar_t));
71 } else if (!ustricmp(p
->keyword
, L
"man-charset")) {
72 ret
.charset
= charset_from_ustr(&p
->fpos
, uadv(p
->keyword
));
73 } else if (!ustricmp(p
->keyword
, L
"man-headnumbers")) {
74 ret
.headnumbers
= utob(uadv(p
->keyword
));
75 } else if (!ustricmp(p
->keyword
, L
"man-mindepth")) {
76 ret
.mindepth
= utoi(uadv(p
->keyword
));
77 } else if (!ustricmp(p
->keyword
, L
"man-filename")) {
79 ret
.filename
= dupstr(adv(p
->origkeyword
));
80 } else if (!ustricmp(p
->keyword
, L
"man-bullet")) {
81 ret
.bullet
= uadv(p
->keyword
);
82 } else if (!ustricmp(p
->keyword
, L
"man-quotes")) {
83 if (*uadv(p
->keyword
) && *uadv(uadv(p
->keyword
))) {
84 ret
.lquote
= uadv(p
->keyword
);
85 ret
.rquote
= uadv(ret
.lquote
);
92 * Now process fallbacks on quote characters and bullets.
94 while (*uadv(ret
.rquote
) && *uadv(uadv(ret
.rquote
)) &&
95 (!cvt_ok(ret
.charset
, ret
.lquote
) ||
96 !cvt_ok(ret
.charset
, ret
.rquote
))) {
97 ret
.lquote
= uadv(ret
.rquote
);
98 ret
.rquote
= uadv(ret
.lquote
);
101 while (*ret
.bullet
&& *uadv(ret
.bullet
) &&
102 !cvt_ok(ret
.charset
, ret
.bullet
))
103 ret
.bullet
= uadv(ret
.bullet
);
108 static void man_conf_cleanup(manconfig cf
)
114 paragraph
*man_config_filename(char *filename
)
116 return cmdline_cfg_simple("man-filename", filename
, NULL
);
119 #define QUOTE_INITCTRL 1 /* quote initial . and ' on a line */
120 #define QUOTE_QUOTES 2 /* quote double quotes by doubling them */
122 void man_backend(paragraph
*sourceform
, keywordlist
*keywords
,
123 indexdata
*idx
, void *unused
) {
127 int had_described_thing
;
133 conf
= man_configure(sourceform
);
136 * Open the output file.
138 fp
= fopen(conf
.filename
, "w");
140 error(err_cantopenw
, conf
.filename
);
144 /* Do the version ID */
145 for (p
= sourceform
; p
; p
= p
->next
)
146 if (p
->type
== para_VersionID
) {
147 fprintf(fp
, ".\\\" ");
148 man_text(fp
, p
->words
, TRUE
, 0, &conf
);
151 /* .TH name-of-program manual-section */
153 if (conf
.th
&& *conf
.th
) {
157 for (wp
= conf
.th
; *wp
; wp
= uadv(wp
)) {
159 man_convert(wp
, 0, &c
, QUOTE_QUOTES
, conf
.charset
, NULL
);
167 had_described_thing
= FALSE
;
168 #define cleanup_described_thing do { \
169 if (had_described_thing) \
171 had_described_thing = FALSE; \
174 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
176 * Things we ignore because we've already processed them or
177 * aren't going to touch them in this pass.
181 case para_Biblio
: /* only touch BiblioCited */
192 case para_UnnumberedChapter
:
196 cleanup_described_thing
;
199 if (p
->type
== para_Subsect
)
201 else if (p
->type
== para_Heading
)
205 if (depth
>= conf
.mindepth
) {
206 if (depth
> conf
.mindepth
)
207 fprintf(fp
, ".SS \"");
209 fprintf(fp
, ".SH \"");
210 if (conf
.headnumbers
&& p
->kwtext
) {
211 man_text(fp
, p
->kwtext
, FALSE
, QUOTE_QUOTES
, &conf
);
214 man_text(fp
, p
->words
, FALSE
, QUOTE_QUOTES
, &conf
);
224 cleanup_described_thing
;
225 fprintf(fp
, ".PP\n");
226 man_codepara(fp
, p
->words
, conf
.charset
);
234 cleanup_described_thing
;
235 fprintf(fp
, ".PP\n");
236 man_text(fp
, p
->words
, TRUE
, 0, &conf
);
242 case para_Description
:
243 case para_BiblioCited
:
245 case para_NumberedList
:
246 if (p
->type
!= para_Description
)
247 cleanup_described_thing
;
249 if (p
->type
== para_Bullet
) {
251 man_convert(conf
.bullet
, -1, &bullettext
, QUOTE_QUOTES
,
253 fprintf(fp
, ".IP \"\\fB%s\\fP\"\n", bullettext
);
255 } else if (p
->type
== para_NumberedList
) {
256 fprintf(fp
, ".IP \"");
257 man_text(fp
, p
->kwtext
, FALSE
, QUOTE_QUOTES
, &conf
);
259 } else if (p
->type
== para_Description
) {
260 if (had_described_thing
) {
262 * Do nothing; the .xP for this paragraph is the
263 * .IP which has come before it in the
268 * A \dd without a preceding \dt is given a blank
271 fprintf(fp
, ".IP \"\"\n");
273 } else if (p
->type
== para_BiblioCited
) {
274 fprintf(fp
, ".IP \"");
275 man_text(fp
, p
->kwtext
, FALSE
, QUOTE_QUOTES
, &conf
);
278 man_text(fp
, p
->words
, TRUE
, 0, &conf
);
279 had_described_thing
= FALSE
;
282 case para_DescribedThing
:
283 cleanup_described_thing
;
284 fprintf(fp
, ".IP \"");
285 man_text(fp
, p
->words
, FALSE
, QUOTE_QUOTES
, &conf
);
287 had_described_thing
= TRUE
;
292 * New paragraph containing a horizontal line 1/2em above the
293 * baseline whose length is the line length minus the current
296 cleanup_described_thing
;
297 fprintf(fp
, ".PP\n\\u\\l'\\n(.lu-\\n(.iu'\\d\n");
302 cleanup_described_thing
;
303 fprintf(fp
, ".RS\n");
307 cleanup_described_thing
;
308 fprintf(fp
, ".RE\n");
311 cleanup_described_thing
;
317 man_conf_cleanup(conf
);
321 * Convert a wide string into a string of chars; mallocs the
322 * resulting string and stores a pointer to it in `*result'.
324 * If `state' is non-NULL, updates the charset state pointed to. If
325 * `state' is NULL, this function uses its own state, initialises
326 * it from scratch, and cleans it up when finished. If `state' is
327 * non-NULL but _s_ is NULL, cleans up a provided state.
329 * Return is nonzero if all characters are OK. If not all
330 * characters are OK but `result' is non-NULL, a result _will_
331 * still be generated!
333 * This function also does escaping of groff special characters.
335 static int man_convert(wchar_t const *s
, int maxlen
,
336 char **result
, int quote_props
,
337 int charset
, charset_state
*state
) {
338 charset_state internal_state
= CHARSET_INIT_STATE
;
341 int plen
= 0, psize
= 0;
342 rdstringc out
= {0, 0, NULL
};
345 state
= &internal_state
;
347 slen
= (s ?
ustrlen(s
) : 0);
349 if (slen
> maxlen
&& maxlen
> 0)
354 p
= snewn(psize
, char);
358 int ret
= charset_from_unicode(&s
, &slen
, p
+plen
, psize
-plen
,
359 charset
, state
, (err ? NULL
: &err
));
362 if (psize
- plen
< 256) {
364 p
= sresize(p
, psize
, char);
369 if (state
== &internal_state
|| s
== NULL
) {
370 int ret
= charset_from_unicode(NULL
, 0, p
+plen
, psize
-plen
,
371 charset
, state
, NULL
);
376 for (q
= p
; q
< p
+plen
; q
++) {
377 if (q
== p
&& (*q
== '.' || *q
== '\'') &&
378 (quote_props
& QUOTE_INITCTRL
)) {
380 * Control character (. or ') at the start of a
381 * line. Quote it by putting \& (troff zero-width
386 } else if (*q
== '\\') {
388 * Quote backslashes by doubling them, always.
391 } else if (*q
== '"' && (quote_props
& QUOTE_QUOTES
)) {
393 * Double quote within double quotes. Quote it by
404 *result
= rdtrimc(&out
);
406 *result
= dupstr("");
411 static int man_rdaddwc(rdstringc
*rs
, word
*text
, word
*end
,
412 int quote_props
, manconfig
*conf
,
413 charset_state
*state
) {
416 for (; text
&& text
!= end
; text
= text
->next
) switch (text
->type
) {
429 case word_WhiteSpace
:
432 case word_WkCodeSpace
:
436 case word_WkCodeQuote
:
437 assert(text
->type
!= word_CodeQuote
&&
438 text
->type
!= word_WkCodeQuote
);
440 if (towordstyle(text
->type
) == word_Emph
&&
441 (attraux(text
->aux
) == attr_First
||
442 attraux(text
->aux
) == attr_Only
)) {
443 man_convert(NULL
, 0, &c
, quote_props
, conf
->charset
, state
);
446 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
448 *state
= charset_init_state
;
450 } else if ((towordstyle(text
->type
) == word_Code
||
451 towordstyle(text
->type
) == word_WeakCode
) &&
452 (attraux(text
->aux
) == attr_First
||
453 attraux(text
->aux
) == attr_Only
)) {
454 man_convert(NULL
, 0, &c
, quote_props
, conf
->charset
, state
);
457 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
459 *state
= charset_init_state
;
463 if (removeattr(text
->type
) == word_Normal
) {
464 charset_state s2
= *state
;
466 if (man_convert(text
->text
, 0, &c
, quote_props
, conf
->charset
, &s2
) ||
470 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
473 quote_props
= man_rdaddwc(rs
, text
->alt
, NULL
,
474 quote_props
, conf
, state
);
477 } else if (removeattr(text
->type
) == word_WhiteSpace
) {
478 man_convert(L
" ", 1, &c
, quote_props
, conf
->charset
, state
);
481 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
483 } else if (removeattr(text
->type
) == word_Quote
) {
484 man_convert(quoteaux(text
->aux
) == quote_Open ?
485 conf
->lquote
: conf
->rquote
, 0,
486 &c
, quote_props
, conf
->charset
, state
);
489 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
492 if (towordstyle(text
->type
) != word_Normal
&&
493 (attraux(text
->aux
) == attr_Last
||
494 attraux(text
->aux
) == attr_Only
)) {
495 man_convert(NULL
, 0, &c
, quote_props
, conf
->charset
, state
);
498 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
500 *state
= charset_init_state
;
505 man_convert(NULL
, 0, &c
, quote_props
, conf
->charset
, state
);
508 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
514 static void man_text(FILE *fp
, word
*text
, int newline
,
515 int quote_props
, manconfig
*conf
) {
516 rdstringc t
= { 0, 0, NULL
};
517 charset_state state
= CHARSET_INIT_STATE
;
519 man_rdaddwc(&t
, text
, NULL
, quote_props
| QUOTE_INITCTRL
, conf
, &state
);
520 fprintf(fp
, "%s", t
.text
);
526 static void man_codepara(FILE *fp
, word
*text
, int charset
) {
527 fprintf(fp
, ".nf\n");
528 for (; text
; text
= text
->next
) if (text
->type
== word_WeakCode
) {
531 int quote_props
= QUOTE_INITCTRL
;
534 if (text
->next
&& text
->next
->type
== word_Emph
) {
535 e
= text
->next
->text
;
540 while (e
&& *e
&& *t
) {
544 for (n
= 0; t
[n
] && e
[n
] && e
[n
] == ec
; n
++);
549 man_convert(t
, n
, &c
, quote_props
, charset
, NULL
);
550 quote_props
&= ~QUOTE_INITCTRL
;
551 fprintf(fp
, "%s", c
);
553 if (ec
== 'i' || ec
== 'b')
558 man_convert(t
, 0, &c
, quote_props
, charset
, NULL
);
559 fprintf(fp
, "%s\n", c
);
562 fprintf(fp
, ".fi\n");