Add a config directive to generate the INFO-DIR-ENTRY things that
[sgt/halibut] / bk_info.c
1 /*
2 * info backend for Halibut
3 *
4 * TODO:
5 *
6 * - configurable choice of how to allocate node names
7 * - escape, warn or simply remove commas and colons in node
8 * names; also test colons in index terms.
9 * - might be helpful to diagnose duplicate node names too!
10 * - test everything in info(1), and probably jed too
11 *
12 * Later:
13 *
14 * - configurable indentation, bullets, emphasis, quotes etc?
15 */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <assert.h>
20 #include "halibut.h"
21
22 typedef struct {
23 char *filename;
24 int maxfilesize;
25 } infoconfig;
26
27 typedef struct node_tag node;
28 struct node_tag {
29 node *listnext;
30 node *up, *prev, *next, *lastchild;
31 int pos, started_menu, filenum;
32 char *name;
33 rdstringc text;
34 };
35
36 typedef struct {
37 char *text;
38 int nnodes, nodesize;
39 node **nodes;
40 } info_idx;
41
42 static int info_convert(wchar_t *, char **);
43
44 static void info_heading(rdstringc *, word *, word *, int);
45 static void info_rule(rdstringc *, int, int);
46 static void info_para(rdstringc *, word *, char *, word *, keywordlist *,
47 int, int, int);
48 static void info_codepara(rdstringc *, word *, int, int);
49 static void info_versionid(rdstringc *, word *);
50 static void info_menu_item(rdstringc *, node *, paragraph *);
51 static word *info_transform_wordlist(word *, keywordlist *);
52 static int info_check_index(word *, node *, indexdata *);
53
54 static void info_rdaddwc(rdstringc *, word *, word *, int);
55
56 static node *info_node_new(char *name);
57 static char *info_node_name(paragraph *p);
58
59 static infoconfig info_configure(paragraph *source) {
60 infoconfig ret;
61
62 /*
63 * Defaults.
64 */
65 ret.filename = dupstr("output.info");
66 ret.maxfilesize = 64 << 10;
67
68 for (; source; source = source->next) {
69 if (source->type == para_Config) {
70 if (!ustricmp(source->keyword, L"info-filename")) {
71 sfree(ret.filename);
72 ret.filename = utoa_dup(uadv(source->keyword));
73 } else if (!ustricmp(source->keyword, L"info-max-file-size")) {
74 ret.maxfilesize = utoi(uadv(source->keyword));
75 }
76 }
77 }
78
79 return ret;
80 }
81
82 paragraph *info_config_filename(char *filename)
83 {
84 paragraph *p;
85 wchar_t *ufilename, *up;
86 int len;
87
88 p = mknew(paragraph);
89 memset(p, 0, sizeof(*p));
90 p->type = para_Config;
91 p->next = NULL;
92 p->fpos.filename = "<command line>";
93 p->fpos.line = p->fpos.col = -1;
94
95 ufilename = ufroma_dup(filename);
96 len = ustrlen(ufilename) + 2 + lenof(L"info-filename");
97 p->keyword = mknewa(wchar_t, len);
98 up = p->keyword;
99 ustrcpy(up, L"info-filename");
100 up = uadv(up);
101 ustrcpy(up, ufilename);
102 up = uadv(up);
103 *up = L'\0';
104 assert(up - p->keyword < len);
105 sfree(ufilename);
106
107 return p;
108 }
109
110 void info_backend(paragraph *sourceform, keywordlist *keywords,
111 indexdata *idx) {
112 paragraph *p;
113 infoconfig conf;
114 word *prefix, *body, *wp;
115 word spaceword;
116 char *prefixextra;
117 int nesting, nestindent;
118 int indentb, indenta;
119 int filepos;
120 int has_index;
121 rdstringc intro_text = { 0, 0, NULL };
122 node *topnode, *currnode;
123 word bullet;
124 FILE *fp;
125
126 /*
127 * FIXME
128 */
129 int width = 70, listindentbefore = 1, listindentafter = 3;
130 int indent_code = 2, index_width = 40;
131
132 IGNORE(keywords); /* we don't happen to need this */
133 IGNORE(idx); /* or this */
134
135 conf = info_configure(sourceform);
136
137 /*
138 * Go through and create a node for each section.
139 */
140 topnode = info_node_new("Top");
141 currnode = topnode;
142 for (p = sourceform; p; p = p->next) switch (p->type) {
143 /*
144 * Chapter titles.
145 */
146 case para_Chapter:
147 case para_Appendix:
148 case para_UnnumberedChapter:
149 case para_Heading:
150 case para_Subsect:
151 {
152 node *newnode, *upnode;
153 char *nodename;
154
155 nodename = info_node_name(p);
156 newnode = info_node_new(nodename);
157 sfree(nodename);
158
159 p->private_data = newnode;
160
161 if (p->parent)
162 upnode = (node *)p->parent->private_data;
163 else
164 upnode = topnode;
165 assert(upnode);
166 newnode->up = upnode;
167
168 currnode->next = newnode;
169 newnode->prev = currnode;
170
171 currnode->listnext = newnode;
172 currnode = newnode;
173 }
174 break;
175 }
176
177 /*
178 * Set up the display form of each index entry.
179 */
180 {
181 int i;
182 indexentry *entry;
183
184 for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
185 info_idx *ii = mknew(info_idx);
186 rdstringc rs = { 0, 0, NULL };
187
188 ii->nnodes = ii->nodesize = 0;
189 ii->nodes = NULL;
190
191 info_rdaddwc(&rs, entry->text, NULL, FALSE);
192 /*
193 * FIXME: splatter colons.
194 */
195 ii->text = rs.text;
196
197 entry->backend_data = ii;
198 }
199 }
200
201 /*
202 * An Info file begins with a piece of introductory text which
203 * is apparently never shown anywhere. This seems to me to be a
204 * good place to put the copyright notice and the version IDs.
205 * Also, Info directory entries are expected to go here.
206 */
207
208 rdaddsc(&intro_text,
209 "This Info file generated by Halibut, ");
210 rdaddsc(&intro_text, version);
211 rdaddsc(&intro_text, "\n\n");
212
213 for (p = sourceform; p; p = p->next)
214 if (p->type == para_Config &&
215 !ustricmp(p->keyword, L"info-dir-entry")) {
216 wchar_t *section, *shortname, *longname, *kw;
217 char *s;
218
219 section = uadv(p->keyword);
220 shortname = *section ? uadv(section) : NULL;
221 longname = *shortname ? uadv(shortname) : NULL;
222 kw = *longname ? uadv(longname) : NULL;
223
224 if (!*longname) {
225 error(err_infodirentry, &p->fpos);
226 continue;
227 }
228
229 rdaddsc(&intro_text, "INFO-DIR-SECTION ");
230 s = utoa_dup(section);
231 rdaddsc(&intro_text, s);
232 sfree(s);
233 rdaddsc(&intro_text, "\nSTART-INFO-DIR-ENTRY\n* ");
234 s = utoa_dup(shortname);
235 rdaddsc(&intro_text, s);
236 sfree(s);
237 rdaddsc(&intro_text, ": (");
238 s = dupstr(conf.filename);
239 if (strlen(s) > 5 && !strcmp(s+strlen(s)-5, ".info"))
240 s[strlen(s)-5] = '\0';
241 rdaddsc(&intro_text, s);
242 sfree(s);
243 rdaddsc(&intro_text, ")");
244 if (*kw) {
245 keyword *kwl = kw_lookup(keywords, kw);
246 if (kwl && kwl->para->private_data) {
247 node *n = (node *)kwl->para->private_data;
248 rdaddsc(&intro_text, n->name);
249 }
250 }
251 rdaddsc(&intro_text, ". ");
252 s = utoa_dup(longname);
253 rdaddsc(&intro_text, s);
254 sfree(s);
255 rdaddsc(&intro_text, "\nEND-INFO-DIR-ENTRY\n\n");
256 }
257
258 for (p = sourceform; p; p = p->next)
259 if (p->type == para_Copyright)
260 info_para(&intro_text, NULL, NULL, p->words, keywords,
261 0, 0, width);
262
263 for (p = sourceform; p; p = p->next)
264 if (p->type == para_VersionID)
265 info_versionid(&intro_text, p->words);
266
267 if (intro_text.text[intro_text.pos-1] != '\n')
268 rdaddc(&intro_text, '\n');
269
270 /* Do the title */
271 for (p = sourceform; p; p = p->next)
272 if (p->type == para_Title)
273 info_heading(&topnode->text, NULL, p->words, width);
274
275 nestindent = listindentbefore + listindentafter;
276 nesting = 0;
277
278 currnode = topnode;
279
280 /* Do the main document */
281 for (p = sourceform; p; p = p->next) switch (p->type) {
282
283 case para_QuotePush:
284 nesting += 2;
285 break;
286 case para_QuotePop:
287 nesting -= 2;
288 assert(nesting >= 0);
289 break;
290
291 case para_LcontPush:
292 nesting += nestindent;
293 break;
294 case para_LcontPop:
295 nesting -= nestindent;
296 assert(nesting >= 0);
297 break;
298
299 /*
300 * Things we ignore because we've already processed them or
301 * aren't going to touch them in this pass.
302 */
303 case para_IM:
304 case para_BR:
305 case para_Biblio: /* only touch BiblioCited */
306 case para_VersionID:
307 case para_NoCite:
308 case para_Title:
309 break;
310
311 /*
312 * Chapter titles.
313 */
314 case para_Chapter:
315 case para_Appendix:
316 case para_UnnumberedChapter:
317 case para_Heading:
318 case para_Subsect:
319 currnode = p->private_data;
320 assert(currnode);
321 assert(currnode->up);
322
323 if (!currnode->up->started_menu) {
324 rdaddsc(&currnode->up->text, "* Menu:\n\n");
325 currnode->up->started_menu = TRUE;
326 }
327 info_menu_item(&currnode->up->text, currnode, p);
328
329 has_index |= info_check_index(p->words, currnode, idx);
330 info_heading(&currnode->text, p->kwtext, p->words, width);
331 nesting = 0;
332 break;
333
334 case para_Rule:
335 info_rule(&currnode->text, nesting, width - nesting);
336 break;
337
338 case para_Normal:
339 case para_Copyright:
340 case para_DescribedThing:
341 case para_Description:
342 case para_BiblioCited:
343 case para_Bullet:
344 case para_NumberedList:
345 has_index |= info_check_index(p->words, currnode, idx);
346 if (p->type == para_Bullet) {
347 bullet.next = NULL;
348 bullet.alt = NULL;
349 bullet.type = word_Normal;
350 bullet.text = L"-"; /* FIXME: configurability */
351 prefix = &bullet;
352 prefixextra = NULL;
353 indentb = listindentbefore;
354 indenta = listindentafter;
355 } else if (p->type == para_NumberedList) {
356 prefix = p->kwtext;
357 prefixextra = "."; /* FIXME: configurability */
358 indentb = listindentbefore;
359 indenta = listindentafter;
360 } else if (p->type == para_Description) {
361 prefix = NULL;
362 prefixextra = NULL;
363 indentb = listindentbefore;
364 indenta = listindentafter;
365 } else {
366 prefix = NULL;
367 prefixextra = NULL;
368 indentb = indenta = 0;
369 }
370 if (p->type == para_BiblioCited) {
371 body = dup_word_list(p->kwtext);
372 for (wp = body; wp->next; wp = wp->next);
373 wp->next = &spaceword;
374 spaceword.next = p->words;
375 spaceword.alt = NULL;
376 spaceword.type = word_WhiteSpace;
377 spaceword.text = NULL;
378 } else {
379 wp = NULL;
380 body = p->words;
381 }
382 info_para(&currnode->text, prefix, prefixextra, body, keywords,
383 nesting + indentb, indenta,
384 width - nesting - indentb - indenta);
385 if (wp) {
386 wp->next = NULL;
387 free_word_list(body);
388 }
389 break;
390
391 case para_Code:
392 info_codepara(&currnode->text, p->words,
393 nesting + indent_code,
394 width - nesting - 2 * indent_code);
395 break;
396 }
397
398 /*
399 * Create an index node if required.
400 */
401 if (has_index) {
402 node *newnode;
403 int i, j, k;
404 indexentry *entry;
405
406 newnode = info_node_new("Index");
407 newnode->up = topnode;
408
409 currnode->next = newnode;
410 newnode->prev = currnode;
411 currnode->listnext = newnode;
412
413 rdaddsc(&newnode->text, "Index\n-----\n\n* Menu:\n\n");
414
415 info_menu_item(&topnode->text, newnode, NULL);
416
417 for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
418 info_idx *ii = (info_idx *)entry->backend_data;
419
420 for (j = 0; j < ii->nnodes; j++) {
421 int pos0 = newnode->text.pos;
422 rdaddsc(&newnode->text, "* ");
423 /*
424 * When we have multiple references for a single
425 * index term, we only display the actual term on
426 * the first line, to make it clear that the terms
427 * really are the same.
428 */
429 if (j == 0)
430 rdaddsc(&newnode->text, ii->text);
431 for (k = newnode->text.pos - pos0; k < index_width; k++)
432 rdaddc(&newnode->text, ' ');
433 rdaddsc(&newnode->text, ": ");
434 rdaddsc(&newnode->text, ii->nodes[j]->name);
435 rdaddsc(&newnode->text, ".\n");
436 }
437 }
438 }
439
440 /*
441 * Finalise the text of each node, by adding the ^_ delimiter
442 * and the node line at the top.
443 */
444 for (currnode = topnode; currnode; currnode = currnode->listnext) {
445 char *origtext = currnode->text.text;
446 currnode->text.text = NULL;
447 currnode->text.pos = currnode->text.size = 0;
448 rdaddsc(&currnode->text, "\037\nFile: ");
449 rdaddsc(&currnode->text, conf.filename);
450 rdaddsc(&currnode->text, ", Node: ");
451 rdaddsc(&currnode->text, currnode->name);
452 if (currnode->prev) {
453 rdaddsc(&currnode->text, ", Prev: ");
454 rdaddsc(&currnode->text, currnode->prev->name);
455 }
456 rdaddsc(&currnode->text, ", Up: ");
457 rdaddsc(&currnode->text, (currnode->up ?
458 currnode->up->name : "(dir)"));
459 if (currnode->next) {
460 rdaddsc(&currnode->text, ", Next: ");
461 rdaddsc(&currnode->text, currnode->next->name);
462 }
463 rdaddsc(&currnode->text, "\n\n");
464 rdaddsc(&currnode->text, origtext);
465 /*
466 * Just make _absolutely_ sure we end with a newline.
467 */
468 if (currnode->text.text[currnode->text.pos-1] != '\n')
469 rdaddc(&currnode->text, '\n');
470
471 sfree(origtext);
472 }
473
474 /*
475 * Compute the offsets for the tag table.
476 */
477 filepos = intro_text.pos;
478 for (currnode = topnode; currnode; currnode = currnode->listnext) {
479 currnode->pos = filepos;
480 filepos += currnode->text.pos;
481 }
482
483 /*
484 * Split into sub-files.
485 */
486 if (conf.maxfilesize > 0) {
487 int currfilesize = intro_text.pos, currfilenum = 1;
488 for (currnode = topnode; currnode; currnode = currnode->listnext) {
489 if (currfilesize > intro_text.pos &&
490 currfilesize + currnode->text.pos > conf.maxfilesize) {
491 currfilenum++;
492 currfilesize = intro_text.pos;
493 }
494 currnode->filenum = currfilenum;
495 currfilesize += currnode->text.pos;
496 }
497 }
498
499 /*
500 * Write the primary output file.
501 */
502 fp = fopen(conf.filename, "w");
503 if (!fp) {
504 error(err_cantopenw, conf.filename);
505 return;
506 }
507 fputs(intro_text.text, fp);
508 if (conf.maxfilesize == 0) {
509 for (currnode = topnode; currnode; currnode = currnode->listnext)
510 fputs(currnode->text.text, fp);
511 } else {
512 int filenum = 0;
513 fprintf(fp, "\037\nIndirect:\n");
514 for (currnode = topnode; currnode; currnode = currnode->listnext)
515 if (filenum != currnode->filenum) {
516 filenum = currnode->filenum;
517 fprintf(fp, "%s-%d: %d\n", conf.filename, filenum,
518 currnode->pos);
519 }
520 }
521 fprintf(fp, "\037\nTag Table:\n");
522 if (conf.maxfilesize > 0)
523 fprintf(fp, "(Indirect)\n");
524 for (currnode = topnode; currnode; currnode = currnode->listnext)
525 fprintf(fp, "Node: %s\177%d\n", currnode->name, currnode->pos);
526 fprintf(fp, "\037\nEnd Tag Table\n");
527 fclose(fp);
528
529 /*
530 * Write the subfiles.
531 */
532 if (conf.maxfilesize > 0) {
533 int filenum = 0;
534 fp = NULL;
535
536 for (currnode = topnode; currnode; currnode = currnode->listnext) {
537 if (filenum != currnode->filenum) {
538 char *fname;
539
540 filenum = currnode->filenum;
541
542 if (fp)
543 fclose(fp);
544 fname = mknewa(char, strlen(conf.filename) + 40);
545 sprintf(fname, "%s-%d", conf.filename, filenum);
546 fp = fopen(fname, "w");
547 if (!fp) {
548 error(err_cantopenw, fname);
549 return;
550 }
551 sfree(fname);
552 fputs(intro_text.text, fp);
553 }
554 fputs(currnode->text.text, fp);
555 }
556
557 if (fp)
558 fclose(fp);
559 }
560 }
561
562 static int info_check_index(word *w, node *n, indexdata *idx)
563 {
564 int ret = 0;
565
566 for (; w; w = w->next) {
567 if (w->type == word_IndexRef) {
568 indextag *tag;
569 int i;
570
571 tag = index_findtag(idx, w->text);
572 if (!tag)
573 break;
574
575 for (i = 0; i < tag->nrefs; i++) {
576 indexentry *entry = tag->refs[i];
577 info_idx *ii = (info_idx *)entry->backend_data;
578
579 if (ii->nnodes > 0 && ii->nodes[ii->nnodes-1] == n) {
580 /*
581 * If the same index term is indexed twice
582 * within the same section, we only want to
583 * mention it once in the index. So do nothing
584 * here.
585 */
586 continue;
587 }
588
589 if (ii->nnodes >= ii->nodesize) {
590 ii->nodesize += 32;
591 ii->nodes = resize(ii->nodes, ii->nodesize);
592 }
593
594 ii->nodes[ii->nnodes++] = n;
595
596 ret = 1;
597 }
598 }
599 }
600
601 return ret;
602 }
603
604 /*
605 * Convert a wide string into a string of chars. If `result' is
606 * non-NULL, mallocs the resulting string and stores a pointer to
607 * it in `*result'. If `result' is NULL, merely checks whether all
608 * characters in the string are feasible for the output character
609 * set.
610 *
611 * Return is nonzero if all characters are OK. If not all
612 * characters are OK but `result' is non-NULL, a result _will_
613 * still be generated!
614 */
615 static int info_convert(wchar_t *s, char **result) {
616 /*
617 * FIXME. Currently this is ISO8859-1 only.
618 */
619 int doing = (result != 0);
620 int ok = TRUE;
621 char *p = NULL;
622 int plen = 0, psize = 0;
623
624 for (; *s; s++) {
625 wchar_t c = *s;
626 char outc;
627
628 if ((c >= 32 && c <= 126) ||
629 (c >= 160 && c <= 255)) {
630 /* Char is OK. */
631 outc = (char)c;
632 } else {
633 /* Char is not OK. */
634 ok = FALSE;
635 outc = 0xBF; /* approximate the good old DEC `uh?' */
636 }
637 if (doing) {
638 if (plen >= psize) {
639 psize = plen + 256;
640 p = resize(p, psize);
641 }
642 p[plen++] = outc;
643 }
644 }
645 if (doing) {
646 p = resize(p, plen+1);
647 p[plen] = '\0';
648 *result = p;
649 }
650 return ok;
651 }
652
653 static word *info_transform_wordlist(word *words, keywordlist *keywords)
654 {
655 word *ret = dup_word_list(words);
656 word *w;
657 keyword *kwl;
658
659 for (w = ret; w; w = w->next) {
660 w->private_data = NULL;
661 if (w->type == word_UpperXref || w->type == word_LowerXref) {
662 kwl = kw_lookup(keywords, w->text);
663 if (kwl) {
664 if (kwl->para->type == para_NumberedList ||
665 kwl->para->type == para_BiblioCited) {
666 /*
667 * In Info, we do nothing special for xrefs to
668 * numbered list items or bibliography entries.
669 */
670 break;
671 } else {
672 /*
673 * An xref to a different section has its text
674 * completely replaced.
675 */
676 word *w2, *w3, *w4;
677 w2 = w3 = w->next;
678 w4 = NULL;
679 while (w2) {
680 if (w2->type == word_XrefEnd) {
681 w4 = w2->next;
682 w2->next = NULL;
683 break;
684 }
685 w2 = w2->next;
686 }
687 free_word_list(w3);
688
689 /*
690 * Now w is the UpperXref / LowerXref we
691 * started with, and w4 is the next word after
692 * the corresponding XrefEnd (if any). The
693 * simplest thing is just to stick a pointer to
694 * the target node structure in the private
695 * data field of the xref word, and let
696 * info_rdaddwc and friends read the node name
697 * out from there.
698 */
699 w->next = w4;
700 w->private_data = kwl->para->private_data;
701 assert(w->private_data);
702 }
703 }
704 }
705 }
706
707 return ret;
708 }
709
710 static void info_rdaddwc(rdstringc *rs, word *words, word *end, int xrefs) {
711 char *c;
712
713 for (; words && words != end; words = words->next) switch (words->type) {
714 case word_HyperLink:
715 case word_HyperEnd:
716 case word_XrefEnd:
717 case word_IndexRef:
718 break;
719
720 case word_Normal:
721 case word_Emph:
722 case word_Code:
723 case word_WeakCode:
724 case word_WhiteSpace:
725 case word_EmphSpace:
726 case word_CodeSpace:
727 case word_WkCodeSpace:
728 case word_Quote:
729 case word_EmphQuote:
730 case word_CodeQuote:
731 case word_WkCodeQuote:
732 assert(words->type != word_CodeQuote &&
733 words->type != word_WkCodeQuote);
734 if (towordstyle(words->type) == word_Emph &&
735 (attraux(words->aux) == attr_First ||
736 attraux(words->aux) == attr_Only))
737 rdaddc(rs, '_'); /* FIXME: configurability */
738 else if (towordstyle(words->type) == word_Code &&
739 (attraux(words->aux) == attr_First ||
740 attraux(words->aux) == attr_Only))
741 rdaddc(rs, '`'); /* FIXME: configurability */
742 if (removeattr(words->type) == word_Normal) {
743 if (info_convert(words->text, &c))
744 rdaddsc(rs, c);
745 else
746 info_rdaddwc(rs, words->alt, NULL, FALSE);
747 sfree(c);
748 } else if (removeattr(words->type) == word_WhiteSpace) {
749 rdaddc(rs, ' ');
750 } else if (removeattr(words->type) == word_Quote) {
751 rdaddc(rs, quoteaux(words->aux) == quote_Open ? '`' : '\'');
752 /* FIXME: configurability */
753 }
754 if (towordstyle(words->type) == word_Emph &&
755 (attraux(words->aux) == attr_Last ||
756 attraux(words->aux) == attr_Only))
757 rdaddc(rs, '_'); /* FIXME: configurability */
758 else if (towordstyle(words->type) == word_Code &&
759 (attraux(words->aux) == attr_Last ||
760 attraux(words->aux) == attr_Only))
761 rdaddc(rs, '\''); /* FIXME: configurability */
762 break;
763
764 case word_UpperXref:
765 case word_LowerXref:
766 if (xrefs && words->private_data) {
767 rdaddsc(rs, "*Note ");
768 rdaddsc(rs, ((node *)words->private_data)->name);
769 rdaddsc(rs, "::");
770 }
771 break;
772 }
773 }
774
775 static int info_width_internal(word *words, int xrefs);
776
777 static int info_width_internal_list(word *words, int xrefs) {
778 int w = 0;
779 while (words) {
780 w += info_width_internal(words, xrefs);
781 words = words->next;
782 }
783 return w;
784 }
785
786 static int info_width_internal(word *words, int xrefs) {
787 switch (words->type) {
788 case word_HyperLink:
789 case word_HyperEnd:
790 case word_XrefEnd:
791 case word_IndexRef:
792 return 0;
793
794 case word_Normal:
795 case word_Emph:
796 case word_Code:
797 case word_WeakCode:
798 return (((words->type == word_Emph ||
799 words->type == word_Code)
800 ? (attraux(words->aux) == attr_Only ? 2 :
801 attraux(words->aux) == attr_Always ? 0 : 1)
802 : 0) +
803 (info_convert(words->text, NULL) ?
804 ustrlen(words->text) :
805 info_width_internal_list(words->alt, xrefs)));
806
807 case word_WhiteSpace:
808 case word_EmphSpace:
809 case word_CodeSpace:
810 case word_WkCodeSpace:
811 case word_Quote:
812 case word_EmphQuote:
813 case word_CodeQuote:
814 case word_WkCodeQuote:
815 assert(words->type != word_CodeQuote &&
816 words->type != word_WkCodeQuote);
817 return (((towordstyle(words->type) == word_Emph ||
818 towordstyle(words->type) == word_Code)
819 ? (attraux(words->aux) == attr_Only ? 2 :
820 attraux(words->aux) == attr_Always ? 0 : 1)
821 : 0) + 1);
822
823 case word_UpperXref:
824 case word_LowerXref:
825 if (xrefs && words->private_data) {
826 /* "*Note " plus "::" comes to 8 characters */
827 return 8 + strlen(((node *)words->private_data)->name);
828 }
829 break;
830 }
831 return 0; /* should never happen */
832 }
833
834 static int info_width_noxrefs(word *words)
835 {
836 return info_width_internal(words, FALSE);
837 }
838 static int info_width_xrefs(word *words)
839 {
840 return info_width_internal(words, TRUE);
841 }
842
843 static void info_heading(rdstringc *text, word *tprefix,
844 word *words, int width) {
845 rdstringc t = { 0, 0, NULL };
846 int margin, length;
847 int firstlinewidth, wrapwidth;
848 int i;
849 wrappedline *wrapping, *p;
850
851 if (tprefix) {
852 info_rdaddwc(&t, tprefix, NULL, FALSE);
853 rdaddsc(&t, ": "); /* FIXME: configurability */
854 }
855 margin = length = (t.text ? strlen(t.text) : 0);
856
857 margin = 0;
858 firstlinewidth = width - length;
859 wrapwidth = width;
860
861 wrapping = wrap_para(words, firstlinewidth, wrapwidth, info_width_noxrefs);
862 for (p = wrapping; p; p = p->next) {
863 info_rdaddwc(&t, p->begin, p->end, FALSE);
864 length = (t.text ? strlen(t.text) : 0);
865 for (i = 0; i < margin; i++)
866 rdaddc(text, ' ');
867 rdaddsc(text, t.text);
868 rdaddc(text, '\n');
869 for (i = 0; i < margin; i++)
870 rdaddc(text, ' ');
871 while (length--)
872 rdaddc(text, '-');
873 rdaddc(text, '\n');
874 margin = 0;
875 sfree(t.text);
876 t = empty_rdstringc;
877 }
878 wrap_free(wrapping);
879 rdaddc(text, '\n');
880
881 sfree(t.text);
882 }
883
884 static void info_rule(rdstringc *text, int indent, int width) {
885 while (indent--) rdaddc(text, ' ');
886 while (width--) rdaddc(text, '-');
887 rdaddc(text, '\n');
888 rdaddc(text, '\n');
889 }
890
891 static void info_para(rdstringc *text, word *prefix, char *prefixextra,
892 word *input, keywordlist *keywords,
893 int indent, int extraindent, int width) {
894 wrappedline *wrapping, *p;
895 word *words;
896 rdstringc pfx = { 0, 0, NULL };
897 int e;
898 int i;
899 int firstlinewidth = width;
900
901 words = info_transform_wordlist(input, keywords);
902
903 if (prefix) {
904 info_rdaddwc(&pfx, prefix, NULL, FALSE);
905 if (prefixextra)
906 rdaddsc(&pfx, prefixextra);
907 for (i = 0; i < indent; i++)
908 rdaddc(text, ' ');
909 rdaddsc(text, pfx.text);
910 /* If the prefix is too long, shorten the first line to fit. */
911 e = extraindent - strlen(pfx.text);
912 if (e < 0) {
913 firstlinewidth += e; /* this decreases it, since e < 0 */
914 if (firstlinewidth < 0) {
915 e = indent + extraindent;
916 firstlinewidth = width;
917 rdaddc(text, '\n');
918 } else
919 e = 0;
920 }
921 sfree(pfx.text);
922 } else
923 e = indent + extraindent;
924
925 wrapping = wrap_para(words, firstlinewidth, width, info_width_xrefs);
926 for (p = wrapping; p; p = p->next) {
927 for (i = 0; i < e; i++)
928 rdaddc(text, ' ');
929 info_rdaddwc(text, p->begin, p->end, TRUE);
930 rdaddc(text, '\n');
931 e = indent + extraindent;
932 }
933 wrap_free(wrapping);
934 rdaddc(text, '\n');
935
936 free_word_list(words);
937 }
938
939 static void info_codepara(rdstringc *text, word *words,
940 int indent, int width) {
941 int i;
942
943 for (; words; words = words->next) if (words->type == word_WeakCode) {
944 char *c;
945 info_convert(words->text, &c);
946 if (strlen(c) > (size_t)width) {
947 /* FIXME: warn */
948 }
949 for (i = 0; i < indent; i++)
950 rdaddc(text, ' ');
951 rdaddsc(text, c);
952 rdaddc(text, '\n');
953 sfree(c);
954 }
955
956 rdaddc(text, '\n');
957 }
958
959 static void info_versionid(rdstringc *text, word *words) {
960 rdaddc(text, '['); /* FIXME: configurability */
961 info_rdaddwc(text, words, NULL, FALSE);
962 rdaddsc(text, "]\n");
963 }
964
965 static node *info_node_new(char *name)
966 {
967 node *n;
968
969 n = mknew(node);
970 n->text.text = NULL;
971 n->text.pos = n->text.size = 0;
972 n->up = n->next = n->prev = n->lastchild = n->listnext = NULL;
973 n->name = dupstr(name);
974 n->started_menu = FALSE;
975
976 return n;
977 }
978
979 static char *info_node_name(paragraph *p)
980 {
981 rdstringc rsc = { 0, 0, NULL };
982 info_rdaddwc(&rsc, p->kwtext ? p->kwtext : p->words, NULL, FALSE);
983 return rsc.text;
984 }
985
986 static void info_menu_item(rdstringc *text, node *n, paragraph *p)
987 {
988 /*
989 * FIXME: Depending on how we're doing node names in this info
990 * file, we might want to do
991 *
992 * * Node name:: Chapter title
993 *
994 * _or_
995 *
996 * * Chapter number: Node name.
997 *
998 *
999 */
1000 rdaddsc(text, "* ");
1001 rdaddsc(text, n->name);
1002 rdaddsc(text, "::");
1003 if (p) {
1004 rdaddc(text, ' ');
1005 info_rdaddwc(text, p->words, NULL, FALSE);
1006 }
1007 rdaddc(text, '\n');
1008 }