Added an info(1) backend, which constructs .info files directly
[sgt/halibut] / bk_info.c
1 /*
2 * info backend for Halibut
3 *
4 * TODO:
5 *
6 * - basic vital configuration: Info dir entries in heading, and
7 * how to allocate node names
8 * - escape, warn or simply remove commas and colons in node
9 * names; also test colons in index terms.
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 *
206 * FIXME: also Info directory entries are expected to go here.
207 * This will need to be a configurable thing of some sort.
208 */
209
210 rdaddsc(&intro_text,
211 "This Info file generated by Halibut, ");
212 rdaddsc(&intro_text, version);
213 rdaddsc(&intro_text, "\n\n");
214
215 for (p = sourceform; p; p = p->next)
216 if (p->type == para_Copyright)
217 info_para(&intro_text, NULL, NULL, p->words, keywords,
218 0, 0, width);
219
220 for (p = sourceform; p; p = p->next)
221 if (p->type == para_VersionID)
222 info_versionid(&intro_text, p->words);
223
224 if (intro_text.text[intro_text.pos-1] != '\n')
225 rdaddc(&intro_text, '\n');
226
227 /* Do the title */
228 for (p = sourceform; p; p = p->next)
229 if (p->type == para_Title)
230 info_heading(&topnode->text, NULL, p->words, width);
231
232 nestindent = listindentbefore + listindentafter;
233 nesting = 0;
234
235 currnode = topnode;
236
237 /* Do the main document */
238 for (p = sourceform; p; p = p->next) switch (p->type) {
239
240 case para_QuotePush:
241 nesting += 2;
242 break;
243 case para_QuotePop:
244 nesting -= 2;
245 assert(nesting >= 0);
246 break;
247
248 case para_LcontPush:
249 nesting += nestindent;
250 break;
251 case para_LcontPop:
252 nesting -= nestindent;
253 assert(nesting >= 0);
254 break;
255
256 /*
257 * Things we ignore because we've already processed them or
258 * aren't going to touch them in this pass.
259 */
260 case para_IM:
261 case para_BR:
262 case para_Biblio: /* only touch BiblioCited */
263 case para_VersionID:
264 case para_NoCite:
265 case para_Title:
266 break;
267
268 /*
269 * Chapter titles.
270 */
271 case para_Chapter:
272 case para_Appendix:
273 case para_UnnumberedChapter:
274 case para_Heading:
275 case para_Subsect:
276 currnode = p->private_data;
277 assert(currnode);
278 assert(currnode->up);
279
280 if (!currnode->up->started_menu) {
281 rdaddsc(&currnode->up->text, "* Menu:\n\n");
282 currnode->up->started_menu = TRUE;
283 }
284 info_menu_item(&currnode->up->text, currnode, p);
285
286 has_index |= info_check_index(p->words, currnode, idx);
287 info_heading(&currnode->text, p->kwtext, p->words, width);
288 nesting = 0;
289 break;
290
291 case para_Rule:
292 info_rule(&currnode->text, nesting, width - nesting);
293 break;
294
295 case para_Normal:
296 case para_Copyright:
297 case para_DescribedThing:
298 case para_Description:
299 case para_BiblioCited:
300 case para_Bullet:
301 case para_NumberedList:
302 has_index |= info_check_index(p->words, currnode, idx);
303 if (p->type == para_Bullet) {
304 bullet.next = NULL;
305 bullet.alt = NULL;
306 bullet.type = word_Normal;
307 bullet.text = L"-"; /* FIXME: configurability */
308 prefix = &bullet;
309 prefixextra = NULL;
310 indentb = listindentbefore;
311 indenta = listindentafter;
312 } else if (p->type == para_NumberedList) {
313 prefix = p->kwtext;
314 prefixextra = "."; /* FIXME: configurability */
315 indentb = listindentbefore;
316 indenta = listindentafter;
317 } else if (p->type == para_Description) {
318 prefix = NULL;
319 prefixextra = NULL;
320 indentb = listindentbefore;
321 indenta = listindentafter;
322 } else {
323 prefix = NULL;
324 prefixextra = NULL;
325 indentb = indenta = 0;
326 }
327 if (p->type == para_BiblioCited) {
328 body = dup_word_list(p->kwtext);
329 for (wp = body; wp->next; wp = wp->next);
330 wp->next = &spaceword;
331 spaceword.next = p->words;
332 spaceword.alt = NULL;
333 spaceword.type = word_WhiteSpace;
334 spaceword.text = NULL;
335 } else {
336 wp = NULL;
337 body = p->words;
338 }
339 info_para(&currnode->text, prefix, prefixextra, body, keywords,
340 nesting + indentb, indenta,
341 width - nesting - indentb - indenta);
342 if (wp) {
343 wp->next = NULL;
344 free_word_list(body);
345 }
346 break;
347
348 case para_Code:
349 info_codepara(&currnode->text, p->words,
350 nesting + indent_code,
351 width - nesting - 2 * indent_code);
352 break;
353 }
354
355 /*
356 * Create an index node if required.
357 */
358 if (has_index) {
359 node *newnode;
360 int i, j, k;
361 indexentry *entry;
362
363 newnode = info_node_new("Index");
364 newnode->up = topnode;
365
366 currnode->next = newnode;
367 newnode->prev = currnode;
368 currnode->listnext = newnode;
369
370 rdaddsc(&newnode->text, "Index\n-----\n\n* Menu:\n\n");
371
372 info_menu_item(&topnode->text, newnode, NULL);
373
374 for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
375 info_idx *ii = (info_idx *)entry->backend_data;
376
377 for (j = 0; j < ii->nnodes; j++) {
378 int pos0 = newnode->text.pos;
379 rdaddsc(&newnode->text, "* ");
380 /*
381 * When we have multiple references for a single
382 * index term, we only display the actual term on
383 * the first line, to make it clear that the terms
384 * really are the same.
385 */
386 if (j == 0)
387 rdaddsc(&newnode->text, ii->text);
388 for (k = newnode->text.pos - pos0; k < index_width; k++)
389 rdaddc(&newnode->text, ' ');
390 rdaddsc(&newnode->text, ": ");
391 rdaddsc(&newnode->text, ii->nodes[j]->name);
392 rdaddsc(&newnode->text, ".\n");
393 }
394 }
395 }
396
397 /*
398 * Finalise the text of each node, by adding the ^_ delimiter
399 * and the node line at the top.
400 */
401 for (currnode = topnode; currnode; currnode = currnode->listnext) {
402 char *origtext = currnode->text.text;
403 currnode->text.text = NULL;
404 currnode->text.pos = currnode->text.size = 0;
405 rdaddsc(&currnode->text, "\037\nFile: ");
406 rdaddsc(&currnode->text, conf.filename);
407 rdaddsc(&currnode->text, ", Node: ");
408 rdaddsc(&currnode->text, currnode->name);
409 if (currnode->prev) {
410 rdaddsc(&currnode->text, ", Prev: ");
411 rdaddsc(&currnode->text, currnode->prev->name);
412 }
413 rdaddsc(&currnode->text, ", Up: ");
414 rdaddsc(&currnode->text, (currnode->up ?
415 currnode->up->name : "(dir)"));
416 if (currnode->next) {
417 rdaddsc(&currnode->text, ", Next: ");
418 rdaddsc(&currnode->text, currnode->next->name);
419 }
420 rdaddsc(&currnode->text, "\n\n");
421 rdaddsc(&currnode->text, origtext);
422 /*
423 * Just make _absolutely_ sure we end with a newline.
424 */
425 if (currnode->text.text[currnode->text.pos-1] != '\n')
426 rdaddc(&currnode->text, '\n');
427
428 sfree(origtext);
429 }
430
431 /*
432 * Compute the offsets for the tag table.
433 */
434 filepos = intro_text.pos;
435 for (currnode = topnode; currnode; currnode = currnode->listnext) {
436 currnode->pos = filepos;
437 filepos += currnode->text.pos;
438 }
439
440 /*
441 * Split into sub-files.
442 */
443 if (conf.maxfilesize > 0) {
444 int currfilesize = intro_text.pos, currfilenum = 1;
445 for (currnode = topnode; currnode; currnode = currnode->listnext) {
446 if (currfilesize > intro_text.pos &&
447 currfilesize + currnode->text.pos > conf.maxfilesize) {
448 currfilenum++;
449 currfilesize = intro_text.pos;
450 }
451 currnode->filenum = currfilenum;
452 currfilesize += currnode->text.pos;
453 }
454 }
455
456 /*
457 * Write the primary output file.
458 */
459 fp = fopen(conf.filename, "w");
460 if (!fp) {
461 error(err_cantopenw, conf.filename);
462 return;
463 }
464 fputs(intro_text.text, fp);
465 if (conf.maxfilesize == 0) {
466 for (currnode = topnode; currnode; currnode = currnode->listnext)
467 fputs(currnode->text.text, fp);
468 } else {
469 int filenum = 0;
470 fprintf(fp, "\037\nIndirect:\n");
471 for (currnode = topnode; currnode; currnode = currnode->listnext)
472 if (filenum != currnode->filenum) {
473 filenum = currnode->filenum;
474 fprintf(fp, "%s-%d: %d\n", conf.filename, filenum,
475 currnode->pos);
476 }
477 }
478 fprintf(fp, "\037\nTag Table:\n");
479 if (conf.maxfilesize > 0)
480 fprintf(fp, "(Indirect)\n");
481 for (currnode = topnode; currnode; currnode = currnode->listnext)
482 fprintf(fp, "Node: %s\177%d\n", currnode->name, currnode->pos);
483 fprintf(fp, "\037\nEnd Tag Table\n");
484 fclose(fp);
485
486 /*
487 * Write the subfiles.
488 */
489 if (conf.maxfilesize > 0) {
490 int filenum = 0;
491 fp = NULL;
492
493 for (currnode = topnode; currnode; currnode = currnode->listnext) {
494 if (filenum != currnode->filenum) {
495 char *fname;
496
497 filenum = currnode->filenum;
498
499 if (fp)
500 fclose(fp);
501 fname = mknewa(char, strlen(conf.filename) + 40);
502 sprintf(fname, "%s-%d", conf.filename, filenum);
503 fp = fopen(fname, "w");
504 if (!fp) {
505 error(err_cantopenw, fname);
506 return;
507 }
508 sfree(fname);
509 fputs(intro_text.text, fp);
510 }
511 fputs(currnode->text.text, fp);
512 }
513
514 if (fp)
515 fclose(fp);
516 }
517 }
518
519 static int info_check_index(word *w, node *n, indexdata *idx)
520 {
521 int ret = 0;
522
523 for (; w; w = w->next) {
524 if (w->type == word_IndexRef) {
525 indextag *tag;
526 int i;
527
528 tag = index_findtag(idx, w->text);
529 if (!tag)
530 break;
531
532 for (i = 0; i < tag->nrefs; i++) {
533 indexentry *entry = tag->refs[i];
534 info_idx *ii = (info_idx *)entry->backend_data;
535
536 if (ii->nnodes > 0 && ii->nodes[ii->nnodes-1] == n) {
537 /*
538 * If the same index term is indexed twice
539 * within the same section, we only want to
540 * mention it once in the index. So do nothing
541 * here.
542 */
543 continue;
544 }
545
546 if (ii->nnodes >= ii->nodesize) {
547 ii->nodesize += 32;
548 ii->nodes = resize(ii->nodes, ii->nodesize);
549 }
550
551 ii->nodes[ii->nnodes++] = n;
552
553 ret = 1;
554 }
555 }
556 }
557
558 return ret;
559 }
560
561 /*
562 * Convert a wide string into a string of chars. If `result' is
563 * non-NULL, mallocs the resulting string and stores a pointer to
564 * it in `*result'. If `result' is NULL, merely checks whether all
565 * characters in the string are feasible for the output character
566 * set.
567 *
568 * Return is nonzero if all characters are OK. If not all
569 * characters are OK but `result' is non-NULL, a result _will_
570 * still be generated!
571 */
572 static int info_convert(wchar_t *s, char **result) {
573 /*
574 * FIXME. Currently this is ISO8859-1 only.
575 */
576 int doing = (result != 0);
577 int ok = TRUE;
578 char *p = NULL;
579 int plen = 0, psize = 0;
580
581 for (; *s; s++) {
582 wchar_t c = *s;
583 char outc;
584
585 if ((c >= 32 && c <= 126) ||
586 (c >= 160 && c <= 255)) {
587 /* Char is OK. */
588 outc = (char)c;
589 } else {
590 /* Char is not OK. */
591 ok = FALSE;
592 outc = 0xBF; /* approximate the good old DEC `uh?' */
593 }
594 if (doing) {
595 if (plen >= psize) {
596 psize = plen + 256;
597 p = resize(p, psize);
598 }
599 p[plen++] = outc;
600 }
601 }
602 if (doing) {
603 p = resize(p, plen+1);
604 p[plen] = '\0';
605 *result = p;
606 }
607 return ok;
608 }
609
610 static word *info_transform_wordlist(word *words, keywordlist *keywords)
611 {
612 word *ret = dup_word_list(words);
613 word *w;
614 keyword *kwl;
615
616 for (w = ret; w; w = w->next) {
617 w->private_data = NULL;
618 if (w->type == word_UpperXref || w->type == word_LowerXref) {
619 kwl = kw_lookup(keywords, w->text);
620 if (kwl) {
621 if (kwl->para->type == para_NumberedList ||
622 kwl->para->type == para_BiblioCited) {
623 /*
624 * In Info, we do nothing special for xrefs to
625 * numbered list items or bibliography entries.
626 */
627 break;
628 } else {
629 /*
630 * An xref to a different section has its text
631 * completely replaced.
632 */
633 word *w2, *w3, *w4;
634 w2 = w3 = w->next;
635 w4 = NULL;
636 while (w2) {
637 if (w2->type == word_XrefEnd) {
638 w4 = w2->next;
639 w2->next = NULL;
640 break;
641 }
642 w2 = w2->next;
643 }
644 free_word_list(w3);
645
646 /*
647 * Now w is the UpperXref / LowerXref we
648 * started with, and w4 is the next word after
649 * the corresponding XrefEnd (if any). The
650 * simplest thing is just to stick a pointer to
651 * the target node structure in the private
652 * data field of the xref word, and let
653 * info_rdaddwc and friends read the node name
654 * out from there.
655 */
656 w->next = w4;
657 w->private_data = kwl->para->private_data;
658 assert(w->private_data);
659 }
660 }
661 }
662 }
663
664 return ret;
665 }
666
667 static void info_rdaddwc(rdstringc *rs, word *words, word *end, int xrefs) {
668 char *c;
669
670 for (; words && words != end; words = words->next) switch (words->type) {
671 case word_HyperLink:
672 case word_HyperEnd:
673 case word_XrefEnd:
674 case word_IndexRef:
675 break;
676
677 case word_Normal:
678 case word_Emph:
679 case word_Code:
680 case word_WeakCode:
681 case word_WhiteSpace:
682 case word_EmphSpace:
683 case word_CodeSpace:
684 case word_WkCodeSpace:
685 case word_Quote:
686 case word_EmphQuote:
687 case word_CodeQuote:
688 case word_WkCodeQuote:
689 assert(words->type != word_CodeQuote &&
690 words->type != word_WkCodeQuote);
691 if (towordstyle(words->type) == word_Emph &&
692 (attraux(words->aux) == attr_First ||
693 attraux(words->aux) == attr_Only))
694 rdaddc(rs, '_'); /* FIXME: configurability */
695 else if (towordstyle(words->type) == word_Code &&
696 (attraux(words->aux) == attr_First ||
697 attraux(words->aux) == attr_Only))
698 rdaddc(rs, '`'); /* FIXME: configurability */
699 if (removeattr(words->type) == word_Normal) {
700 if (info_convert(words->text, &c))
701 rdaddsc(rs, c);
702 else
703 info_rdaddwc(rs, words->alt, NULL, FALSE);
704 sfree(c);
705 } else if (removeattr(words->type) == word_WhiteSpace) {
706 rdaddc(rs, ' ');
707 } else if (removeattr(words->type) == word_Quote) {
708 rdaddc(rs, quoteaux(words->aux) == quote_Open ? '`' : '\'');
709 /* FIXME: configurability */
710 }
711 if (towordstyle(words->type) == word_Emph &&
712 (attraux(words->aux) == attr_Last ||
713 attraux(words->aux) == attr_Only))
714 rdaddc(rs, '_'); /* FIXME: configurability */
715 else if (towordstyle(words->type) == word_Code &&
716 (attraux(words->aux) == attr_Last ||
717 attraux(words->aux) == attr_Only))
718 rdaddc(rs, '\''); /* FIXME: configurability */
719 break;
720
721 case word_UpperXref:
722 case word_LowerXref:
723 if (xrefs && words->private_data) {
724 rdaddsc(rs, "*Note ");
725 rdaddsc(rs, ((node *)words->private_data)->name);
726 rdaddsc(rs, "::");
727 }
728 break;
729 }
730 }
731
732 static int info_width_internal(word *words, int xrefs);
733
734 static int info_width_internal_list(word *words, int xrefs) {
735 int w = 0;
736 while (words) {
737 w += info_width_internal(words, xrefs);
738 words = words->next;
739 }
740 return w;
741 }
742
743 static int info_width_internal(word *words, int xrefs) {
744 switch (words->type) {
745 case word_HyperLink:
746 case word_HyperEnd:
747 case word_XrefEnd:
748 case word_IndexRef:
749 return 0;
750
751 case word_Normal:
752 case word_Emph:
753 case word_Code:
754 case word_WeakCode:
755 return (((words->type == word_Emph ||
756 words->type == word_Code)
757 ? (attraux(words->aux) == attr_Only ? 2 :
758 attraux(words->aux) == attr_Always ? 0 : 1)
759 : 0) +
760 (info_convert(words->text, NULL) ?
761 ustrlen(words->text) :
762 info_width_internal_list(words->alt, xrefs)));
763
764 case word_WhiteSpace:
765 case word_EmphSpace:
766 case word_CodeSpace:
767 case word_WkCodeSpace:
768 case word_Quote:
769 case word_EmphQuote:
770 case word_CodeQuote:
771 case word_WkCodeQuote:
772 assert(words->type != word_CodeQuote &&
773 words->type != word_WkCodeQuote);
774 return (((towordstyle(words->type) == word_Emph ||
775 towordstyle(words->type) == word_Code)
776 ? (attraux(words->aux) == attr_Only ? 2 :
777 attraux(words->aux) == attr_Always ? 0 : 1)
778 : 0) + 1);
779
780 case word_UpperXref:
781 case word_LowerXref:
782 if (xrefs && words->private_data) {
783 /* "*Note " plus "::" comes to 8 characters */
784 return 8 + strlen(((node *)words->private_data)->name);
785 }
786 break;
787 }
788 return 0; /* should never happen */
789 }
790
791 static int info_width_noxrefs(word *words)
792 {
793 return info_width_internal(words, FALSE);
794 }
795 static int info_width_xrefs(word *words)
796 {
797 return info_width_internal(words, TRUE);
798 }
799
800 static void info_heading(rdstringc *text, word *tprefix,
801 word *words, int width) {
802 rdstringc t = { 0, 0, NULL };
803 int margin, length;
804 int firstlinewidth, wrapwidth;
805 int i;
806 wrappedline *wrapping, *p;
807
808 if (tprefix) {
809 info_rdaddwc(&t, tprefix, NULL, FALSE);
810 rdaddsc(&t, ": "); /* FIXME: configurability */
811 }
812 margin = length = (t.text ? strlen(t.text) : 0);
813
814 margin = 0;
815 firstlinewidth = width - length;
816 wrapwidth = width;
817
818 wrapping = wrap_para(words, firstlinewidth, wrapwidth, info_width_noxrefs);
819 for (p = wrapping; p; p = p->next) {
820 info_rdaddwc(&t, p->begin, p->end, FALSE);
821 length = (t.text ? strlen(t.text) : 0);
822 for (i = 0; i < margin; i++)
823 rdaddc(text, ' ');
824 rdaddsc(text, t.text);
825 rdaddc(text, '\n');
826 for (i = 0; i < margin; i++)
827 rdaddc(text, ' ');
828 while (length--)
829 rdaddc(text, '-');
830 rdaddc(text, '\n');
831 margin = 0;
832 sfree(t.text);
833 t = empty_rdstringc;
834 }
835 wrap_free(wrapping);
836 rdaddc(text, '\n');
837
838 sfree(t.text);
839 }
840
841 static void info_rule(rdstringc *text, int indent, int width) {
842 while (indent--) rdaddc(text, ' ');
843 while (width--) rdaddc(text, '-');
844 rdaddc(text, '\n');
845 rdaddc(text, '\n');
846 }
847
848 static void info_para(rdstringc *text, word *prefix, char *prefixextra,
849 word *input, keywordlist *keywords,
850 int indent, int extraindent, int width) {
851 wrappedline *wrapping, *p;
852 word *words;
853 rdstringc pfx = { 0, 0, NULL };
854 int e;
855 int i;
856 int firstlinewidth = width;
857
858 words = info_transform_wordlist(input, keywords);
859
860 if (prefix) {
861 info_rdaddwc(&pfx, prefix, NULL, FALSE);
862 if (prefixextra)
863 rdaddsc(&pfx, prefixextra);
864 for (i = 0; i < indent; i++)
865 rdaddc(text, ' ');
866 rdaddsc(text, pfx.text);
867 /* If the prefix is too long, shorten the first line to fit. */
868 e = extraindent - strlen(pfx.text);
869 if (e < 0) {
870 firstlinewidth += e; /* this decreases it, since e < 0 */
871 if (firstlinewidth < 0) {
872 e = indent + extraindent;
873 firstlinewidth = width;
874 rdaddc(text, '\n');
875 } else
876 e = 0;
877 }
878 sfree(pfx.text);
879 } else
880 e = indent + extraindent;
881
882 wrapping = wrap_para(words, firstlinewidth, width, info_width_xrefs);
883 for (p = wrapping; p; p = p->next) {
884 for (i = 0; i < e; i++)
885 rdaddc(text, ' ');
886 info_rdaddwc(text, p->begin, p->end, TRUE);
887 rdaddc(text, '\n');
888 e = indent + extraindent;
889 }
890 wrap_free(wrapping);
891 rdaddc(text, '\n');
892
893 free_word_list(words);
894 }
895
896 static void info_codepara(rdstringc *text, word *words,
897 int indent, int width) {
898 int i;
899
900 for (; words; words = words->next) if (words->type == word_WeakCode) {
901 char *c;
902 info_convert(words->text, &c);
903 if (strlen(c) > (size_t)width) {
904 /* FIXME: warn */
905 }
906 for (i = 0; i < indent; i++)
907 rdaddc(text, ' ');
908 rdaddsc(text, c);
909 rdaddc(text, '\n');
910 sfree(c);
911 }
912
913 rdaddc(text, '\n');
914 }
915
916 static void info_versionid(rdstringc *text, word *words) {
917 rdaddc(text, '['); /* FIXME: configurability */
918 info_rdaddwc(text, words, NULL, FALSE);
919 rdaddsc(text, "]\n");
920 }
921
922 static node *info_node_new(char *name)
923 {
924 node *n;
925
926 n = mknew(node);
927 n->text.text = NULL;
928 n->text.pos = n->text.size = 0;
929 n->up = n->next = n->prev = n->lastchild = n->listnext = NULL;
930 n->name = dupstr(name);
931 n->started_menu = FALSE;
932
933 return n;
934 }
935
936 static char *info_node_name(paragraph *p)
937 {
938 rdstringc rsc = { 0, 0, NULL };
939 info_rdaddwc(&rsc, p->kwtext ? p->kwtext : p->words, NULL, FALSE);
940 return rsc.text;
941 }
942
943 static void info_menu_item(rdstringc *text, node *n, paragraph *p)
944 {
945 /*
946 * FIXME: Depending on how we're doing node names in this info
947 * file, we might want to do
948 *
949 * * Node name:: Chapter title
950 *
951 * _or_
952 *
953 * * Chapter number: Node name.
954 *
955 *
956 */
957 rdaddsc(text, "* ");
958 rdaddsc(text, n->name);
959 rdaddsc(text, "::");
960 if (p) {
961 rdaddc(text, ' ');
962 info_rdaddwc(text, p->words, NULL, FALSE);
963 }
964 rdaddc(text, '\n');
965 }