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