Taint: reject or log more tainted list metadata elements
[exim.git] / doc / doc-scripts / g2t
1 #! /usr/bin/perl -w
2
3 # A Perl script to turn the SGCAL source of the Exim documentation into
4 # Texinfo input, more or less...
5
6 # Supply the source file names as arguments.
7 # The output goes to the standard output.
8
9
10 ##################################################
11 #         Ensure unique node name                #
12 ##################################################
13
14 # Node names must be unique. Occasionally in the Exim spec there are duplicate
15 # section names, and it's become too much of a hassle to keep them distinct
16 # manually. So it is now automated.
17
18 ########### Never really got this working. Abandoned ###############
19
20 sub unique {
21 my($node) = $_[0];
22 if (defined $node_names{$node})
23   {
24   $node_names{$node} += 1; 
25   $node = "$node ($node_names{$node})"; 
26   
27 print STDERR "+++ $node\n";
28  
29   }
30 else
31   {
32   $node_names{$node} = 0;
33   }
34 $node;
35 }  
36
37
38
39 ##################################################
40 #         De-comma a node name                   #
41 ##################################################
42
43 # Commas, colons, and apostrophes are not permitted in Texinfo
44 # node names. I find this incredible, but it is clearly documented.
45 # The Exim manual has been re-organized not to have colons or
46 # apostrophes in any chapter or section titles, but I can't manage
47 # without commas. This function turns "," into " and", which is
48 # the best that can be done; we can use some clever Perlery to
49 # just take out commas before "and".
50
51 # Sigh. The Sendmail option -p<rval>:<sval> now means that there's a colon
52 # in the node name for that option. Turn the colon into <colon>. This is also
53 # done for menus.
54
55 # Another thing that causes problems in node names in some versions of
56 # Texinfo is the use of @sc{xxx} for small caps. Just turn these all into
57 # real caps. This is also done for menus.
58
59 sub decomma {
60 $_[0] =~ s/,(?!\sand)/ and/g;
61 $_[0] =~ s/,//g;
62 $_[0] =~ s/\@sc\{([^}]*)\}/\U$1/g;
63 $_[0] =~ s/:/<colon>/g;
64 $_[0];
65 }
66
67
68
69 ##################################################
70 #           De-quote a string                    #
71 ##################################################
72
73 # @x is turned into x, except when x=@, or when asis is set,
74 # in which case single @ must turn into @@. A single substitute
75 # doesn't work in the non-asis case, because of the problems of
76 # handling things like @@@$, so we do it the hard way.
77
78 sub dequote {
79 if ($asis) { $_[0] =~ s/@/@@/g; } else
80   {
81   $_[0] =~ s/@@/&at&/g;
82   $_[0] =~ s/@([^@])/$1/g;
83   $_[0] =~ s/&at&/@@/g;
84   }
85 $_[0];
86 }
87
88
89 ##################################################
90 #           Get next line                        #
91 ##################################################
92
93 # Called from handle_directive, to get the next source line
94 # into $_.
95
96 sub get_next_line {
97 if ($processing_subsection)
98   { return $_ = shift @SUBBUFFER; }
99 else
100   { return $_ = <>; }
101 }
102
103
104
105 ##################################################
106 #           Handle text lines                    #
107 ##################################################
108
109 # This function is handed whole paragraphs, and we assume that
110 # SGCAL font changing markup is always complete within a paragraph.
111 # We have to replace escaped versions of significant characters with
112 # some magic before performing general transformations, and then
113 # put them back afterwards. The character & is not common in the text,
114 # and && is unknown, so we use that.
115
116 sub handle_text {
117 $_ = $_[0];
118
119 if ($asis)
120   {
121   $_ = dequote($_);
122   s/(\{|\})/\@$1/g;
123   return $_;
124   }
125
126 while (/~~/)
127   {
128   $left = $`;
129   ($name) = $' =~ /^(\w+)/;
130   $right = $';
131
132   $value = $references{$name};
133   $value = "" if !defined($value);
134
135   if ($value =~ /\*\*\*\*/)
136     {
137     $value = ($` eq $current_chapter)? "\"$'\"" :
138       "\"$'\" in chapter \"$`\"";
139     $value = "" if $value eq "\"\"";
140     }
141   elsif ($value !~ /^[0-9]+\.[0-9]+$/)   # quote unless version number
142     {                                                
143     $value = "\"$value\"";                          
144     }
145
146   $_ = "${left}${value}${right}";
147   }
148
149 s/\@\@/&&a/g;         # @@
150 s/\@\\/&&b/g;         # @\
151 s/\@</&&l/g;          # @<
152 s/\@>/&&g/g;          # @>
153 s/\@\{/&&c/g;         # @{
154 s/\@\}/&&d/g;         # @}
155 s/\@#/&&s/g;          # @#
156
157 # Now remove all other @'s
158
159 $_ = dequote($_);
160
161 # Convert SGCAL markup
162
163 s/#/ /g;                            # turn #   into a space
164 s/\$~//g;                           # turn $~  into nothing
165 s/__/_/g;                           # turn __  into _
166 s/\$sm\{//g;                        # turn $sm{     into nothing
167 s/\$sc\{([^\}]*?)\}/$1/g;           # turn $sc{xxx} into xxx
168 s/\$st\{([^\}]*?)\}/$1/g;           # turn $st{xxx} into xxx
169 s/\$si\{([^\}]*?)\}/$1/g;           # turn $si{xxx} into xxx
170 s/\$tt\{([^\}]*?)\}/$1/g;           # turn $tt{xxx} into xxx
171
172 s/\$it\{([^\}]*?)\}/$1/g;           # turn $it{xxx} into xxx
173
174 s/\$bf\{([^\}]*?)\}/$1/g;           # turn $bf{xxx} into xxx
175 s/\$rm\{([^\}]*?)\}/$1/g;           # turn $rm{xxx} into xxx
176 s/\$cb\{([^\}]*?)\}/$1/g;           # turn $cb{xxx} into xxx
177
178 # This is a fudge for some specific usages of $<; can't just do a global
179 # is it occurs in things like $<variable name> as well.
180
181 s/\[\$<\]/[]/g;                     # turn [$<]     into []
182 s/&&b\$<\./&&b./g;                  # turn \$<.     into \.  (\ == &&b by now)
183 s/(\d)\$<-/$1-/g;                   # turn 0$<-     into 0-
184
185 # There is one case where the terminating } of an escape sequence is
186 # in another paragraph - this follows $sm{ - it can be fixed by
187 # removing any stray } in a paragraph that contains no { chars.
188
189 s/\}//g if !/\{/;
190
191 # Any remaining {} must be escaped to prevent Texinfo from complaining
192
193 s/(\{|\})/\@$1/g;
194
195 # Now to conversions that put {} into the file.
196 # Change <<..>> from @var to just <...> as the caps that Texinfo
197 # uses look far too shouty.
198
199 s/\\\\([^\\]*?)\\\\/\@sc\{\L$1\}/g; # turn \\xxx\\  into @sc{xxx}
200 s/\\\(([^)]*?)\)\\/\@file\{$1\}/g;  # turn \(xxx)\  into @file{xxx}
201 s/\\\"([^\"]*?)\"\\/\@file\{$1\}/g; # turn \"xxx"\  into @file{xxx}
202
203 s/\\\?([^?]*?)\?\\/$1/g;            # turn \?URL?\    into URL
204 s/<<([^>]*?)>>/<$1>/g;              # turn <<xxx>>    into <xxx>
205 s/\\\$([^\$]*?)\$\\/\$$1/g;         # turn \$xxx$\    into $xxx
206 s/\\\-([^-]*?)\-\\/\-$1/g;          # turn \-xxx-\    into -xxx
207 s/\\\*\*([^*]*?)\*\*\\/$1/g;        # turn \**xxx**\  into xxx
208 s/\[\(([\w\/]*)\)\]//g;             # remove inline HTML
209
210 s/\\\*([^*]*?)\*\\/\@dfn\{$1\}/g;     # turn \*xxx*\    into @dfn{xxx}
211 s/\\%([^*]*?)%\\/\@dfn\{$1\}/g;       # turn \%xxx%\    into @dfn{xxx}
212 s/:::([^:]*?)::/\@dfn\{:$1:\}/g;      # turn :::xxx::   into @dfn{:xxx:}
213 s/::([^:]*?)::/\@dfn\{$1:\}/g;        # turn ::xxx::    into @dfn{xxx:}
214 s/\\([^\\]*?)\\/\@dfn\{$1\}/g;        # turn \xxx\      into @dfn{xxx}
215 s/\$\*\$/\*/g;                        # turn $*$        into *
216
217 # Put back escaped SGCAL specials
218
219 s/&&a/\@\@/g;
220 s/&&b/\\/g;
221 s/&&l/</g;
222 s/&&g/>/g;
223 s/&&c/\@{/g;
224 s/&&rc/{/g;
225 s/&&rd/}/g;
226 s/&&d/\@}/g;
227 s/&&s/#/g;
228
229 # Remove any null flags ($$)
230
231 s/\$\$//g;
232
233 # If the paragraph starts with $c\b, change this into @center. Assume
234 # we don't ever get two of these in a row.
235
236 s/^\$c\b/\@center /;
237
238 # If the paragraph starts with $e\b, stuff some tabs in there, as
239 # Texinfo can't do this on its own (as far as I can see). They must
240 # tabs; Texinfo treats them as different to spaces. Sigh.
241
242 s/^\$e\b/\t\t\t\t\t\t\t/;
243
244 # Handle $t. The Exim spec only ever has one tab per line. Er, not
245 # quite true, but a good enough assumption. $t is always followed
246 # by a non-word character.
247
248 # The .tabs directive has stashed the value in the $tab variable.
249 # Don't count Texinfo font chars.
250
251 while (/(^|.+?\n)(.+?)\$t(\W.*\n)/)
252   {
253   $before = $` . $1;
254   $after = $';
255   $left = $2;
256   $right = $3;
257
258   $left =~ s/\s$//;
259   $right =~ s/^\s+//;
260
261   $plainleft = $left;
262   $plainleft =~ s/\@[a-z]+\{([^}]+?)\}/$1/g;
263   $plainleft =~ s/\@//g;
264
265   $_ = $before . $left . (" " x ($tab - length($plainleft))) . $right . $after;
266
267   # Fudge for the one case where there are two tabs
268
269   if ($tab2 != 0)
270     {
271     $temp = $tab;
272     $tab = $tab2;
273     $tab2 = $temp;
274     }
275   }
276
277 # Return the new line (paragraph)
278
279 $_;
280 }
281
282
283
284 ##################################################
285 #           Handle directive lines               #
286 ##################################################
287
288 # Use get_next_line() instead of <> because this is called to process
289 # stacked up subsection lines
290
291 sub handle_directive {
292
293 my($new_lastwasitem) = 0;
294
295 # Chapter directives just require . => @; however, dequoting the
296 # line thereafter will remove the first @, so just force it back
297 # afterwards. If the chapter is one describing a driver, set
298 # the driver name.
299
300 if (/\.chapter/)
301   {
302   tr/./@/;
303   push(@ONESECTION, "@" . &dequote("$_\n"));
304   $driver_name = (/The\s+(\S+)\s+(director|router|transport|authenticator)/)? $1 :
305     (/Generic options common to both directors and routers/)?
306       "director or router" :
307     (/[Gg]eneric\s+options for (\S+)s/)? $1 : "";
308   $driver_name = &dequote($driver_name);
309   }
310
311 # Section directives just require . => @; however, dequoting the
312 # line thereafter will remove the first @, so just force it back
313 # afterwards. Remove any colons in section titles as they cause
314 # Texinfo trouble. Also remove any \\ (small caps) markup, which
315 # appears in a couple of cases.
316
317 elsif (/\.section/)
318   {
319   tr/./@/;
320   s/://;
321   s"\\\\""g;
322   push(@ONESECTION, "@" . &dequote("$_\n"));
323
324   # Horrible magic fudge to cope with the fact that exim_lock has
325   # -v and -q options, just like the main program.
326
327   $driver_name = "exim_lock" if /Mailbox maintenance/;
328   
329   # Similar magic for exiqgrep, which also duplicates options
330   
331   $driver_name = "exiqgrep" if /Selective queue listing/;  
332   }
333
334 # .newline must put @* on the end of the previous line, if any, except
335 # inside a display, where it causes trouble.
336
337 elsif (/\.newline/)
338   {
339   if (@ONESECTION > 0 && ! $indisplay)
340     {
341     $_ = pop(@ONESECTION);
342     s/(\n*)$/\@*$1/;
343     push(@ONESECTION, $_);
344     }
345   }
346
347 # .blank turns into @sp, adding 1 if no argument
348
349 elsif (/\.blank/)
350   {
351   s/\.blank\s+(\d+)/\@sp $1/;
352   s/\.blank/\@sp 1/;
353   push(@ONESECTION, $_);
354   }
355
356 # .rule turns into a line of hyphens
357
358 elsif (/\.rule/)
359   {
360   push(@ONESECTION, ("-" x ($in_itemize? 68 : 73)) . "\@*\n");
361   }
362
363 # There's one explicit .tabset setting for two tab stops
364
365 elsif (/\.tabset\s*/)
366   {
367   $rest = $';
368   ($first,$second) = $rest =~ /(\d+)em\s+(\d+)em/;
369   $tab = ($first * 7)/6;
370   $tab2 = $tab + ($second * 7)/6;
371   }
372
373 # .tabs remembers the first (and only) tab setting
374
375 elsif (/\.tabs\s*/)
376   {
377   $tab = ($' * 7)/6;
378   $tab2 = 0;
379   }
380
381 # .tempindent is used only to align some of the expansion stuff nicely;
382 # just ignore it. It is used in conjunction with .push/.pop.
383
384 elsif (/\.(tempindent|push|pop)\s*/)
385   {
386   }
387
388 # There are some instances of .if ~~sys.fancy in the source. Some of these
389 # are two-part things, in which case we just keep the non-fancy. For diagrams,
390 # however, they are in three parts:
391 #
392 # .if ~~sys.fancy
393 # <aspic drawing stuff>
394 # .elif ~~nothtml
395 # <ascii art for txt and Texinfo>
396 # .else
397 # <HTML instructions for including a gif>
398 # .fi
399
400 elsif (/\.if \~\~sys\.fancy/)
401   {
402   while (&get_next_line())
403     { last if /\.else\b/ || /\.elif\s+\~\~nothtml/ || /\.fi\b/; }
404
405   if (/\.elif/)
406     {
407     $skip_else = 1;
408     }
409   }
410
411 # There are occasional requirements to do things differently for
412 # Texinfo/HTML and the PS/txt versions, and there are also some
413 # HTML-specific things.
414
415 elsif (/\.if\s+~~sgcal/ || /\.if\s+~~html/)
416   {
417   while (&get_next_line()) { last if /\.else\b/ || /\.fi\b/; }
418   }
419
420 # We may also have Texinfo-specific bits
421
422 elsif (/^\.if\s+~~texinfo/)
423   {
424   $skip_else = 1;
425   }
426
427 # Ignore any other .if directives
428
429 elsif (/\.if/) {}
430
431 # Skip else part if flag set
432
433 elsif (/\.else/ && $skip_else)
434   {
435   while (&get_next_line()) { last if /\.fi\b/; }
436   $skip_else = 0;
437   }
438
439 # Ignore other .fi and .else as any .if directives are handled specially
440
441 elsif (/\.fi/ || /\.else/) {}
442
443 # Ignore .indent
444
445 elsif (/\.indent/) {}
446
447 # Plain .index goes to @cindex - the "concept" index. Also, there
448 # are some calls to vindex and findex in the SGCAL source - treated
449 # as synonymous with .index - which are split into the equivalent
450 # indexes here.
451
452 elsif (/\.(.?)index/)
453   {
454   $rest = $';
455   $letter = ($1 eq "")? "c" : $1;
456   tr/./@/;                           # .index -> @index
457   
458   $rest =~ s/\\\(//g;                # Remove markup
459   $rest =~ s/\)\\//g; 
460   $rest =~ s/\\%//g;
461   $rest =~ s/%\\//g;
462   $rest =~ s/\\\*//g;
463   $rest =~ s/\*\\//g;    
464   $rest =~ s/\\"//g;
465   $rest =~ s/"\\//g;
466   $rest =~ s/:://g;
467   $rest =~ s/\\-/-/g;
468   $rest =~ s/-\\//g;
469   $rest =~ s/~~//g;     
470  
471   $rest =~ tr/\\//d;                 # Remove \
472    
473   $rest =~ s/\@\$/\$/g;              # @$  -> $
474   $rest =~ s/\@_/_/g;                # @_  -> _
475   $rest =~ s/\@\+/+/g;               # @+  -> +
476   $rest =~ s/\$\*\$/\*/g;            # $*$ -> *
477   $rest =~ s/\$([^\$]+)\$/\$$1/g;    # $x$ -> $x
478    
479   $rest =~ s/^\s+//;                 # Remove leading spaces
480   $rest =~ s/\s+$//;                 # Remove trailing spaces
481   $rest =~ s/\|\|/:/;                # || -> : 
482   push(@ONESECTION, "\@${letter}index $rest\n");
483
484   # Duplicate entries for things that were listed as "x see y"
485
486   if (defined $indirections{$rest})
487     {
488     push(@ONESECTION, "\@${letter}index $indirections{$rest}\n");
489     }
490   }
491
492 # Various flavours of numberpars map to itemize and enumerate.
493 # Haven't found a way of having a blank space 'bullet' yet, so
494 # currently using minus.
495
496 elsif (/\.numberpars/)
497   {
498   $rest = $';
499   $type = "enumerate";
500   $flag = "";
501
502   if    ($rest =~ /\$\./)  { $flag = " \@bullet"; $type = "itemize" }
503   elsif ($rest =~ /\" \"/) { $flag = " \@minus";  $type = "itemize"; }
504   elsif ($rest =~ /roman/) { $flag = " a"; $type = "enumerate"; }
505
506   push(@ONESECTION, "\n\@$type$flag\n\n\@item\n");
507   push(@ENDLIST, $type);
508   $in_itemize++;
509   }
510
511 elsif (/\.nextp/)
512   {
513   push(@ONESECTION, "\n\@item\n");
514   }
515
516 elsif (/\.endp/)
517   {
518   $endname = pop(@ENDLIST);
519   push(@ONESECTION, "\@end $endname\n\n");
520   $in_itemize--;
521   }
522
523 # The normal .display (typewriter font) => @example, while the rm
524 # form goes to @display (no change of font). For Texinfo we need a
525 # blank line after @display.
526
527 elsif (/\.display/)
528   {
529   $type = /rm/? "display" : "example";
530   $asis = 1 if /asis/;
531   $indisplay = 1;
532   push(@ONESECTION, "\@$type\n\n");
533   push(@ENDLIST, $type);
534   }
535
536 elsif (/\.endd/)
537   {
538   $asis = 0;
539   $indisplay = 0;
540   $endname = pop(@ENDLIST);
541   push(@ONESECTION, "\@end $endname\n\n");
542   }
543
544 elsif (/\.conf/)
545   {
546   ($option, $type, $default) =
547     /\.conf\s+(\S+)\s+("(?:[^"]|"")+"|\S+)\s+("(?:[^"]|"")+"|.*)/;
548
549   $option = &dequote($option);
550
551   # If $type ends with $**$ (turned into a dagger for PS version),
552   # replace with ", expanded". Remove any surrounding quotes.
553
554   $type =~ s/^"([^"]+)"/$1/;
555   $type =~ s/\$\*\*\$/, expanded/;
556
557   # Default may be quoted, and it may also have quotes that are required,
558   # if it is a string.
559
560   $default =~ s/^"(.*)"$/$1/;
561   $default =~ s/""/"/g;
562   $default = &handle_text($default);
563
564   push(@ONESECTION, "\nType: $type\@*\nDefault: $default\n\n");
565   }
566
567 # Handle .startitems, .enditems, and .item
568
569 elsif (/\.startitem/ || /\.enditem/) {}
570
571 elsif (/\.item/)
572   {
573   $arg = $';
574   $arg =~ s/^\s*"//;
575   $arg =~ s/"\s*$//;
576   $arg = &dequote($arg);
577   $arg = &handle_text("\\**$arg**\\");
578
579   # If there are two .items in a row, we don't want to put in the
580   # separator line.
581
582 #  push(@ONESECTION, "\n\@example\n");
583   push(@ONESECTION, "\n");
584   if (! $lastwasitem)
585     {
586     push(@ONESECTION, "_" x 75, "\n\n");
587     }
588 #  push(@ONESECTION, "$arg\n\@end example\n\n");
589   push(@ONESECTION, "$arg\n\n");
590   $new_lastwasitem = 1;
591   }
592
593 elsif (/\.option/)
594   {
595   chomp($arg = $');
596   $arg =~ s/^\s*//;
597   $arg = &dequote("-$arg");
598   $arg = &handle_text($arg);
599   }
600
601 # Texinfo has no facility for emphasis bars.
602
603 elsif (/\.em/) {}
604 elsif (/\.nem/) {}
605
606 # Just ignore any .(r)set directives pro tem (or maybe always!)
607
608 elsif (/\.r?set/) {}
609
610 # Ignore .space, .linelength, and .justify
611
612 elsif (/\.space/ || /\.justify/ || /\.linelength/) {}
613
614 # Found an SGCAL directive that isn't dealt with. Oh dear.
615 # Turn the embarrassing characters into question marks and
616 # output it in an emphasized way.
617
618 else
619   {
620   tr/@{}/???/;
621   push(@ONESECTION, "\n\>>>>>>> $_\n") if ! /^\.( |$)/;
622   }
623
624 $lastwasitem = $new_lastwasitem;
625 }
626
627
628
629 ##################################################
630 #             Flush a section                    #
631 ##################################################
632
633 # $section_name is the name of the next section.
634 # $current_section is the name of the one we have buffered up.
635 # If it is unset, we are at the first section of a chapter.
636 # $previous_node is the section we last flushed if it was a node.
637
638 sub flush_section {
639
640 # If there is no text in the section, omit it entirely. However, it
641 # will have had a pointer set up at the start of the previous section.
642 # Remember what to replace this with when the chapter gets flushed.
643
644 my($skip) = 1;
645 foreach $s (@ONESECTION)
646   {
647   if ($s !~ /^(\@cindex|\@section|\s*$)/) { $skip = 0; last }
648   }
649
650 if ($skip)
651   {
652   pop @section_list;
653   $rewrite{$current_section} = $section_name;
654   @ONESECTION = ();
655   return;
656   }
657
658 # There is data in the section: write it out to the chapter file
659
660 if ($current_section)
661   {
662   printf ONECHAPTER ("\@node %s, %s, %s, %s\n",
663     &decomma($current_section), &decomma($section_name),
664     &decomma($previous_node), &decomma($current_up));
665   $previous_node = $current_section;
666   while(scalar(@ONESECTION))
667     { print ONECHAPTER shift(@ONESECTION); }
668   }
669 else
670   {
671   while(scalar(@ONESECTION))
672     { push(@TOPSECTION, shift(@ONESECTION)); }
673   }
674 }
675
676
677
678 ##################################################
679 #          Handle a "subsection"                 #
680 ##################################################
681
682 # A "subsection" is a set of options that must have their own
683 # local menu. Do two passes; the first just collects the names
684 # for the menu. This is called for .conf and .option items.
685
686 sub handle_subsection{
687 my($type) = $_[0];
688 my($save_up) = $current_up;
689
690 $current_up = $current_section? $current_section : $current_chapter;
691
692 @sublist = ();
693 @SUBBUFFER = ();
694
695 while (<>)
696   {
697   last if /^\.end$type/;
698   push(@SUBBUFFER, $_);
699
700   # .conf takes the first non-space string as the name, but as there are
701   # duplicate confs in various parts of the spec, use the driver name to
702   # de-duplicate; .option takes the entire rest of arg as the name, but
703   # removes any sequence of ... because this disturbs TexInfo. Also, it
704   # turns @- into -.
705
706   if (/^\.$type\s+(\S+)(.*)/)
707     {
708     if ($type eq "conf")
709       {
710       $name = &handle_text($1);
711       $name .= " ($driver_name)" if ($driver_name ne "");
712       }
713     else
714       {
715       chomp($name = &handle_text("-$1$2"));
716       $name =~ s/\s*\.\.\.//g;
717
718       $name .= " ($driver_name)" if ($driver_name ne "");
719
720       # There seems to be a major problem in texinfo with the string "--".
721       # In the text it gets turned into a single hyphen. This happens if it
722       # is used as a menu item, but *not* as a node name. Exim has a command
723       # line option "--". With no special action, this appears in the menu
724       # as "-", but then the info software complains there is no node called
725       # "-". If we triple it in the menu it gets displayed OK, but building
726       # software complains about non-existent cross references etc.
727
728       # I have gone for the horrid kludge of turning it into "-<hyphen>"
729       # in the menus and nodes.
730
731       # Exim 4 has added --help, which has the same problem.
732
733       $name = "-<hyphen>" if ($name eq "--");
734       $name = "-<hyphen>help" if ($name eq "--help");
735       }
736     push(@sublist, $name);
737     }
738   }
739
740 push (@ONESECTION, "\n\@sp 2\n\@menu\n");
741 for ($i = 0; $i < scalar(@sublist); $i++)
742   {
743   $mitem = $sublist[$i];
744   $mitem =~ s/\@sc\{([^}]*)\}/\U$1/g;       # Get rid of small caps
745   $mitem =~ s/:/<colon>/g;                  # Get rid of colons
746   push (@ONESECTION, "* ${mitem}::\n");
747   }
748 push (@ONESECTION, "\@end menu\n\n");
749
750 $prevsub = $current_up;
751 $processing_subsection = 1;
752 while ($_ = shift(@SUBBUFFER))
753   {
754   if (/^\.$type\s+(\S+)/)
755     {
756     $name = shift @sublist;
757     $next = (scalar(@sublist))? $sublist[0] : "";
758     push @ONESECTION, sprintf("\@node %s, %s, %s, %s\n",
759       &decomma($name), &decomma($next), &decomma($prevsub),
760       &decomma($current_up));
761
762     if ($name eq "-<hyphen>")    # Fudge for Texinfo
763       {
764       push(@ONESECTION,
765            "\@findex $name\n",
766            "\@unnumberedsubsec --- option\n");
767       push(@ONESECTION,
768            "This option consists of two consecutive hyphens. It appears in\n",
769            "the menu as \"-<hyphen>\" because otherwise Texinfo gets\n",
770            "confused with its cross-referencing.\n");
771       }
772     elsif ($name eq "-<hyphen>help")    # Fudge for Texinfo
773       {
774       push(@ONESECTION,
775            "\@findex $name\n",
776            "\@unnumberedsubsec ---help option\n");
777       push(@ONESECTION,
778            "This option consists of \"help\" preceded by two consecutive\n" .
779            "hyphens. It appears in the menu as \"-<hyphen>help\" because\n" .
780            "otherwise Texinfo gets confused with its cross-referencing.\n");
781       }
782     else
783       {
784       push(@ONESECTION,
785            "\@findex $name\n",
786            "\@unnumberedsubsec $name option\n");
787       }
788
789     $prevsub = $name;
790     }
791
792   # Then handle as text or directive
793
794   if (substr($_, 0, 1) eq ".")
795     { handle_directive(); }
796   else
797     {
798     while($nextline = shift(@SUBBUFFER))
799       {
800       last if $nextline =~ /^(\.|\s*$)/;
801       $_ .= $nextline;
802       }
803     push(@ONESECTION, handle_text($_));
804     $_ = $nextline;
805     last if !defined($_);
806     redo;
807     }
808   }
809
810 $processing_subsection = 0;
811 $section_pending = 1;
812 $current_up = $save_up;
813 }
814
815
816
817
818 ##################################################
819 #            Handle a single chapter             #
820 ##################################################
821
822 sub handle_chapter{
823 chop;
824 ($current_chapter) = /^\.chapter (.*)/;
825 $current_chapter = &dequote($current_chapter);
826
827 $current_chapter = $current_chapter;
828
829 my($tmp) = $current_chapter;
830 $tmp =~ s/\[\[\[\]\]\]/./;
831 print STDERR "processing chapter: $tmp\n";
832
833 # Remember the chapter name for the top-level menu
834
835 push(@chapter_list, $current_chapter);
836
837 # Open a temporary file to hold the chapter's text while collecting
838 # all its sections for a chapter-level menu.
839
840 $ONECHAPTER = "/tmp/ONECHAPTER.$$";
841 open(ONECHAPTER, ">$ONECHAPTER") || die "Can't open $ONECHAPTER for writing";
842
843 # Initialize for handling sections
844
845 @section_list = ();
846 %rewrite = ();
847 @ONESECTION = ();
848 @TOPSECTION = ();
849 undef $current_section;
850 undef $next_node;
851
852 $processing_subsection = 0;
853
854 $previous_node = $current_up = $current_chapter;
855 $section_pending = 0;
856
857 # Handle the .chapter directive as the first text of a section without
858 # a section title.
859
860 handle_directive();
861
862 # Loop, handling each section. Assume they are sufficiently short that
863 # we can buffer the text in store, in an array called ONESECTION, instead
864 # of thrashing yet another file.
865
866 while (<>)
867   {
868   last if /^\.chapter /;
869
870   # Handle a new section, preserving $_ (handle_text flattens it).
871   # It seems we cannot get a fullstop into a Texinfo node name; use a magic
872   # character string that gets turned back into a dot by the post-processing.
873
874   if (/^\.section\s+/)
875     {
876     $save = $_;
877     $section_name = $';
878     $section_name =~ s/(\s|\n)+$//;
879     $section_name =~ s/://;
880     $section_name = &handle_text($section_name);
881     flush_section();
882     push(@section_list, $section_name);
883     $current_section = $section_name;
884     $next_node = $section_name if !$next_node;
885     $section_pending = 0;
886     $_ = $save;
887     }
888
889   # The .startconf macro introduces a set of .conf's which must have
890   # their own local set of menus. Suspend processing the section while
891   # we sort out the menu and copy their data. This is all done in a
892   # subroutine that is shared with options.
893
894   elsif (/^\.startconf\s+(.*)/)
895     {
896     $confuse = $1; 
897     $confuse = &dequote($confuse); 
898     handle_subsection("conf");
899     next;
900     }
901
902   elsif (/^\.startoption/)
903     {
904     handle_subsection("option");
905     next;
906     }
907
908   # Deal with the actual data lines; if there's a section pending
909   # start a new section on hitting some text. We hope this happens
910   # only once per chapter...
911
912   if (substr($_, 0, 1) eq ".")
913     {
914     handle_directive();
915     }
916   else
917     {
918     while($nextline = <>)
919       {
920       last if $nextline =~ /^(\.|\s*$)/;
921       $_ .= $nextline;
922       }
923     if ($section_pending && !/^\s*$/)
924       {
925       $section_name = (defined $current_section)?
926         "$current_section (continued)" :
927         "$current_chapter (continued)" ;
928       flush_section();
929       push(@section_list, $section_name);
930       $current_section = $section_name;
931       $next_node = $section_name if !$next_node;
932       $section_pending = 0;
933       }
934
935     push(@ONESECTION, handle_text($_));
936     $_ = $nextline;
937     last if !defined($_);
938     redo;
939     }
940   }
941
942 # Flush any pending text, making its next field null.
943 # and fudging section_name for the final section of the previous.
944
945 $section_name = "";
946 flush_section();
947
948 # Set up section name as the start of the next chapter
949
950 $section_name = "Concept Index" if (!$doing_filter);
951
952 if (defined $_ && /^\.chapter (.*)/)
953   {
954   $section_name = $1;
955   $section_name = &dequote($section_name);
956   }
957 $next_node = $section_name;
958
959 # Write out the chapter to the CHAPTERS file, sticking the chapter
960 # menu after the text that came before the first section heading. This
961 # will always at least contain the chapter title.
962
963 printf CHAPTERS ("\@node %s, %s, %s, Top\n",
964   &decomma($current_chapter), &decomma($next_node),
965   &decomma($previous_chapter));
966
967 # The pre-section stuff; if we hit an @end menu line, it is the menu of
968 # a "subsection" before the first section. In that case, we need to put
969 # the chapter's menu one the end of it, and then resume with the rest of
970 # the TOPSECTION data afterwards. We also need to thread together this
971 # "subsection"s nodes because they are all at the same level under the
972 # chapter.
973
974 $in_menu = 0;
975 while(scalar(@TOPSECTION))
976   {
977   $s = shift(@TOPSECTION);
978   if ($s =~ /^\@end menu/)
979     {
980     $in_menu = 1;
981     last;
982     }
983   print CHAPTERS $s;
984   }
985
986 # Menu for sections
987
988 undef $next_actual_section;
989 undef $point_back;
990
991 if (scalar(@section_list))
992   {
993   print CHAPTERS "\n\@sp 2\n\@menu\n" if ! $in_menu;
994   $next_actual_section = $section_list[0];
995   for ($i = 0; $i < scalar(@section_list); $i++)
996     {
997     $section_name = $section_list[$i];
998     $section_name =~ s/\@sc\{([^}]*)\}/\U$1/g;
999     print CHAPTERS "* ${section_name}::\n";
1000     }
1001   $in_menu = 1;
1002   }
1003 print CHAPTERS "\@end menu\n\n" if $in_menu;
1004
1005 # Remainder of topsection; we must arrange that the final @node in
1006 # it (which will have a blank "next" field) actually points on to
1007 # the next section, if any. If this happens, then the next section
1008 # must point back to the final @node.
1009
1010 while(scalar(@TOPSECTION))
1011   {
1012   $s = shift(@TOPSECTION);
1013   if ($next_actual_section && $s =~
1014          /^\@node\s+([^,]+),\s*,\s*([^,]*),\s*(.*)/)
1015     {
1016     my($t1, $t2, $t3) = ($1, $2, $3);    # So can be decomma'd
1017     printf CHAPTERS ("\@node %s, %s, %s, %s\n", &decomma($t1),
1018       &decomma($next_actual_section), &decomma($t2), &decomma($t3));
1019     $point_back = $1;
1020     }
1021   else { print CHAPTERS $s; }
1022   }
1023
1024 close(ONECHAPTER);
1025 open(ONECHAPTER, "$ONECHAPTER") || die "Can't open $ONECHAPTER for reading";
1026
1027 # While copying the chapter data, check for node references to empty
1028 # sections that got omitted and correct them, and correct the prev pointer
1029 # in the first node if necessary.
1030
1031 while ($buff = <ONECHAPTER>)
1032   {
1033   foreach $key (keys %rewrite)
1034     {
1035     $buff =~ s/$key/$rewrite{$key}/;
1036     }
1037   if ($point_back && $buff =~ /^\@node\s+([^,]+),\s*([^,]*),\s*([^,]*),\s*(.*)/)
1038     {
1039     my($t1, $t2, $t4) = ($1, $2, $4);   # so can be decomma'd
1040     printf CHAPTERS ("\@node %s, %s, %s, %s\n", &decomma($t1),
1041       &decomma($t2), &decomma($point_back), &decomma($t4));
1042     undef $point_back;
1043     }
1044   else { print CHAPTERS $buff; }
1045   }
1046
1047 $previous_chapter = $current_chapter;
1048
1049 close(ONECHAPTER);
1050 unlink($ONECHAPTER);
1051 }
1052
1053
1054
1055 ##################################################
1056 #                Main Program                    #
1057 ##################################################
1058
1059 # This is a two-pass algorithm. The first pass goes through and gets the
1060 # variable names for cross references. The second pass does the real work,
1061 # but we can't just read through doing the translation in one pass. We need
1062 # to know the list of chapters in order to build a top-level menu, and for
1063 # each chapter we need to know the sections in order to build a section
1064 # menu. Consequently, make use of temporary files to buffer things.
1065
1066 # This script is used for the filter document and the overview as well;
1067 # flags tell it if it is doing one of them.
1068
1069 $doing_filter = 0;
1070 $skip_else = 0;
1071 $in_itemize = 0;
1072 $lastwasitem = 0;
1073 $confuse = "";
1074
1075 $chapter_number = 0;
1076 $section_number = 0;
1077
1078 if ($#ARGV >= 0 && $ARGV[0] eq "-filter")
1079   {
1080   $doing_filter = 1;
1081   shift @ARGV;
1082   }
1083
1084 # First pass: Just fish out variable settings. Save the arguments so that
1085 # they can be reinstated for a second pass.
1086
1087 print STDERR "Scanning for references\n";
1088 @save_argv = @ARGV;
1089
1090 # Pick up any .set directives right at the very start
1091
1092 while (<>)
1093   {
1094   last if ! /^\.set\s+(\S+)\s+(.+)$/;
1095   $name = $1;
1096   $value = $2;
1097   $value =~ s/^\"?(.*?)\"?\s*$/$1/;
1098   $references{$name} = $value;
1099   }
1100
1101 # Now skip everything before the first .chapter except for
1102 # .index lines that set up indirections. Save these so that
1103 # the relevant index entries can be duplicated.
1104
1105 while (<>)
1106   {
1107   if (/^\.chapter\s+(.+)$/)
1108     {
1109     $chapter_number++;
1110     $section_number = 0;
1111     $current_chapter = $1;
1112     $current_chapter = $current_chapter;
1113     $current_section = "";
1114     last;
1115     }
1116
1117   if (/^\.index\s+([^\$]+)\s+\$it\{see\s+([^}]+)\}\s*$/)
1118     {
1119     $indirections{"$2"} = $1;
1120     }
1121   }
1122
1123 # Do the business
1124
1125 while (<>)
1126   {
1127   if (/^\.chapter\s+(.+)$/)
1128     {
1129     $current_chapter = $1;
1130     $current_chapter = &dequote($current_chapter);
1131     $current_section = "";
1132     }
1133   elsif (/^\.section\s+(.+)$/)
1134     {
1135     $current_section = $1;
1136     $current_section = &dequote($current_section);
1137     $current_section =~ s/://;
1138     }
1139   elsif (/^\.r?set\s+(\S+)\s+(.+)$/ && $1 ne "runningfoot")
1140     {
1141     $name = $1;
1142     $value = $2;
1143
1144     # Only set the first time. This handles a few special cases in part2
1145     # which is included in the filter text as well.
1146
1147     if (!exists($references{$name}))
1148       {
1149       $value =~ s/^\"?(.*?)\"?\s*$/$1/;
1150       $value =~ s/~~chapter\./~~chapter****/;
1151       $value =~ s/~~chapter/$current_chapter/;
1152       $value =~ s/~~section/$current_section/;
1153       $references{$name} = $value;
1154       }
1155     }
1156   }
1157
1158 $final_chapter = defined($current_chapter)? $current_chapter : "";
1159
1160 # Reinstate ARGV with the list of files and proceed to the main pass
1161
1162 @ARGV = @save_argv;
1163
1164 # $asis is set true when processing .display asis blocks, to stop
1165 # characters getting interpreted.
1166
1167 $asis = 0;
1168
1169 # $indisplay is set true while processing .display blocks, to stop
1170 # .newlines being handled therein (adding @* wrecks alignment)
1171
1172 $indisplay = 0;
1173
1174 # $tab is set to the value of the tab stop - only one stop is ever used
1175 # in the Exim source.
1176
1177 $tab = 0;
1178
1179 # Current driver name, for disambiguating nodes
1180
1181 $driver_name = "";
1182
1183 # $section_pending is set if a new section is to be started on hitting
1184 # any data lines.
1185
1186 $section_pending = 0;
1187
1188 # Open a file to buffer up the entire set of chapters
1189
1190 $CHAPTERS = "/tmp/CHAPTERS.$$";
1191 open(CHAPTERS, ">$CHAPTERS") || die "Can't open $CHAPTERS for writing";
1192
1193 # Skip everything before the first .chapter
1194
1195 while (<>) { last if /^\.chapter /; }
1196
1197 # Loop, handling each chapter
1198
1199 $current_up = "";
1200 $previous_chapter = "Top";
1201 $previous_node = "Top";
1202
1203 $chapter_number = 0;
1204 $section_number = 0;
1205
1206 while (defined ($_) && /^\.chapter /)
1207   {
1208   handle_chapter();
1209   }
1210
1211 # Output the stuff at the start of the file
1212
1213 print "\\input texinfo\n";
1214
1215 print "\@set{wmYear} 2003\n";
1216 print "\@set{wmAuthor} Philip Hazel\n";
1217 print "\@set{wmAuthor_email} <ph10\@\@cus.cam.ac.uk>\n";
1218 print "\@set{COPYRIGHT1} Copyright \@copyright{} \@value{wmYear} University of Cambridge\n";
1219
1220 print "\@c %**start of header\n";
1221
1222 if (!$doing_filter)
1223   {
1224   print "\@setfilename spec.info\n";
1225   print "\@settitle Exim Specification\n";
1226   }
1227 else
1228   {
1229   print "\@setfilename filter.info\n";
1230   print "\@settitle Exim Filter Specification\n";
1231   }
1232
1233 print "\@paragraphindent 0\n";
1234 print "\@c %**end of header\n\n";
1235
1236
1237 print "\@titlepage\n";
1238 print "\@title The Exim Mail Transfer Agent\n";
1239 print "\@author \@value{wmAuthor}\n";
1240
1241 print "\@page\n";
1242 print "\@vskip 0pt plus 1filll\n";
1243
1244 print "Permission is granted to make and distribute verbatim copies of this manual provided the\n";
1245 print "copyright notice and this permission notice are preserved on all copies.\n";
1246
1247 print "\@sp2\n";
1248 print "\@value{COPYRIGHT1}\@*\n";
1249
1250 print "\@end titlepage\n\n";
1251
1252 # Output the top-level node and its introductory blurb
1253
1254 print "\@node       Top,       $chapter_list[0], (dir), (dir)\n";
1255 print "\@top\n";
1256
1257 if (!$doing_filter)
1258 {
1259 print <<End;
1260 The Exim Mail Transfer Agent\@*
1261 ****************************
1262
1263 The specification of the Exim Mail Transfer Agent is converted mechanically
1264 into Texinfo format from its original marked-up source. Some typographic
1265 representations are changed, chapters and sections cannot be numbered, and
1266 Texinfo lacks the ability to mark updated parts of the specification with
1267 change bars.
1268
1269 Because the chapters and sections are unnumbered, cross references are set to
1270 their names. This makes the English a bit odd, with phrases like \`see chapter
1271 \"Retry configuration\"\' but it seemed very cumbersome to change this to \`see
1272 the chapter entitled \"Retry configuration\"\' each time.
1273
1274 Each chapter, section, and configuration option has been placed in a separate
1275 Texinfo node. Texinfo doesn\'t allow commas, colons, or apostrophes in node
1276 names, which is a rather nasty restriction. I have arranged not to use colons
1277 or apostrophes in section titles, but cannot bring myself to omit them from
1278 titles such as \"The foo, bar and baz commands\". For the corresponding node
1279 names I have just used multiple occurrences of \"and\", though it looks very
1280 ugly.
1281
1282 If a chapter or section continues after a list of configuration options that is
1283 not in a new section, a new node is started, using the chapter\'s or section\'s
1284 name plus \`(continued)\'. The \`Up\' operation from a section or configuration
1285 option returns to the start of the current chapter; the \`Up\' operation at a
1286 chapter start returns to the top of the document; the \`Up\' in a list of
1287 configuration options within a section returns to the top of that section.
1288
1289 A number of drivers have options with the same name, so they have been
1290 disambiguated by adding the name of the driver to its option names in order to
1291 create node names. Thus, for example, the specification of the \`command\'
1292 options of the \`lmtp\' and \`pipe\' transports are in nodes called \`command
1293 (lmtp)\' and \`command (pipe)\', respectively.
1294
1295 End
1296 }
1297
1298 else
1299 {
1300 print <<End;
1301 Filtering with the Exim Mail Transfer Agent\@*
1302 *******************************************
1303
1304 The specifications of the Exim Mail Transfer Agent\'s filtering facility is
1305 converted mechanically into Texinfo format from its original marked-up source.
1306 Some typographic representations are changed, chapters and sections cannot be
1307 numbered, and Texinfo lacks the ability to mark updated parts of the
1308 specification with change bars.
1309
1310 Because the chapters and sections are unnumbered, cross references are set to
1311 their names. This makes the English a bit odd, with phrases like \`see section
1312 \"Multiple personal mailboxes\"\' but it seemed very cumbersome to change this to
1313 \`see the section entitled \"Multiple personal mailboxes\"\' each time.
1314
1315 End
1316 }
1317
1318 # Output the top-level menu
1319
1320 print "\@menu\n";
1321
1322 while (scalar(@chapter_list))
1323   {
1324   $name = &decomma(shift(@chapter_list));
1325   print "* ${name}::\n";
1326   }
1327 print "* Concept Index::\n" if (!$doing_filter);
1328 print "\@end menu\n\n";
1329
1330 # Copy the chapters, then delete the temporary file
1331
1332 close(CHAPTERS);
1333 open(CHAPTERS, "$CHAPTERS") || die "Can't open $CHAPTERS for reading";
1334 print $buff while($buff = <CHAPTERS>);
1335 close(CHAPTERS);
1336 unlink($CHAPTERS);
1337
1338 # Output the finishing off stuff
1339
1340 if (!$doing_filter)
1341   {
1342   print "\@node Concept Index, , $final_chapter, Top\n";
1343   print "\@chapter Concept Index\n\@printindex cp\n";
1344   print "\@chapter Function Index\n\@printindex fn\n";
1345   }
1346 print "\@contents\n";
1347 print "\@bye\n";
1348
1349 # End