X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/0756eb3cb50d73a77b486e47528f7cb1bffdb299..495ae4b01f36d0d8bb0e34a1d7263c2b8224aa4a:/doc/doc-scripts/g2t diff --git a/doc/doc-scripts/g2t b/doc/doc-scripts/g2t new file mode 100755 index 000000000..30c713c08 --- /dev/null +++ b/doc/doc-scripts/g2t @@ -0,0 +1,1347 @@ +#! /usr/bin/perl -w +# $Cambridge: exim/doc/doc-scripts/g2t,v 1.1 2004/10/07 15:04:35 ph10 Exp $ + +# 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/) + { + 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; + +$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