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