#! /usr/bin/perl -w # A Perl script to turn the SGCAL source of the Exim documentation into # Texinfo input, more or less... # Supply the source file names as arguments. # The output goes to the standard output. ################################################## # Ensure unique node name # ################################################## # Node names must be unique. Occasionally in the Exim spec there are duplicate # section names, and it's become too much of a hassle to keep them distinct # manually. So it is now automated. ########### Never really got this working. Abandoned ############### sub unique { my($node) = $_[0]; if (defined $node_names{$node}) { $node_names{$node} += 1; $node = "$node ($node_names{$node})"; print STDERR "+++ $node\n"; } else { $node_names{$node} = 0; } $node; } ################################################## # De-comma a node name # ################################################## # Commas, colons, and apostrophes are not permitted in Texinfo # node names. I find this incredible, but it is clearly documented. # The Exim manual has been re-organized not to have colons or # apostrophes in any chapter or section titles, but I can't manage # without commas. This function turns "," into " and", which is # the best that can be done; we can use some clever Perlery to # just take out commas before "and". # Sigh. The Sendmail option -p: now means that there's a colon # in the node name for that option. Turn the colon into . This is also # done for menus. # Another thing that causes problems in node names in some versions of # Texinfo is the use of @sc{xxx} for small caps. Just turn these all into # real caps. This is also done for menus. sub decomma { $_[0] =~ s/,(?!\sand)/ and/g; $_[0] =~ s/,//g; $_[0] =~ s/\@sc\{([^}]*)\}/\U$1/g; $_[0] =~ s/://g; $_[0]; } ################################################## # De-quote a string # ################################################## # @x is turned into x, except when x=@, or when asis is set, # in which case single @ must turn into @@. A single substitute # doesn't work in the non-asis case, because of the problems of # handling things like @@@$, so we do it the hard way. sub dequote { if ($asis) { $_[0] =~ s/@/@@/g; } else { $_[0] =~ s/@@/&at&/g; $_[0] =~ s/@([^@])/$1/g; $_[0] =~ s/&at&/@@/g; } $_[0]; } ################################################## # Get next line # ################################################## # Called from handle_directive, to get the next source line # into $_. sub get_next_line { if ($processing_subsection) { return $_ = shift @SUBBUFFER; } else { return $_ = <>; } } ################################################## # Handle text lines # ################################################## # This function is handed whole paragraphs, and we assume that # SGCAL font changing markup is always complete within a paragraph. # We have to replace escaped versions of significant characters with # some magic before performing general transformations, and then # put them back afterwards. The character & is not common in the text, # and && is unknown, so we use that. sub handle_text { $_ = $_[0]; if ($asis) { $_ = dequote($_); s/(\{|\})/\@$1/g; return $_; } while (/~~/) { $left = $`; ($name) = $' =~ /^(\w+)/; $right = $'; $value = $references{$name}; $value = "" if !defined($value); if ($value =~ /\*\*\*\*/) { $value = ($` eq $current_chapter)? "\"$'\"" : "\"$'\" in chapter \"$`\""; $value = "" if $value eq "\"\""; } elsif ($value !~ /^[0-9]+\.[0-9]+$/) # quote unless version number { $value = "\"$value\""; } $_ = "${left}${value}${right}"; } s/\@\@/&&a/g; # @@ s/\@\\/&&b/g; # @\ s/\@/&&g/g; # @> s/\@\{/&&c/g; # @{ s/\@\}/&&d/g; # @} s/\@#/&&s/g; # @# # Now remove all other @'s $_ = dequote($_); # Convert SGCAL markup s/#/ /g; # turn # into a space s/\$~//g; # turn $~ into nothing s/__/_/g; # turn __ into _ s/\$sm\{//g; # turn $sm{ into nothing s/\$sc\{([^\}]*?)\}/$1/g; # turn $sc{xxx} into xxx s/\$st\{([^\}]*?)\}/$1/g; # turn $st{xxx} into xxx s/\$si\{([^\}]*?)\}/$1/g; # turn $si{xxx} into xxx s/\$tt\{([^\}]*?)\}/$1/g; # turn $tt{xxx} into xxx s/\$it\{([^\}]*?)\}/$1/g; # turn $it{xxx} into xxx s/\$bf\{([^\}]*?)\}/$1/g; # turn $bf{xxx} into xxx s/\$rm\{([^\}]*?)\}/$1/g; # turn $rm{xxx} into xxx s/\$cb\{([^\}]*?)\}/$1/g; # turn $cb{xxx} into xxx # This is a fudge for some specific usages of $<; can't just do a global # is it occurs in things like $ as well. s/\[\$<\]/[]/g; # turn [$<] into [] s/&&b\$<\./&&b./g; # turn \$<. into \. (\ == &&b by now) s/(\d)\$<-/$1-/g; # turn 0$<- into 0- # There is one case where the terminating } of an escape sequence is # in another paragraph - this follows $sm{ - it can be fixed by # removing any stray } in a paragraph that contains no { chars. s/\}//g if !/\{/; # Any remaining {} must be escaped to prevent Texinfo from complaining s/(\{|\})/\@$1/g; # Now to conversions that put {} into the file. # Change <<..>> from @var to just <...> as the caps that Texinfo # uses look far too shouty. s/\\\\([^\\]*?)\\\\/\@sc\{\L$1\}/g; # turn \\xxx\\ into @sc{xxx} s/\\\(([^)]*?)\)\\/\@file\{$1\}/g; # turn \(xxx)\ into @file{xxx} s/\\\"([^\"]*?)\"\\/\@file\{$1\}/g; # turn \"xxx"\ into @file{xxx} s/\\\?([^?]*?)\?\\/$1/g; # turn \?URL?\ into URL s/<<([^>]*?)>>/<$1>/g; # turn <> into s/\\\$([^\$]*?)\$\\/\$$1/g; # turn \$xxx$\ into $xxx s/\\\-([^-]*?)\-\\/\-$1/g; # turn \-xxx-\ into -xxx s/\\\*\*([^*]*?)\*\*\\/$1/g; # turn \**xxx**\ into xxx s/\[\(([\w\/]*)\)\]//g; # remove inline HTML s/\\\*([^*]*?)\*\\/\@dfn\{$1\}/g; # turn \*xxx*\ into @dfn{xxx} s/\\%([^*]*?)%\\/\@dfn\{$1\}/g; # turn \%xxx%\ into @dfn{xxx} s/:::([^:]*?)::/\@dfn\{:$1:\}/g; # turn :::xxx:: into @dfn{:xxx:} s/::([^:]*?)::/\@dfn\{$1:\}/g; # turn ::xxx:: into @dfn{xxx:} s/\\([^\\]*?)\\/\@dfn\{$1\}/g; # turn \xxx\ into @dfn{xxx} s/\$\*\$/\*/g; # turn $*$ into * # Put back escaped SGCAL specials s/&&a/\@\@/g; s/&&b/\\/g; s/&&l//g; s/&&c/\@{/g; s/&&rc/{/g; s/&&rd/}/g; s/&&d/\@}/g; s/&&s/#/g; # Remove any null flags ($$) s/\$\$//g; # If the paragraph starts with $c\b, change this into @center. Assume # we don't ever get two of these in a row. s/^\$c\b/\@center /; # If the paragraph starts with $e\b, stuff some tabs in there, as # Texinfo can't do this on its own (as far as I can see). They must # tabs; Texinfo treats them as different to spaces. Sigh. s/^\$e\b/\t\t\t\t\t\t\t/; # Handle $t. The Exim spec only ever has one tab per line. Er, not # quite true, but a good enough assumption. $t is always followed # by a non-word character. # The .tabs directive has stashed the value in the $tab variable. # Don't count Texinfo font chars. while (/(^|.+?\n)(.+?)\$t(\W.*\n)/) { $before = $` . $1; $after = $'; $left = $2; $right = $3; $left =~ s/\s$//; $right =~ s/^\s+//; $plainleft = $left; $plainleft =~ s/\@[a-z]+\{([^}]+?)\}/$1/g; $plainleft =~ s/\@//g; $_ = $before . $left . (" " x ($tab - length($plainleft))) . $right . $after; # Fudge for the one case where there are two tabs if ($tab2 != 0) { $temp = $tab; $tab = $tab2; $tab2 = $temp; } } # Return the new line (paragraph) $_; } ################################################## # Handle directive lines # ################################################## # Use get_next_line() instead of <> because this is called to process # stacked up subsection lines sub handle_directive { my($new_lastwasitem) = 0; # Chapter directives just require . => @; however, dequoting the # line thereafter will remove the first @, so just force it back # afterwards. If the chapter is is one describing a driver, set # the driver name. if (/\.chapter/) { tr/./@/; push(@ONESECTION, "@" . &dequote("$_\n")); $driver_name = (/The\s+(\S+)\s+(director|router|transport|authenticator)/)? $1 : (/Generic options common to both directors and routers/)? "director or router" : (/[Gg]eneric\s+options for (\S+)s/)? $1 : ""; $driver_name = &dequote($driver_name); } # Section directives just require . => @; however, dequoting the # line thereafter will remove the first @, so just force it back # afterwards. Remove any colons in section titles as they cause # Texinfo trouble. Also remove any \\ (small caps) markup, which # appears in a couple of cases. elsif (/\.section/) { tr/./@/; s/://; s"\\\\""g; push(@ONESECTION, "@" . &dequote("$_\n")); # Horrible magic fudge to cope with the fact that exim_lock has # -v and -q options, just like the main program. $driver_name = "exim_lock" if /Mailbox maintenance/; # Similar magic for exiqgrep, which also duplicates options $driver_name = "exiqgrep" if /Selective queue listing/; } # .newline must put @* on the end of the previous line, if any, except # inside a display, where it causes trouble. elsif (/\.newline/) { if (@ONESECTION > 0 && ! $indisplay) { $_ = pop(@ONESECTION); s/(\n*)$/\@*$1/; push(@ONESECTION, $_); } } # .blank turns into @sp, adding 1 if no argument elsif (/\.blank/) { s/\.blank\s+(\d+)/\@sp $1/; s/\.blank/\@sp 1/; push(@ONESECTION, $_); } # .rule turns into a line of hyphens elsif (/\.rule/) { push(@ONESECTION, ("-" x ($in_itemize? 68 : 73)) . "\@*\n"); } # There's one explicit .tabset setting for two tab stops elsif (/\.tabset\s*/) { $rest = $'; ($first,$second) = $rest =~ /(\d+)em\s+(\d+)em/; $tab = ($first * 7)/6; $tab2 = $tab + ($second * 7)/6; } # .tabs remembers the first (and only) tab setting elsif (/\.tabs\s*/) { $tab = ($' * 7)/6; $tab2 = 0; } # .tempindent is used only to align some of the expansion stuff nicely; # just ignore it. It is used in conjunction with .push/.pop. elsif (/\.(tempindent|push|pop)\s*/) { } # There are some instances of .if ~~sys.fancy in the source. Some of these # are two-part things, in which case we just keep the non-fancy. For diagrams, # however, they are in three parts: # # .if ~~sys.fancy # # .elif ~~nothtml # # .else # # .fi elsif (/\.if \~\~sys\.fancy/) { while (&get_next_line()) { last if /\.else\b/ || /\.elif\s+\~\~nothtml/ || /\.fi\b/; } if (/\.elif/) { $skip_else = 1; } } # There are occasional requirements to do things differently for # Texinfo/HTML and the PS/txt versions, and there are also some # HTML-specific things. elsif (/\.if\s+~~sgcal/ || /\.if\s+~~html/) { while (&get_next_line()) { last if /\.else\b/ || /\.fi\b/; } } # We may also have Texinfo-specific bits elsif (/^\.if\s+~~texinfo/) { $skip_else = 1; } # Ignore any other .if directives elsif (/\.if/) {} # Skip else part if flag set elsif (/\.else/ && $skip_else) { while (&get_next_line()) { last if /\.fi\b/; } $skip_else = 0; } # Ignore other .fi and .else as any .if directives are handled specially elsif (/\.fi/ || /\.else/) {} # Ignore .indent elsif (/\.indent/) {} # Plain .index goes to @cindex - the "concept" index. Also, there # are some calls to vindex and findex in the SGCAL source - treated # as synonymous with .index - which are split into the equivalent # indexes here. elsif (/\.(.?)index/) { $rest = $'; $letter = ($1 eq "")? "c" : $1; tr/./@/; # .index -> @index $rest =~ s/\\\(//g; # Remove markup $rest =~ s/\)\\//g; $rest =~ s/\\%//g; $rest =~ s/%\\//g; $rest =~ s/\\\*//g; $rest =~ s/\*\\//g; $rest =~ s/\\"//g; $rest =~ s/"\\//g; $rest =~ s/:://g; $rest =~ s/\\-/-/g; $rest =~ s/-\\//g; $rest =~ s/~~//g; $rest =~ tr/\\//d; # Remove \ $rest =~ s/\@\$/\$/g; # @$ -> $ $rest =~ s/\@_/_/g; # @_ -> _ $rest =~ s/\@\+/+/g; # @+ -> + $rest =~ s/\$\*\$/\*/g; # $*$ -> * $rest =~ s/\$([^\$]+)\$/\$$1/g; # $x$ -> $x $rest =~ s/^\s+//; # Remove leading spaces $rest =~ s/\s+$//; # Remove trailing spaces $rest =~ s/\|\|/:/; # || -> : push(@ONESECTION, "\@${letter}index $rest\n"); # Duplicate entries for things that were listed as "x see y" if (defined $indirections{$rest}) { push(@ONESECTION, "\@${letter}index $indirections{$rest}\n"); } } # Various flavours of numberpars map to itemize and enumerate. # Haven't found a way of having a blank space 'bullet' yet, so # currently using minus. elsif (/\.numberpars/) { $rest = $'; $type = "enumerate"; $flag = ""; if ($rest =~ /\$\./) { $flag = " \@bullet"; $type = "itemize" } elsif ($rest =~ /\" \"/) { $flag = " \@minus"; $type = "itemize"; } elsif ($rest =~ /roman/) { $flag = " a"; $type = "enumerate"; } push(@ONESECTION, "\n\@$type$flag\n\n\@item\n"); push(@ENDLIST, $type); $in_itemize++; } elsif (/\.nextp/) { push(@ONESECTION, "\n\@item\n"); } elsif (/\.endp/) { $endname = pop(@ENDLIST); push(@ONESECTION, "\@end $endname\n\n"); $in_itemize--; } # The normal .display (typewriter font) => @example, while the rm # form goes to @display (no change of font). For Texinfo we need a # blank line after @display. elsif (/\.display/) { $type = /rm/? "display" : "example"; $asis = 1 if /asis/; $indisplay = 1; push(@ONESECTION, "\@$type\n\n"); push(@ENDLIST, $type); } elsif (/\.endd/) { $asis = 0; $indisplay = 0; $endname = pop(@ENDLIST); push(@ONESECTION, "\@end $endname\n\n"); } elsif (/\.conf/) { ($option, $type, $default) = /\.conf\s+(\S+)\s+("(?:[^"]|"")+"|\S+)\s+("(?:[^"]|"")+"|.*)/; $option = &dequote($option); # If $type ends with $**$ (turned into a dagger for PS version), # replace with ", expanded". Remove any surrounding quotes. $type =~ s/^"([^"]+)"/$1/; $type =~ s/\$\*\*\$/, expanded/; # Default may be quoted, and it may also have quotes that are required, # if it is a string. $default =~ s/^"(.*)"$/$1/; $default =~ s/""/"/g; $default = &handle_text($default); push(@ONESECTION, "\nType: $type\@*\nDefault: $default\n\n"); } # Handle .startitems, .enditems, and .item elsif (/\.startitem/ || /\.enditem/) {} elsif (/\.item/) { $arg = $'; $arg =~ s/^\s*"//; $arg =~ s/"\s*$//; $arg = &dequote($arg); $arg = &handle_text("\\**$arg**\\"); # If there are two .items in a row, we don't want to put in the # separator line. # push(@ONESECTION, "\n\@example\n"); push(@ONESECTION, "\n"); if (! $lastwasitem) { push(@ONESECTION, "_" x 75, "\n\n"); } # push(@ONESECTION, "$arg\n\@end example\n\n"); push(@ONESECTION, "$arg\n\n"); $new_lastwasitem = 1; } elsif (/\.option/) { chomp($arg = $'); $arg =~ s/^\s*//; $arg = &dequote("-$arg"); $arg = &handle_text($arg); } # Texinfo has no facility for emphasis bars. elsif (/\.em/) {} elsif (/\.nem/) {} # Just ignore any .(r)set directives pro tem (or maybe always!) elsif (/\.r?set/) {} # Ignore .space, .linelength, and .justify elsif (/\.space/ || /\.justify/ || /\.linelength/) {} # Found an SGCAL directive that isn't dealt with. Oh dear. # Turn the embarrassing characters into question marks and # output it in an emphasized way. else { tr/@{}/???/; push(@ONESECTION, "\n\>>>>>>> $_\n") if ! /^\.( |$)/; } $lastwasitem = $new_lastwasitem; } ################################################## # Flush a section # ################################################## # $section_name is the name of the next section. # $current_section is the name of the one we have buffered up. # If it is unset, we are at the first section of a chapter. # $previous_node is the section we last flushed if it was a node. sub flush_section { # If there is no text in the section, omit it entirely. However, it # will have had a pointer set up at the start of the previous section. # Remember what to replace this with when the chapter gets flushed. my($skip) = 1; foreach $s (@ONESECTION) { if ($s !~ /^(\@cindex|\@section|\s*$)/) { $skip = 0; last } } if ($skip) { pop @section_list; $rewrite{$current_section} = $section_name; @ONESECTION = (); return; } # There is data in the section: write it out to the chapter file if ($current_section) { printf ONECHAPTER ("\@node %s, %s, %s, %s\n", &decomma($current_section), &decomma($section_name), &decomma($previous_node), &decomma($current_up)); $previous_node = $current_section; while(scalar(@ONESECTION)) { print ONECHAPTER shift(@ONESECTION); } } else { while(scalar(@ONESECTION)) { push(@TOPSECTION, shift(@ONESECTION)); } } } ################################################## # Handle a "subsection" # ################################################## # A "subsection" is a set of options that must have their own # local menu. Do two passes; the first just collects the names # for the menu. This is called for .conf and .option items. sub handle_subsection{ my($type) = $_[0]; my($save_up) = $current_up; $current_up = $current_section? $current_section : $current_chapter; @sublist = (); @SUBBUFFER = (); while (<>) { last if /^\.end$type/; push(@SUBBUFFER, $_); # .conf takes the first non-space string as the name, but as there are # duplicate confs in various parts of the spec, use the driver name to # de-duplicate; .option takes the entire rest of arg as the name, but # removes any sequence of ... because this disturbs TexInfo. Also, it # turns @- into -. if (/^\.$type\s+(\S+)(.*)/) { if ($type eq "conf") { $name = &handle_text($1); $name .= " ($driver_name)" if ($driver_name ne ""); } else { chomp($name = &handle_text("-$1$2")); $name =~ s/\s*\.\.\.//g; $name .= " ($driver_name)" if ($driver_name ne ""); # There seems to be a major problem in texinfo with the string "--". # In the text it gets turned into a single hyphen. This happens if it # is used as a menu item, but *not* as a node name. Exim has a command # line option "--". With no special action, this appears in the menu # as "-", but then the info software complains there is no node called # "-". If we triple it in the menu it gets displayed OK, but building # software complains about non-existent cross references etc. # I have gone for the horrid kludge of turning it into "-" # in the menus and nodes. # Exim 4 has added --help, which has the same problem. $name = "-" if ($name eq "--"); $name = "-help" if ($name eq "--help"); } push(@sublist, $name); } } push (@ONESECTION, "\n\@sp 2\n\@menu\n"); for ($i = 0; $i < scalar(@sublist); $i++) { $mitem = $sublist[$i]; $mitem =~ s/\@sc\{([^}]*)\}/\U$1/g; # Get rid of small caps $mitem =~ s/://g; # Get rid of colons push (@ONESECTION, "* ${mitem}::\n"); } push (@ONESECTION, "\@end menu\n\n"); $prevsub = $current_up; $processing_subsection = 1; while ($_ = shift(@SUBBUFFER)) { if (/^\.$type\s+(\S+)/) { $name = shift @sublist; $next = (scalar(@sublist))? $sublist[0] : ""; push @ONESECTION, sprintf("\@node %s, %s, %s, %s\n", &decomma($name), &decomma($next), &decomma($prevsub), &decomma($current_up)); if ($name eq "-") # Fudge for Texinfo { push(@ONESECTION, "\@findex $name\n", "\@unnumberedsubsec --- option\n"); push(@ONESECTION, "This option consists of two consecutive hyphens. It appears in\n", "the menu as \"-\" because otherwise Texinfo gets\n", "confused with its cross-referencing.\n"); } elsif ($name eq "-help") # Fudge for Texinfo { push(@ONESECTION, "\@findex $name\n", "\@unnumberedsubsec ---help option\n"); push(@ONESECTION, "This option consists of \"help\" preceded by two consecutive\n" . "hyphens. It appears in the menu as \"-help\" because\n" . "otherwise Texinfo gets confused with its cross-referencing.\n"); } else { push(@ONESECTION, "\@findex $name\n", "\@unnumberedsubsec $name option\n"); } $prevsub = $name; } # Then handle as text or directive if (substr($_, 0, 1) eq ".") { handle_directive(); } else { while($nextline = shift(@SUBBUFFER)) { last if $nextline =~ /^(\.|\s*$)/; $_ .= $nextline; } push(@ONESECTION, handle_text($_)); $_ = $nextline; last if !defined($_); redo; } } $processing_subsection = 0; $section_pending = 1; $current_up = $save_up; } ################################################## # Handle a single chapter # ################################################## sub handle_chapter{ chop; ($current_chapter) = /^\.chapter (.*)/; $current_chapter = &dequote($current_chapter); $current_chapter = $current_chapter; my($tmp) = $current_chapter; $tmp =~ s/\[\[\[\]\]\]/./; print STDERR "processing chapter: $tmp\n"; # Remember the chapter name for the top-level menu push(@chapter_list, $current_chapter); # Open a temporary file to hold the chapter's text while collecting # all its sections for a chapter-level menu. $ONECHAPTER = "/tmp/ONECHAPTER.$$"; open(ONECHAPTER, ">$ONECHAPTER") || die "Can't open $ONECHAPTER for writing"; # Initialize for handling sections @section_list = (); %rewrite = (); @ONESECTION = (); @TOPSECTION = (); undef $current_section; undef $next_node; $processing_subsection = 0; $previous_node = $current_up = $current_chapter; $section_pending = 0; # Handle the .chapter directive as the first text of a section without # a section title. handle_directive(); # Loop, handling each section. Assume they are sufficiently short that # we can buffer the text in store, in an array called ONESECTION, instead # of thrashing yet another file. while (<>) { last if /^\.chapter /; # Handle a new section, preserving $_ (handle_text flattens it). # It seems we cannot get a fullstop into a Texinfo node name; use a magic # character string that gets turned back into a dot by the post-processing. if (/^\.section\s+/) { $save = $_; $section_name = $'; $section_name =~ s/(\s|\n)+$//; $section_name =~ s/://; $section_name = &handle_text($section_name); flush_section(); push(@section_list, $section_name); $current_section = $section_name; $next_node = $section_name if !$next_node; $section_pending = 0; $_ = $save; } # The .startconf macro introduces a set of .conf's which must have # their own local set of menus. Suspend processing the section while # we sort out the menu and copy their data. This is all done in a # subroutine that is shared with options. elsif (/^\.startconf\s+(.*)/) { $confuse = $1; $confuse = &dequote($confuse); handle_subsection("conf"); next; } elsif (/^\.startoption/) { handle_subsection("option"); next; } # Deal with the actual data lines; if there's a section pending # start a new section on hitting some text. We hope this happens # only once per chapter... if (substr($_, 0, 1) eq ".") { handle_directive(); } else { while($nextline = <>) { last if $nextline =~ /^(\.|\s*$)/; $_ .= $nextline; } if ($section_pending && !/^\s*$/) { $section_name = (defined $current_section)? "$current_section (continued)" : "$current_chapter (continued)" ; flush_section(); push(@section_list, $section_name); $current_section = $section_name; $next_node = $section_name if !$next_node; $section_pending = 0; } push(@ONESECTION, handle_text($_)); $_ = $nextline; last if !defined($_); redo; } } # Flush any pending text, making its next field null. # and fudging section_name for the final section of the previous. $section_name = ""; flush_section(); # Set up section name as the start of the next chapter $section_name = "Concept Index" if (!$doing_filter); if (defined $_ && /^\.chapter (.*)/) { $section_name = $1; $section_name = &dequote($section_name); } $next_node = $section_name; # Write out the chapter to the CHAPTERS file, sticking the chapter # menu after the text that came before the first section heading. This # will always at least contain the chapter title. printf CHAPTERS ("\@node %s, %s, %s, Top\n", &decomma($current_chapter), &decomma($next_node), &decomma($previous_chapter)); # The pre-section stuff; if we hit an @end menu line, it is the menu of # a "subsection" before the first section. In that case, we need to put # the chapter's menu one the end of it, and then resume with the rest of # the TOPSECTION data afterwards. We also need to thread together this # "subsection"s nodes because they are all at the same level under the # chapter. $in_menu = 0; while(scalar(@TOPSECTION)) { $s = shift(@TOPSECTION); if ($s =~ /^\@end menu/) { $in_menu = 1; last; } print CHAPTERS $s; } # Menu for sections undef $next_actual_section; undef $point_back; if (scalar(@section_list)) { print CHAPTERS "\n\@sp 2\n\@menu\n" if ! $in_menu; $next_actual_section = $section_list[0]; for ($i = 0; $i < scalar(@section_list); $i++) { $section_name = $section_list[$i]; $section_name =~ s/\@sc\{([^}]*)\}/\U$1/g; print CHAPTERS "* ${section_name}::\n"; } $in_menu = 1; } print CHAPTERS "\@end menu\n\n" if $in_menu; # Remainder of topsection; we must arrange that the final @node in # it (which will have a blank "next" field) actually points on to # the next section, if any. If this happens, then the next section # must point back to the final @node. while(scalar(@TOPSECTION)) { $s = shift(@TOPSECTION); if ($next_actual_section && $s =~ /^\@node\s+([^,]+),\s*,\s*([^,]*),\s*(.*)/) { my($t1, $t2, $t3) = ($1, $2, $3); # So can be decomma'd printf CHAPTERS ("\@node %s, %s, %s, %s\n", &decomma($t1), &decomma($next_actual_section), &decomma($t2), &decomma($t3)); $point_back = $1; } else { print CHAPTERS $s; } } close(ONECHAPTER); open(ONECHAPTER, "$ONECHAPTER") || die "Can't open $ONECHAPTER for reading"; # While copying the chapter data, check for node references to empty # sections that got omitted and correct them, and correct the prev pointer # in the first node if necessary. while ($buff = ) { foreach $key (keys %rewrite) { $buff =~ s/$key/$rewrite{$key}/; } if ($point_back && $buff =~ /^\@node\s+([^,]+),\s*([^,]*),\s*([^,]*),\s*(.*)/) { my($t1, $t2, $t4) = ($1, $2, $4); # so can be decomma'd printf CHAPTERS ("\@node %s, %s, %s, %s\n", &decomma($t1), &decomma($t2), &decomma($point_back), &decomma($t4)); undef $point_back; } else { print CHAPTERS $buff; } } $previous_chapter = $current_chapter; close(ONECHAPTER); unlink($ONECHAPTER); } ################################################## # Main Program # ################################################## # This is a two-pass algorithm. The first pass goes through and gets the # variable names for cross references. The second pass does the real work, # but we can't just read through doing the translation in one pass. We need # to know the list of chapters in order to build a top-level menu, and for # each chapter we need to know the sections in order to build a section # menu. Consequently, make use of temporary files to buffer things. # This script is used for the filter document and the overview as well; # flags tell it if it is doing one of them. $doing_filter = 0; $skip_else = 0; $in_itemize = 0; $lastwasitem = 0; $confuse = ""; $chapter_number = 0; $section_number = 0; if ($#ARGV >= 0 && $ARGV[0] eq "-filter") { $doing_filter = 1; shift @ARGV; } # First pass: Just fish out variable settings. Save the arguments so that # they can be reinstated for a second pass. print STDERR "Scanning for references\n"; @save_argv = @ARGV; # Pick up any .set directives right at the very start while (<>) { last if ! /^\.set\s+(\S+)\s+(.+)$/; $name = $1; $value = $2; $value =~ s/^\"?(.*?)\"?\s*$/$1/; $references{$name} = $value; } # Now skip everything before the first .chapter except for # .index lines that set up indirections. Save these so that # the relevant index entries can be duplicated. while (<>) { if (/^\.chapter\s+(.+)$/) { $chapter_number++; $section_number = 0; $current_chapter = $1; $current_chapter = $current_chapter; $current_section = ""; last; } if (/^\.index\s+([^\$]+)\s+\$it\{see\s+([^}]+)\}\s*$/) { $indirections{"$2"} = $1; } } # Do the business while (<>) { if (/^\.chapter\s+(.+)$/) { $current_chapter = $1; $current_chapter = &dequote($current_chapter); $current_section = ""; } elsif (/^\.section\s+(.+)$/) { $current_section = $1; $current_section = &dequote($current_section); $current_section =~ s/://; } elsif (/^\.r?set\s+(\S+)\s+(.+)$/ && $1 ne "runningfoot") { $name = $1; $value = $2; # Only set the first time. This handles a few special cases in part2 # which is included in the filter text as well. if (!exists($references{$name})) { $value =~ s/^\"?(.*?)\"?\s*$/$1/; $value =~ s/~~chapter\./~~chapter****/; $value =~ s/~~chapter/$current_chapter/; $value =~ s/~~section/$current_section/; $references{$name} = $value; } } } $final_chapter = defined($current_chapter)? $current_chapter : ""; # Reinstate ARGV with the list of files and proceed to the main pass @ARGV = @save_argv; # $asis is set true when processing .display asis blocks, to stop # characters getting interpreted. $asis = 0; # $indisplay is set true while processing .display blocks, to stop # .newlines being handled therein (adding @* wrecks alignment) $indisplay = 0; # $tab is set to the value of the tab stop - only one stop is ever used # in the Exim source. $tab = 0; # Current driver name, for disambiguating nodes $driver_name = ""; # $section_pending is set if a new section is to be started on hitting # any data lines. $section_pending = 0; # Open a file to buffer up the entire set of chapters $CHAPTERS = "/tmp/CHAPTERS.$$"; open(CHAPTERS, ">$CHAPTERS") || die "Can't open $CHAPTERS for writing"; # Skip everything before the first .chapter while (<>) { last if /^\.chapter /; } # Loop, handling each chapter $current_up = ""; $previous_chapter = "Top"; $previous_node = "Top"; $chapter_number = 0; $section_number = 0; while (defined ($_) && /^\.chapter /) { handle_chapter(); } # Output the stuff at the start of the file print "\\input texinfo\n"; print "\@set{wmYear} 2003\n"; print "\@set{wmAuthor} Philip Hazel\n"; print "\@set{wmAuthor_email} \n"; print "\@set{COPYRIGHT1} Copyright \@copyright{} \@value{wmYear} University of Cambridge\n"; print "\@c %**start of header\n"; if (!$doing_filter) { print "\@setfilename spec.info\n"; print "\@settitle Exim Specification\n"; } else { print "\@setfilename filter.info\n"; print "\@settitle Exim Filter Specification\n"; } print "\@paragraphindent 0\n"; print "\@c %**end of header\n\n"; print "\@titlepage\n"; print "\@title The Exim Mail Transfer Agent\n"; print "\@author \@value{wmAuthor}\n"; print "\@page\n"; print "\@vskip 0pt plus 1filll\n"; print "Permission is granted to make and distribute verbatim copies of this manual provided the\n"; print "copyright notice and this permission notice are preserved on all copies.\n"; print "\@sp2\n"; print "\@value{COPYRIGHT1}\@*\n"; print "\@end titlepage\n\n"; # Output the top-level node and its introductory blurb print "\@node Top, $chapter_list[0], (dir), (dir)\n"; print "\@top\n"; if (!$doing_filter) { print <); close(CHAPTERS); unlink($CHAPTERS); # Output the finishing off stuff if (!$doing_filter) { print "\@node Concept Index, , $final_chapter, Top\n"; print "\@chapter Concept Index\n\@printindex cp\n"; print "\@chapter Function Index\n\@printindex fn\n"; } print "\@contents\n"; print "\@bye\n"; # End