2 * man page backend for Halibut
10 static void man_text(FILE *, word
*,
11 int newline
, int quote_props
, int charset
);
12 static void man_codepara(FILE *, word
*, int charset
);
13 static int man_convert(wchar_t const *s
, int maxlen
,
14 char **result
, int quote_props
,
15 int charset
, charset_state
*state
);
25 static manconfig
man_configure(paragraph
*source
) {
32 ret
.headnumbers
= FALSE
;
34 ret
.filename
= dupstr("output.1");
35 ret
.charset
= CS_ASCII
;
37 for (; source
; source
= source
->next
) {
38 if (source
->type
== para_Config
) {
39 if (!ustricmp(source
->keyword
, L
"man-identity")) {
42 wp
= uadv(source
->keyword
);
47 ret
.th
= mknewa(wchar_t, ep
- wp
+ 1);
48 memcpy(ret
.th
, wp
, (ep
- wp
+ 1) * sizeof(wchar_t));
49 } else if (!ustricmp(source
->keyword
, L
"man-charset")) {
50 char *csname
= utoa_dup(uadv(source
->keyword
), CS_ASCII
);
51 ret
.charset
= charset_from_localenc(csname
);
53 } else if (!ustricmp(source
->keyword
, L
"man-headnumbers")) {
54 ret
.headnumbers
= utob(uadv(source
->keyword
));
55 } else if (!ustricmp(source
->keyword
, L
"man-mindepth")) {
56 ret
.mindepth
= utoi(uadv(source
->keyword
));
57 } else if (!ustricmp(source
->keyword
, L
"man-filename")) {
59 ret
.filename
= dupstr(adv(source
->origkeyword
));
67 static void man_conf_cleanup(manconfig cf
)
73 paragraph
*man_config_filename(char *filename
)
75 return cmdline_cfg_simple("man-filename", filename
, NULL
);
78 #define QUOTE_INITCTRL 1 /* quote initial . and ' on a line */
79 #define QUOTE_QUOTES 2 /* quote double quotes by doubling them */
81 void man_backend(paragraph
*sourceform
, keywordlist
*keywords
,
82 indexdata
*idx
, void *unused
) {
91 conf
= man_configure(sourceform
);
94 * Open the output file.
96 fp
= fopen(conf
.filename
, "w");
98 error(err_cantopenw
, conf
.filename
);
102 /* Do the version ID */
103 for (p
= sourceform
; p
; p
= p
->next
)
104 if (p
->type
== para_VersionID
) {
105 fprintf(fp
, ".\\\" ");
106 man_text(fp
, p
->words
, TRUE
, 0, conf
.charset
);
109 /* .TH name-of-program manual-section */
111 if (conf
.th
&& *conf
.th
) {
115 for (wp
= conf
.th
; *wp
; wp
= uadv(wp
)) {
117 man_convert(wp
, 0, &c
, QUOTE_QUOTES
, conf
.charset
, NULL
);
125 fprintf(fp
, ".UC\n");
127 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
129 * Things we ignore because we've already processed them or
130 * aren't going to touch them in this pass.
134 case para_Biblio
: /* only touch BiblioCited */
145 case para_UnnumberedChapter
:
151 if (p
->type
== para_Subsect
)
153 else if (p
->type
== para_Heading
)
157 if (depth
>= conf
.mindepth
) {
158 fprintf(fp
, ".SH \"");
159 if (conf
.headnumbers
&& p
->kwtext
) {
160 man_text(fp
, p
->kwtext
, FALSE
, QUOTE_QUOTES
, conf
.charset
);
163 man_text(fp
, p
->words
, FALSE
, QUOTE_QUOTES
, conf
.charset
);
173 fprintf(fp
, ".PP\n");
174 man_codepara(fp
, p
->words
, conf
.charset
);
182 fprintf(fp
, ".PP\n");
183 man_text(fp
, p
->words
, TRUE
, 0, conf
.charset
);
189 case para_Description
:
190 case para_BiblioCited
:
192 case para_NumberedList
:
193 if (p
->type
== para_Bullet
) {
194 fprintf(fp
, ".IP \"\\fBo\\fP\"\n"); /* FIXME: configurable? */
195 } else if (p
->type
== para_NumberedList
) {
196 fprintf(fp
, ".IP \"");
197 man_text(fp
, p
->kwtext
, FALSE
, QUOTE_QUOTES
, conf
.charset
);
199 } else if (p
->type
== para_Description
) {
201 * Do nothing; the .xP for this paragraph is the .IP
202 * which has come before it in the DescribedThing.
204 } else if (p
->type
== para_BiblioCited
) {
205 fprintf(fp
, ".IP \"");
206 man_text(fp
, p
->kwtext
, FALSE
, QUOTE_QUOTES
, conf
.charset
);
209 man_text(fp
, p
->words
, TRUE
, 0, conf
.charset
);
212 case para_DescribedThing
:
213 fprintf(fp
, ".IP \"");
214 man_text(fp
, p
->words
, FALSE
, QUOTE_QUOTES
, conf
.charset
);
220 * This isn't terribly good. Anyone who wants to do better
223 fprintf(fp
, ".PP\n----------------------------------------\n");
228 fprintf(fp
, ".RS\n");
232 fprintf(fp
, ".RE\n");
240 man_conf_cleanup(conf
);
244 * Convert a wide string into a string of chars; mallocs the
245 * resulting string and stores a pointer to it in `*result'.
247 * If `state' is non-NULL, updates the charset state pointed to. If
248 * `state' is NULL, this function uses its own state, initialises
249 * it from scratch, and cleans it up when finished. If `state' is
250 * non-NULL but _s_ is NULL, cleans up a provided state.
252 * Return is nonzero if all characters are OK. If not all
253 * characters are OK but `result' is non-NULL, a result _will_
254 * still be generated!
256 * This function also does escaping of groff special characters.
258 static int man_convert(wchar_t const *s
, int maxlen
,
259 char **result
, int quote_props
,
260 int charset
, charset_state
*state
) {
261 charset_state internal_state
= CHARSET_INIT_STATE
;
264 int plen
= 0, psize
= 0;
265 rdstringc out
= {0, 0, NULL
};
268 state
= &internal_state
;
270 slen
= (s ?
ustrlen(s
) : 0);
272 if (slen
> maxlen
&& maxlen
> 0)
277 p
= mknewa(char, psize
);
281 int ret
= charset_from_unicode(&s
, &slen
, p
+plen
, psize
-plen
,
282 charset
, state
, (err ? NULL
: &err
));
285 if (psize
- plen
< 256) {
287 p
= resize(p
, psize
);
292 if (state
== &internal_state
|| s
== NULL
) {
293 int ret
= charset_from_unicode(NULL
, 0, p
+plen
, psize
-plen
,
294 charset
, state
, NULL
);
299 for (q
= p
; q
< p
+plen
; q
++) {
300 if (q
== p
&& (*q
== '.' || *q
== '\'') &&
301 (quote_props
& QUOTE_INITCTRL
)) {
303 * Control character (. or ') at the start of a
304 * line. Quote it by putting \& (troff zero-width
309 } else if (*q
== '\\') {
311 * Quote backslashes by doubling them, always.
314 } else if (*q
== '"' && (quote_props
& QUOTE_QUOTES
)) {
316 * Double quote within double quotes. Quote it by
327 *result
= rdtrimc(&out
);
329 *result
= dupstr("");
334 static void man_rdaddwc(rdstringc
*rs
, word
*text
, word
*end
,
335 int quote_props
, int charset
, charset_state
*state
) {
338 for (; text
&& text
!= end
; text
= text
->next
) switch (text
->type
) {
351 case word_WhiteSpace
:
354 case word_WkCodeSpace
:
358 case word_WkCodeQuote
:
359 assert(text
->type
!= word_CodeQuote
&&
360 text
->type
!= word_WkCodeQuote
);
362 if (towordstyle(text
->type
) == word_Emph
&&
363 (attraux(text
->aux
) == attr_First
||
364 attraux(text
->aux
) == attr_Only
)) {
366 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
367 man_convert(NULL
, 0, &c
, quote_props
, charset
, state
);
370 *state
= charset_init_state
;
372 } else if ((towordstyle(text
->type
) == word_Code
||
373 towordstyle(text
->type
) == word_WeakCode
) &&
374 (attraux(text
->aux
) == attr_First
||
375 attraux(text
->aux
) == attr_Only
)) {
377 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
378 man_convert(NULL
, 0, &c
, quote_props
, charset
, state
);
381 *state
= charset_init_state
;
385 if (removeattr(text
->type
) == word_Normal
) {
386 charset_state s2
= *state
;
389 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
390 if (man_convert(text
->text
, 0, &c
, quote_props
, charset
, &s2
) ||
395 man_rdaddwc(rs
, text
->alt
, NULL
, quote_props
, charset
, state
);
398 } else if (removeattr(text
->type
) == word_WhiteSpace
) {
400 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
401 man_convert(L
" ", 1, &c
, quote_props
, charset
, state
);
404 } else if (removeattr(text
->type
) == word_Quote
) {
406 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
407 man_convert(L
"\"", 1, &c
, quote_props
, charset
, state
);
411 if (towordstyle(text
->type
) != word_Normal
&&
412 (attraux(text
->aux
) == attr_Last
||
413 attraux(text
->aux
) == attr_Only
)) {
415 quote_props
&= ~QUOTE_INITCTRL
; /* not at start any more */
416 man_convert(NULL
, 0, &c
, quote_props
, charset
, state
);
419 *state
= charset_init_state
;
424 man_convert(NULL
, 0, &c
, quote_props
, charset
, state
);
429 static void man_text(FILE *fp
, word
*text
, int newline
,
430 int quote_props
, int charset
) {
431 rdstringc t
= { 0, 0, NULL
};
432 charset_state state
= CHARSET_INIT_STATE
;
434 man_rdaddwc(&t
, text
, NULL
, quote_props
| QUOTE_INITCTRL
, charset
, &state
);
435 fprintf(fp
, "%s", t
.text
);
441 static void man_codepara(FILE *fp
, word
*text
, int charset
) {
442 fprintf(fp
, ".nf\n");
443 for (; text
; text
= text
->next
) if (text
->type
== word_WeakCode
) {
446 int quote_props
= QUOTE_INITCTRL
;
449 if (text
->next
&& text
->next
->type
== word_Emph
) {
450 e
= text
->next
->text
;
455 while (e
&& *e
&& *t
) {
459 for (n
= 0; t
[n
] && e
[n
] && e
[n
] == ec
; n
++);
464 man_convert(t
, n
, &c
, quote_props
, charset
, NULL
);
465 quote_props
&= ~QUOTE_INITCTRL
;
466 fprintf(fp
, "%s", c
);
468 if (ec
== 'i' || ec
== 'b')
473 man_convert(t
, 0, &c
, quote_props
, charset
, NULL
);
474 fprintf(fp
, "%s\n", c
);
477 fprintf(fp
, ".fi\n");