Obsoleted the `\preamble' command. Preamble text is now taken to be
[sgt/halibut] / bk_man.c
1 /*
2 * man page backend for Halibut
3 */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <assert.h>
8 #include "halibut.h"
9
10 static void man_text(FILE *, word *, int newline, int quote_props);
11 static void man_codepara(FILE *, word *);
12 static int man_convert(wchar_t *s, int maxlen,
13 char **result, int quote_props);
14
15 typedef struct {
16 wchar_t *th;
17 int headnumbers;
18 int mindepth;
19 } manconfig;
20
21 static manconfig man_configure(paragraph *source) {
22 manconfig ret;
23
24 /*
25 * Defaults.
26 */
27 ret.th = NULL;
28 ret.headnumbers = FALSE;
29 ret.mindepth = 0;
30
31 for (; source; source = source->next) {
32 if (source->type == para_Config) {
33 if (!ustricmp(source->keyword, L"man-identity")) {
34 wchar_t *wp, *ep;
35
36 wp = uadv(source->keyword);
37 ep = wp;
38 while (*ep)
39 ep = uadv(ep);
40 ret.th = mknewa(wchar_t, ep - wp + 1);
41 memcpy(ret.th, wp, (ep - wp + 1) * sizeof(wchar_t));
42 } else if (!ustricmp(source->keyword, L"man-headnumbers")) {
43 ret.headnumbers = utob(uadv(source->keyword));
44 } else if (!ustricmp(source->keyword, L"man-mindepth")) {
45 ret.mindepth = utoi(uadv(source->keyword));
46 }
47 }
48 }
49
50 return ret;
51 }
52
53 static void man_conf_cleanup(manconfig cf)
54 {
55 sfree(cf.th);
56 }
57
58 #define QUOTE_INITCTRL 1 /* quote initial . and ' on a line */
59 #define QUOTE_QUOTES 2 /* quote double quotes by doubling them */
60
61 void man_backend(paragraph *sourceform, keywordlist *keywords,
62 indexdata *idx) {
63 paragraph *p;
64 FILE *fp;
65 manconfig conf;
66 int done_copyright;
67
68 IGNORE(keywords); /* we don't happen to need this */
69 IGNORE(idx); /* or this */
70
71 conf = man_configure(sourceform);
72
73 /*
74 * Determine the output file name, and open the output file
75 *
76 * FIXME: want configurable output file names here. For the
77 * moment, we'll just call it `output.1'.
78 */
79 fp = fopen("output.1", "w");
80 if (!fp) {
81 error(err_cantopenw, "output.1");
82 return;
83 }
84
85 /* Do the version ID */
86 for (p = sourceform; p; p = p->next)
87 if (p->type == para_VersionID) {
88 fprintf(fp, ".\\\" ");
89 man_text(fp, p->words, TRUE, 0);
90 }
91
92 /* .TH name-of-program manual-section */
93 fprintf(fp, ".TH");
94 if (conf.th && *conf.th) {
95 char *c;
96 wchar_t *wp;
97
98 for (wp = conf.th; *wp; wp = uadv(wp)) {
99 fputs(" \"", fp);
100 man_convert(wp, 0, &c, QUOTE_QUOTES);
101 fputs(c, fp);
102 sfree(c);
103 fputc('"', fp);
104 }
105 }
106 fputc('\n', fp);
107
108 fprintf(fp, ".UC\n");
109
110 done_copyright = FALSE;
111
112 for (p = sourceform; p; p = p->next) switch (p->type) {
113 /*
114 * Things we ignore because we've already processed them or
115 * aren't going to touch them in this pass.
116 */
117 case para_IM:
118 case para_BR:
119 case para_Biblio: /* only touch BiblioCited */
120 case para_VersionID:
121 case para_Copyright:
122 case para_NoCite:
123 case para_Title:
124 break;
125
126 /*
127 * Headings.
128 */
129 case para_Chapter:
130 case para_Appendix:
131 case para_UnnumberedChapter:
132 case para_Heading:
133 case para_Subsect:
134
135 if (!done_copyright) {
136 paragraph *p;
137
138 /*
139 * The copyright comes just before the first chapter
140 * title.
141 */
142 for (p = sourceform; p; p = p->next)
143 if (p->type == para_Copyright) {
144 fprintf(fp, ".PP\n");
145 man_text(fp, p->words, TRUE, 0);
146 }
147 done_copyright = TRUE;
148 }
149
150 {
151 int depth;
152 if (p->type == para_Subsect)
153 depth = p->aux + 2;
154 else if (p->type == para_Heading)
155 depth = 1;
156 else
157 depth = 0;
158 if (depth >= conf.mindepth) {
159 fprintf(fp, ".SH \"");
160 if (conf.headnumbers && p->kwtext) {
161 man_text(fp, p->kwtext, FALSE, QUOTE_QUOTES);
162 fprintf(fp, " ");
163 }
164 man_text(fp, p->words, FALSE, QUOTE_QUOTES);
165 fprintf(fp, "\"\n");
166 }
167 break;
168 }
169
170 /*
171 * Code paragraphs.
172 */
173 case para_Code:
174 fprintf(fp, ".PP\n");
175 man_codepara(fp, p->words);
176 break;
177
178 /*
179 * Normal paragraphs.
180 */
181 case para_Normal:
182 fprintf(fp, ".PP\n");
183 man_text(fp, p->words, TRUE, 0);
184 break;
185
186 /*
187 * List paragraphs.
188 */
189 case para_Description:
190 case para_BiblioCited:
191 case para_Bullet:
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);
198 fprintf(fp, "\"\n");
199 } else if (p->type == para_Description) {
200 /*
201 * Do nothing; the .xP for this paragraph is the .IP
202 * which has come before it in the DescribedThing.
203 */
204 } else if (p->type == para_BiblioCited) {
205 fprintf(fp, ".IP \"");
206 man_text(fp, p->kwtext, FALSE, QUOTE_QUOTES);
207 fprintf(fp, "\"\n");
208 }
209 man_text(fp, p->words, TRUE, 0);
210 break;
211
212 case para_DescribedThing:
213 fprintf(fp, ".IP \"");
214 man_text(fp, p->words, FALSE, QUOTE_QUOTES);
215 fprintf(fp, "\"\n");
216 break;
217
218 case para_Rule:
219 /*
220 * This isn't terribly good. Anyone who wants to do better
221 * should feel free!
222 */
223 fprintf(fp, ".PP\n----------------------------------------\n");
224 break;
225
226 case para_LcontPush:
227 case para_QuotePush:
228 fprintf(fp, ".RS\n");
229 break;
230 case para_LcontPop:
231 case para_QuotePop:
232 fprintf(fp, ".RE\n");
233 break;
234 }
235
236 /*
237 * Tidy up.
238 */
239 fclose(fp);
240 man_conf_cleanup(conf);
241 }
242
243 /*
244 * Convert a wide string into a string of chars. If `result' is
245 * non-NULL, mallocs the resulting string and stores a pointer to
246 * it in `*result'. If `result' is NULL, merely checks whether all
247 * characters in the string are feasible for the output character
248 * set.
249 *
250 * Return is nonzero if all characters are OK. If not all
251 * characters are OK but `result' is non-NULL, a result _will_
252 * still be generated!
253 *
254 * FIXME: Here is probably also a good place to do escaping sorts
255 * of things. I know I at least need to escape backslash, and full
256 * stops at the starts of words are probably trouble as well.
257 */
258 static int man_convert(wchar_t *s, int maxlen,
259 char **result, int quote_props) {
260 /*
261 * FIXME. Currently this is ISO8859-1 only.
262 */
263 int doing = (result != 0);
264 int ok = TRUE;
265 char *p = NULL;
266 int plen = 0, psize = 0;
267
268 if (maxlen <= 0)
269 maxlen = -1;
270
271 for (; *s && maxlen != 0; s++, maxlen--) {
272 wchar_t c = *s;
273 char outc;
274
275 if ((c >= 32 && c <= 126) ||
276 (c >= 160 && c <= 255)) {
277 /* Char is OK. */
278 outc = (char)c;
279 } else {
280 /* Char is not OK. */
281 ok = FALSE;
282 outc = 0xBF; /* approximate the good old DEC `uh?' */
283 }
284 if (doing) {
285 if (plen+3 >= psize) {
286 psize = plen + 256;
287 p = resize(p, psize);
288 }
289 if (plen == 0 && (outc == '.' || outc == '\'') &&
290 (quote_props & QUOTE_INITCTRL)) {
291 /*
292 * Control character (. or ') at the start of a
293 * line. Quote it by putting \& (troff zero-width
294 * space) before it.
295 */
296 p[plen++] = '\\';
297 p[plen++] = '&';
298 } else if (outc == '\\') {
299 /*
300 * Quote backslashes by doubling them, always.
301 */
302 p[plen++] = '\\';
303 } else if (outc == '"' && (quote_props & QUOTE_QUOTES)) {
304 /*
305 * Double quote within double quotes. Quote it by
306 * doubling.
307 */
308 p[plen++] = '"';
309 }
310 p[plen++] = outc;
311 }
312 }
313 if (doing) {
314 p = resize(p, plen+1);
315 p[plen] = '\0';
316 *result = p;
317 }
318 return ok;
319 }
320
321 static void man_rdaddwc(rdstringc *rs, word *text, word *end,
322 int quote_props) {
323 char *c;
324
325 for (; text && text != end; text = text->next) switch (text->type) {
326 case word_HyperLink:
327 case word_HyperEnd:
328 case word_UpperXref:
329 case word_LowerXref:
330 case word_XrefEnd:
331 case word_IndexRef:
332 break;
333
334 case word_Normal:
335 case word_Emph:
336 case word_Code:
337 case word_WeakCode:
338 case word_WhiteSpace:
339 case word_EmphSpace:
340 case word_CodeSpace:
341 case word_WkCodeSpace:
342 case word_Quote:
343 case word_EmphQuote:
344 case word_CodeQuote:
345 case word_WkCodeQuote:
346 assert(text->type != word_CodeQuote &&
347 text->type != word_WkCodeQuote);
348 if (towordstyle(text->type) == word_Emph &&
349 (attraux(text->aux) == attr_First ||
350 attraux(text->aux) == attr_Only))
351 rdaddsc(rs, "\\fI");
352 else if ((towordstyle(text->type) == word_Code ||
353 towordstyle(text->type) == word_WeakCode) &&
354 (attraux(text->aux) == attr_First ||
355 attraux(text->aux) == attr_Only))
356 rdaddsc(rs, "\\fB");
357 if (removeattr(text->type) == word_Normal) {
358 if (rs->pos > 0)
359 quote_props &= ~QUOTE_INITCTRL; /* not at start any more */
360 if (man_convert(text->text, 0, &c, quote_props))
361 rdaddsc(rs, c);
362 else
363 man_rdaddwc(rs, text->alt, NULL, quote_props);
364 sfree(c);
365 } else if (removeattr(text->type) == word_WhiteSpace) {
366 rdaddc(rs, ' ');
367 } else if (removeattr(text->type) == word_Quote) {
368 rdaddc(rs, '"');
369 if (quote_props & QUOTE_QUOTES)
370 rdaddc(rs, '"');
371 }
372 if (towordstyle(text->type) == word_Emph &&
373 (attraux(text->aux) == attr_Last ||
374 attraux(text->aux) == attr_Only))
375 rdaddsc(rs, "\\fP");
376 else if ((towordstyle(text->type) == word_Code ||
377 towordstyle(text->type) == word_WeakCode) &&
378 (attraux(text->aux) == attr_Last ||
379 attraux(text->aux) == attr_Only))
380 rdaddsc(rs, "\\fP");
381 break;
382 }
383 }
384
385 static void man_text(FILE *fp, word *text, int newline, int quote_props) {
386 rdstringc t = { 0, 0, NULL };
387
388 man_rdaddwc(&t, text, NULL, quote_props | QUOTE_INITCTRL);
389 fprintf(fp, "%s", t.text);
390 sfree(t.text);
391 if (newline)
392 fputc('\n', fp);
393 }
394
395 static void man_codepara(FILE *fp, word *text) {
396 fprintf(fp, ".nf\n");
397 for (; text; text = text->next) if (text->type == word_WeakCode) {
398 char *c;
399 wchar_t *t, *e;
400 int quote_props = QUOTE_INITCTRL;
401
402 t = text->text;
403 if (text->next && text->next->type == word_Emph) {
404 e = text->next->text;
405 text = text->next;
406 } else
407 e = NULL;
408
409 while (e && *e && *t) {
410 int n;
411 int ec = *e;
412
413 for (n = 0; t[n] && e[n] && e[n] == ec; n++);
414 if (ec == 'i')
415 fprintf(fp, "\\fI");
416 else if (ec == 'b')
417 fprintf(fp, "\\fB");
418 man_convert(t, n, &c, quote_props);
419 quote_props &= ~QUOTE_INITCTRL;
420 fprintf(fp, "%s", c);
421 sfree(c);
422 if (ec == 'i' || ec == 'b')
423 fprintf(fp, "\\fP");
424 t += n;
425 e += n;
426 }
427 man_convert(t, 0, &c, quote_props);
428 fprintf(fp, "%s\n", c);
429 sfree(c);
430 }
431 fprintf(fp, ".fi\n");
432 }