2 * info backend for Halibut
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
14 * - configurable indentation, bullets, emphasis, quotes etc?
27 typedef struct node_tag node
;
30 node
*up
, *prev
, *next
, *lastchild
;
31 int pos
, started_menu
, filenum
;
42 static int info_convert(wchar_t *, char **);
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
*,
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
*);
54 static void info_rdaddwc(rdstringc
*, word
*, word
*, int);
56 static node
*info_node_new(char *name
);
57 static char *info_node_name(paragraph
*p
);
59 static infoconfig
info_configure(paragraph
*source
) {
65 ret
.filename
= dupstr("output.info");
66 ret
.maxfilesize
= 64 << 10;
68 for (; source
; source
= source
->next
) {
69 if (source
->type
== para_Config
) {
70 if (!ustricmp(source
->keyword
, L
"info-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
));
82 paragraph
*info_config_filename(char *filename
)
85 wchar_t *ufilename
, *up
;
89 memset(p
, 0, sizeof(*p
));
90 p
->type
= para_Config
;
92 p
->fpos
.filename
= "<command line>";
93 p
->fpos
.line
= p
->fpos
.col
= -1;
95 ufilename
= ufroma_dup(filename
);
96 len
= ustrlen(ufilename
) + 2 + lenof(L
"info-filename");
97 p
->keyword
= mknewa(wchar_t, len
);
99 ustrcpy(up
, L
"info-filename");
101 ustrcpy(up
, ufilename
);
104 assert(up
- p
->keyword
< len
);
110 void info_backend(paragraph
*sourceform
, keywordlist
*keywords
,
114 word
*prefix
, *body
, *wp
;
117 int nesting
, nestindent
;
118 int indentb
, indenta
;
121 rdstringc intro_text
= { 0, 0, NULL
};
122 node
*topnode
, *currnode
;
129 int width
= 70, listindentbefore
= 1, listindentafter
= 3;
130 int indent_code
= 2, index_width
= 40;
132 IGNORE(keywords
); /* we don't happen to need this */
133 IGNORE(idx
); /* or this */
135 conf
= info_configure(sourceform
);
138 * Go through and create a node for each section.
140 topnode
= info_node_new("Top");
142 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
148 case para_UnnumberedChapter
:
152 node
*newnode
, *upnode
;
155 nodename
= info_node_name(p
);
156 newnode
= info_node_new(nodename
);
159 p
->private_data
= newnode
;
162 upnode
= (node
*)p
->parent
->private_data
;
166 newnode
->up
= upnode
;
168 currnode
->next
= newnode
;
169 newnode
->prev
= currnode
;
171 currnode
->listnext
= newnode
;
178 * Set up the display form of each index entry.
184 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
185 info_idx
*ii
= mknew(info_idx
);
186 rdstringc rs
= { 0, 0, NULL
};
188 ii
->nnodes
= ii
->nodesize
= 0;
191 info_rdaddwc(&rs
, entry
->text
, NULL
, FALSE
);
193 * FIXME: splatter colons.
197 entry
->backend_data
= ii
;
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.
206 * FIXME: also Info directory entries are expected to go here.
207 * This will need to be a configurable thing of some sort.
211 "This Info file generated by Halibut, ");
212 rdaddsc(&intro_text
, version
);
213 rdaddsc(&intro_text
, "\n\n");
215 for (p
= sourceform
; p
; p
= p
->next
)
216 if (p
->type
== para_Copyright
)
217 info_para(&intro_text
, NULL
, NULL
, p
->words
, keywords
,
220 for (p
= sourceform
; p
; p
= p
->next
)
221 if (p
->type
== para_VersionID
)
222 info_versionid(&intro_text
, p
->words
);
224 if (intro_text
.text
[intro_text
.pos
-1] != '\n')
225 rdaddc(&intro_text
, '\n');
228 for (p
= sourceform
; p
; p
= p
->next
)
229 if (p
->type
== para_Title
)
230 info_heading(&topnode
->text
, NULL
, p
->words
, width
);
232 nestindent
= listindentbefore
+ listindentafter
;
237 /* Do the main document */
238 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
245 assert(nesting
>= 0);
249 nesting
+= nestindent
;
252 nesting
-= nestindent
;
253 assert(nesting
>= 0);
257 * Things we ignore because we've already processed them or
258 * aren't going to touch them in this pass.
262 case para_Biblio
: /* only touch BiblioCited */
273 case para_UnnumberedChapter
:
276 currnode
= p
->private_data
;
278 assert(currnode
->up
);
280 if (!currnode
->up
->started_menu
) {
281 rdaddsc(&currnode
->up
->text
, "* Menu:\n\n");
282 currnode
->up
->started_menu
= TRUE
;
284 info_menu_item(&currnode
->up
->text
, currnode
, p
);
286 has_index
|= info_check_index(p
->words
, currnode
, idx
);
287 info_heading(&currnode
->text
, p
->kwtext
, p
->words
, width
);
292 info_rule(&currnode
->text
, nesting
, width
- nesting
);
297 case para_DescribedThing
:
298 case para_Description
:
299 case para_BiblioCited
:
301 case para_NumberedList
:
302 has_index
|= info_check_index(p
->words
, currnode
, idx
);
303 if (p
->type
== para_Bullet
) {
306 bullet
.type
= word_Normal
;
307 bullet
.text
= L
"-"; /* FIXME: configurability */
310 indentb
= listindentbefore
;
311 indenta
= listindentafter
;
312 } else if (p
->type
== para_NumberedList
) {
314 prefixextra
= "."; /* FIXME: configurability */
315 indentb
= listindentbefore
;
316 indenta
= listindentafter
;
317 } else if (p
->type
== para_Description
) {
320 indentb
= listindentbefore
;
321 indenta
= listindentafter
;
325 indentb
= indenta
= 0;
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
;
339 info_para(&currnode
->text
, prefix
, prefixextra
, body
, keywords
,
340 nesting
+ indentb
, indenta
,
341 width
- nesting
- indentb
- indenta
);
344 free_word_list(body
);
349 info_codepara(&currnode
->text
, p
->words
,
350 nesting
+ indent_code
,
351 width
- nesting
- 2 * indent_code
);
356 * Create an index node if required.
363 newnode
= info_node_new("Index");
364 newnode
->up
= topnode
;
366 currnode
->next
= newnode
;
367 newnode
->prev
= currnode
;
368 currnode
->listnext
= newnode
;
370 rdaddsc(&newnode
->text
, "Index\n-----\n\n* Menu:\n\n");
372 info_menu_item(&topnode
->text
, newnode
, NULL
);
374 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
375 info_idx
*ii
= (info_idx
*)entry
->backend_data
;
377 for (j
= 0; j
< ii
->nnodes
; j
++) {
378 int pos0
= newnode
->text
.pos
;
379 rdaddsc(&newnode
->text
, "* ");
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.
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");
398 * Finalise the text of each node, by adding the ^_ delimiter
399 * and the node line at the top.
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
);
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
);
420 rdaddsc(&currnode
->text
, "\n\n");
421 rdaddsc(&currnode
->text
, origtext
);
423 * Just make _absolutely_ sure we end with a newline.
425 if (currnode
->text
.text
[currnode
->text
.pos
-1] != '\n')
426 rdaddc(&currnode
->text
, '\n');
432 * Compute the offsets for the tag table.
434 filepos
= intro_text
.pos
;
435 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
436 currnode
->pos
= filepos
;
437 filepos
+= currnode
->text
.pos
;
441 * Split into sub-files.
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
) {
449 currfilesize
= intro_text
.pos
;
451 currnode
->filenum
= currfilenum
;
452 currfilesize
+= currnode
->text
.pos
;
457 * Write the primary output file.
459 fp
= fopen(conf
.filename
, "w");
461 error(err_cantopenw
, conf
.filename
);
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
);
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
,
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");
487 * Write the subfiles.
489 if (conf
.maxfilesize
> 0) {
493 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
494 if (filenum
!= currnode
->filenum
) {
497 filenum
= currnode
->filenum
;
501 fname
= mknewa(char, strlen(conf
.filename
) + 40);
502 sprintf(fname
, "%s-%d", conf
.filename
, filenum
);
503 fp
= fopen(fname
, "w");
505 error(err_cantopenw
, fname
);
509 fputs(intro_text
.text
, fp
);
511 fputs(currnode
->text
.text
, fp
);
519 static int info_check_index(word
*w
, node
*n
, indexdata
*idx
)
523 for (; w
; w
= w
->next
) {
524 if (w
->type
== word_IndexRef
) {
528 tag
= index_findtag(idx
, w
->text
);
532 for (i
= 0; i
< tag
->nrefs
; i
++) {
533 indexentry
*entry
= tag
->refs
[i
];
534 info_idx
*ii
= (info_idx
*)entry
->backend_data
;
536 if (ii
->nnodes
> 0 && ii
->nodes
[ii
->nnodes
-1] == n
) {
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
546 if (ii
->nnodes
>= ii
->nodesize
) {
548 ii
->nodes
= resize(ii
->nodes
, ii
->nodesize
);
551 ii
->nodes
[ii
->nnodes
++] = n
;
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
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!
572 static int info_convert(wchar_t *s
, char **result
) {
574 * FIXME. Currently this is ISO8859-1 only.
576 int doing
= (result
!= 0);
579 int plen
= 0, psize
= 0;
585 if ((c
>= 32 && c
<= 126) ||
586 (c
>= 160 && c
<= 255)) {
590 /* Char is not OK. */
592 outc
= 0xBF; /* approximate the good old DEC `uh?' */
597 p
= resize(p
, psize
);
603 p
= resize(p
, plen
+1);
610 static word
*info_transform_wordlist(word
*words
, keywordlist
*keywords
)
612 word
*ret
= dup_word_list(words
);
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
);
621 if (kwl
->para
->type
== para_NumberedList
||
622 kwl
->para
->type
== para_BiblioCited
) {
624 * In Info, we do nothing special for xrefs to
625 * numbered list items or bibliography entries.
630 * An xref to a different section has its text
631 * completely replaced.
637 if (w2
->type
== word_XrefEnd
) {
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
657 w
->private_data
= kwl
->para
->private_data
;
658 assert(w
->private_data
);
667 static void info_rdaddwc(rdstringc
*rs
, word
*words
, word
*end
, int xrefs
) {
670 for (; words
&& words
!= end
; words
= words
->next
) switch (words
->type
) {
681 case word_WhiteSpace
:
684 case word_WkCodeSpace
:
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
))
703 info_rdaddwc(rs
, words
->alt
, NULL
, FALSE
);
705 } else if (removeattr(words
->type
) == word_WhiteSpace
) {
707 } else if (removeattr(words
->type
) == word_Quote
) {
708 rdaddc(rs
, quoteaux(words
->aux
) == quote_Open ?
'`' : '\'');
709 /* FIXME: configurability */
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 */
723 if (xrefs
&& words
->private_data
) {
724 rdaddsc(rs
, "*Note ");
725 rdaddsc(rs
, ((node
*)words
->private_data
)->name
);
732 static int info_width_internal(word
*words
, int xrefs
);
734 static int info_width_internal_list(word
*words
, int xrefs
) {
737 w
+= info_width_internal(words
, xrefs
);
743 static int info_width_internal(word
*words
, int xrefs
) {
744 switch (words
->type
) {
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)
760 (info_convert(words
->text
, NULL
) ?
761 ustrlen(words
->text
) :
762 info_width_internal_list(words
->alt
, xrefs
)));
764 case word_WhiteSpace
:
767 case word_WkCodeSpace
:
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)
782 if (xrefs
&& words
->private_data
) {
783 /* "*Note " plus "::" comes to 8 characters */
784 return 8 + strlen(((node
*)words
->private_data
)->name
);
788 return 0; /* should never happen */
791 static int info_width_noxrefs(word
*words
)
793 return info_width_internal(words
, FALSE
);
795 static int info_width_xrefs(word
*words
)
797 return info_width_internal(words
, TRUE
);
800 static void info_heading(rdstringc
*text
, word
*tprefix
,
801 word
*words
, int width
) {
802 rdstringc t
= { 0, 0, NULL
};
804 int firstlinewidth
, wrapwidth
;
806 wrappedline
*wrapping
, *p
;
809 info_rdaddwc(&t
, tprefix
, NULL
, FALSE
);
810 rdaddsc(&t
, ": "); /* FIXME: configurability */
812 margin
= length
= (t
.text ?
strlen(t
.text
) : 0);
815 firstlinewidth
= width
- length
;
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
++)
824 rdaddsc(text
, t
.text
);
826 for (i
= 0; i
< margin
; i
++)
841 static void info_rule(rdstringc
*text
, int indent
, int width
) {
842 while (indent
--) rdaddc(text
, ' ');
843 while (width
--) rdaddc(text
, '-');
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
;
853 rdstringc pfx
= { 0, 0, NULL
};
856 int firstlinewidth
= width
;
858 words
= info_transform_wordlist(input
, keywords
);
861 info_rdaddwc(&pfx
, prefix
, NULL
, FALSE
);
863 rdaddsc(&pfx
, prefixextra
);
864 for (i
= 0; i
< indent
; i
++)
866 rdaddsc(text
, pfx
.text
);
867 /* If the prefix is too long, shorten the first line to fit. */
868 e
= extraindent
- strlen(pfx
.text
);
870 firstlinewidth
+= e
; /* this decreases it, since e < 0 */
871 if (firstlinewidth
< 0) {
872 e
= indent
+ extraindent
;
873 firstlinewidth
= width
;
880 e
= indent
+ extraindent
;
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
++)
886 info_rdaddwc(text
, p
->begin
, p
->end
, TRUE
);
888 e
= indent
+ extraindent
;
893 free_word_list(words
);
896 static void info_codepara(rdstringc
*text
, word
*words
,
897 int indent
, int width
) {
900 for (; words
; words
= words
->next
) if (words
->type
== word_WeakCode
) {
902 info_convert(words
->text
, &c
);
903 if (strlen(c
) > (size_t)width
) {
906 for (i
= 0; i
< indent
; i
++)
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");
922 static node
*info_node_new(char *name
)
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
;
936 static char *info_node_name(paragraph
*p
)
938 rdstringc rsc
= { 0, 0, NULL
};
939 info_rdaddwc(&rsc
, p
->kwtext ? p
->kwtext
: p
->words
, NULL
, FALSE
);
943 static void info_menu_item(rdstringc
*text
, node
*n
, paragraph
*p
)
946 * FIXME: Depending on how we're doing node names in this info
947 * file, we might want to do
949 * * Node name:: Chapter title
953 * * Chapter number: Node name.
958 rdaddsc(text
, n
->name
);
962 info_rdaddwc(text
, p
->words
, NULL
, FALSE
);