Remove file name extensions from build scripts
authorHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Sat, 10 Feb 2018 21:42:44 +0000 (22:42 +0100)
committerHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Sat, 1 Dec 2018 21:46:36 +0000 (22:46 +0100)
For compatibilty maintain the old names by symlinks

README
script/build [new file with mode: 0755]
script/build.sh [changed from file to symlink]
script/eximhtml2txt [new file with mode: 0755]
script/eximhtml2txt.pl [deleted file]
script/gen [new file with mode: 0755]
script/gen.pl [changed from file to symlink]

diff --git a/README b/README
index 569a7da5a642f6b439db9dfa06a1866c800616bf..f2c5e2284e3922642a82ee4e0fe62ec81539578c 100644 (file)
--- a/README
+++ b/README
@@ -8,7 +8,7 @@ Directory : templates/i     - required images
 Directory : templates/howto - Howto documentation source
 File      : templates/wrapper.xsl   - The HTML wrapper. The stuff that's common across all web pages
 
 Directory : templates/howto - Howto documentation source
 File      : templates/wrapper.xsl   - The HTML wrapper. The stuff that's common across all web pages
 
-script/gen.pl does all the magic. Run it with no arguments to get a
+script/gen does all the magic. Run it with no arguments to get a
 list of options. It takes the stylesheets wrapper.xsl, and those
 inside the "web" and "doc" directories and creates a website from
 them. You also tell it where to find spec.xml and filter.xml and it
 list of options. It takes the stylesheets wrapper.xsl, and those
 inside the "web" and "doc" directories and creates a website from
 them. You also tell it where to find spec.xml and filter.xml and it
@@ -17,7 +17,7 @@ examples:
 
 ## Example 1:
 
 
 ## Example 1:
 
-  script/gen.pl --latest 4.72 --web --tmpl templates --docroot /var/www/
+  script/gen --latest 4.72 --web --tmpl templates --docroot /var/www/
 
 This copies all of the files from the "templates/web" directory
 into "/var/www/". When it comes across .xsl files, it generates
 
 This copies all of the files from the "templates/web" directory
 into "/var/www/". When it comes across .xsl files, it generates
@@ -27,7 +27,7 @@ it's ignored.
 
 ## Example 2:
 
 
 ## Example 2:
 
-  script/gen.pl --latest 4.72 --spec docbook/*/spec.xml \
+  script/gen --latest 4.72 --spec docbook/*/spec.xml \
     --tmpl templates --docroot /var/www/
 
 If there are multiple spec.xml files in "specs/*/" for different
     --tmpl templates --docroot /var/www/
 
 If there are multiple spec.xml files in "specs/*/" for different
@@ -37,11 +37,11 @@ contains the version number it's self, so it uses that in $VERSION
 
 ## Example 3:
 
 
 ## Example 3:
 
-  script/build.sh /var/www/
+  script/build /var/www/
 
 Generates everything into output directory /var/www
 
 Generates everything into output directory /var/www
-Look at the script/build.sh for details - it is just a wrapper
-around build/gen.pl
+Look at the script/build for details - it is just a wrapper
+around script/gen
 
 
 All of the above examples make sure the exim-html-current symlink
 
 
 All of the above examples make sure the exim-html-current symlink
@@ -56,7 +56,7 @@ If you wanted to, for example, add a new link to the navigation bar
 on all pages, you would need to re-generate *all* web pages. This
 is a drawback of having a static website without frames. You would
 only need to edit one file though, specifically wrapper.xsl. Once
 on all pages, you would need to re-generate *all* web pages. This
 is a drawback of having a static website without frames. You would
 only need to edit one file though, specifically wrapper.xsl. Once
-that is done, you would run gen.pl with --web, --spec and --filter,
+that is done, you would run gen with --web, --spec and --filter,
 providing the filter.xml and spec.xml files for every piece of
 online documentation.
 
 providing the filter.xml and spec.xml files for every piece of
 online documentation.
 
diff --git a/script/build b/script/build
new file mode 100755 (executable)
index 0000000..56b8f1f
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+TARGET=${1:-/srv/www/vhosts/www.exim.org}
+LATEST=`(cd docbook; ls -1dv [0-9].[0-9]* | tail -1)`
+
+script/gen \
+  --web \
+  --spec docbook/*/spec.xml \
+  --filter  docbook/*/filter.xml \
+  --tmpl templates \
+  --latest "$LATEST" \
+  --docroot "$TARGET"
deleted file mode 100755 (executable)
index 408b0d499054f67be0aef39b6452746058163a92..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-
-TARGET=${1-/srv/www/vhosts/www.exim.org}
-LATEST=`(cd docbook; ls -1d [0-9].[0-9]* | tail -1)`
-
-set -x
-script/gen.pl \
-  --web \
-  --spec docbook/*/spec.xml \
-  --filter  docbook/*/filter.xml \
-  --latest ${LATEST} \
-  --tmpl templates \
-  --docroot ${TARGET}
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..c795b054e5ade51b7031abab1581a5b7e2d2f5ba
--- /dev/null
@@ -0,0 +1 @@
+build
\ No newline at end of file
diff --git a/script/eximhtml2txt b/script/eximhtml2txt
new file mode 100755 (executable)
index 0000000..38892e1
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/env perl
+#
+use strict;
+use warnings;
+
+use File::Spec;
+use HTML::FormatText;
+use HTML::TreeBuilder;
+
+sub process_chapter {
+    my $fn = shift;
+
+    my $tree = HTML::TreeBuilder->new->parse_file($fn);
+    my ($chapter) = $tree->look_down( "_tag", "div", "class", "chapter", );
+    return '' unless ($chapter);
+    my $formatter = HTML::FormatText->new( leftmargin => 0, rightmargin => 72 );
+
+    my $text = $formatter->format($chapter);
+    $tree->delete;
+    return $text;
+}
+
+sub chapters_in_order {
+    my $dir = shift;
+
+    opendir DIR, $dir or die "opendir($dir) failed: $!\n";
+    my @numeric = sort grep {/^ch\d+\.html$/} readdir(DIR);
+    closedir(DIR) or die "closedir($dir) failed: $!\n";
+
+    my @results = map {
+        $_ = File::Spec->catfile($dir, $_);
+        if (-l $_) {
+            my $t;
+            eval { $t = readlink $_ };
+            $_ = File::Spec->rel2abs($t, $dir) if defined $t;
+        }
+        $_
+    } @numeric;
+    return @results;
+}
+
+
+my $dir = shift;
+die "Need a directory\n" unless defined $dir;
+
+foreach my $fn ( chapters_in_order($dir) ) {
+    print "=" x 72, "\n";
+    print $fn, "\n";
+    print "=" x 72, "\n";
+    print process_chapter($fn);
+    print "-" x 72, "\n";
+}
diff --git a/script/eximhtml2txt.pl b/script/eximhtml2txt.pl
deleted file mode 100755 (executable)
index 38892e1..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/bin/env perl
-#
-use strict;
-use warnings;
-
-use File::Spec;
-use HTML::FormatText;
-use HTML::TreeBuilder;
-
-sub process_chapter {
-    my $fn = shift;
-
-    my $tree = HTML::TreeBuilder->new->parse_file($fn);
-    my ($chapter) = $tree->look_down( "_tag", "div", "class", "chapter", );
-    return '' unless ($chapter);
-    my $formatter = HTML::FormatText->new( leftmargin => 0, rightmargin => 72 );
-
-    my $text = $formatter->format($chapter);
-    $tree->delete;
-    return $text;
-}
-
-sub chapters_in_order {
-    my $dir = shift;
-
-    opendir DIR, $dir or die "opendir($dir) failed: $!\n";
-    my @numeric = sort grep {/^ch\d+\.html$/} readdir(DIR);
-    closedir(DIR) or die "closedir($dir) failed: $!\n";
-
-    my @results = map {
-        $_ = File::Spec->catfile($dir, $_);
-        if (-l $_) {
-            my $t;
-            eval { $t = readlink $_ };
-            $_ = File::Spec->rel2abs($t, $dir) if defined $t;
-        }
-        $_
-    } @numeric;
-    return @results;
-}
-
-
-my $dir = shift;
-die "Need a directory\n" unless defined $dir;
-
-foreach my $fn ( chapters_in_order($dir) ) {
-    print "=" x 72, "\n";
-    print $fn, "\n";
-    print "=" x 72, "\n";
-    print process_chapter($fn);
-    print "-" x 72, "\n";
-}
diff --git a/script/gen b/script/gen
new file mode 100755 (executable)
index 0000000..dfb174e
--- /dev/null
@@ -0,0 +1,611 @@
+#!/usr/bin/env perl
+#
+use strict;
+use warnings;
+
+use CSS::Minifier::XS 0.07;
+use File::Basename;
+use File::Copy;
+use File::Find;
+use File::Path qw(make_path);
+use File::Slurp;
+use File::Spec;
+use Getopt::Long;
+use JavaScript::Minifier::XS;
+use Pod::Usage;
+use XML::LibXML;
+use XML::LibXSLT;
+
+my $canonical_url = 'https://www.exim.org/';
+
+## Parse arguments
+my %opt = parse_arguments();
+
+## setup static root location
+## TODO: for doc generation only this should be within the docs dir
+$opt{staticroot} = File::Spec->catdir( $opt{docroot}, 'static' );
+
+## Generate the pages
+my %cache;    # General cache object
+do_doc( 'spec',   $_ ) foreach @{ $opt{spec}   || [] };
+do_doc( 'filter', $_ ) foreach @{ $opt{filter} || [] };
+do_web() if ( $opt{web} );
+do_static() if ( $opt{web} or !$opt{localstatic} );    # need this for all other pages generated
+
+## Add the exim-html-current symlink
+foreach my $type (qw(html pdf)) {
+    print "Symlinking exim-$type-current to exim-$type-$opt{latest}\n" if ( $opt{verbose} );
+    unlink("$opt{docroot}/exim-$type-current");
+    symlink( "exim-$type-$opt{latest}", "$opt{docroot}/exim-$type-current" )
+        || warn "symlink to $opt{docroot}/exim-$type-current failed";
+}
+
+# ------------------------------------------------------------------
+## Generate the website files
+sub do_web {
+
+    ## copy these templates to docroot...
+    copy_transform_files( "$opt{tmpl}/web", $opt{docroot}, 0 );
+}
+
+# ------------------------------------------------------------------
+## Generate the static file set
+sub do_static {
+    my $staticroot = shift || $opt{staticroot};
+
+    ## make sure I have a directory
+    mkdir($staticroot) or die "Unable to make staticroot: $!\n" unless ( -d $staticroot );
+
+    ## copy these templates to docroot...
+    copy_transform_files( "$opt{tmpl}/static", $staticroot, 1 );
+}
+
+# ------------------------------------------------------------------
+## Generate the website files
+sub copy_transform_files {
+    my $source = shift;
+    my $target = shift;
+    my $static = shift;
+
+    ## Make sure the template web directory exists
+    die "No such directory: $source\n" unless ( -d $source );
+
+    ## Scan the web templates
+    find(
+        sub {
+            my ($path) = substr( $File::Find::name, length("$source"), length($File::Find::name) ) =~ m#^/*(.*)$#;
+
+            if ( -d "$source/$path" ) {
+
+                ## Create the directory in the target if it doesn't exist
+                if ( !-d "$target/$path" ) {
+                    mkdir("$target/$path") or die "Unable to make $target/$path: $!\n";
+                }
+
+            }
+            else {
+
+                ## Build HTML from XSL files and simply copy static files which have changed
+                if ( ( !$static ) and ( $path =~ /(.+)\.xsl$/ ) ) {
+                    print "Generating  : /$1.html\n" if ( $opt{verbose} );
+                    transform( undef, "$source/$path", "$target/$1.html" );
+                }
+                elsif ( -f "$source/$path" ) {
+
+                    ## Skip if the file hasn't changed (mtime/size based)
+                    return
+                        if (( -f "$target/$path" )
+                        and ( ( stat("$source/$path") )[9] == ( stat("$target/$path") )[9] )
+                        and ( ( stat("$source/$path") )[7] == ( stat("$target/$path") )[7] ) );
+
+                    if ( $path =~ /(.+)\.css$/ ) {
+                        print "CSS to  : /$path\n" if ( $opt{verbose} );
+                        my $content = read_file("$source/$path");
+                        write_file( "$target/$path", $opt{minify} ? CSS::Minifier::XS::minify($content) : $content );
+                    }
+                    elsif ( $path =~ /(.+)\.js$/ ) {
+                        print "JS to  : /$path\n" if ( $opt{verbose} );
+                        my $content = read_file("$source/$path");
+                        write_file( "$target/$path",
+                            $opt{minify} ? JavaScript::Minifier::XS::minify($content) : $content );
+                    }
+                    else {
+                        ## Copy
+                        print "Copying to  : /$path\n" if ( $opt{verbose} );
+                        copy( "$source/$path", "$target/$path" ) or die "$path: $!";
+                    }
+                    ## Set mtime
+                    utime( time, ( stat("$source/$path") )[9], "$target/$path" );
+                }
+            }
+
+        },
+        "$source"
+    );
+}
+
+# ------------------------------------------------------------------
+## Generate index/chapter files for a doc
+sub do_doc {
+    my ( $type, $xml_path ) = @_;
+
+    ## Read and validate the XML file
+    my $xml = XML::LibXML->new()->parse_file($xml_path) or die $!;
+
+    ## Get the version number
+    my $version = $xml->findvalue('/book/bookinfo/revhistory/revision/revnumber');
+    die "Unable to get version number\n" unless defined $version && $version =~ /^\d+(\.\d+)*$/;
+
+    ## Prepend chapter filenames?
+    my $prepend_chapter = $type eq 'filter' ? 'filter_' : '';
+
+    ## Add the canonical url for this document
+    $xml->documentElement()
+        ->appendTextChild( 'canonical_url',
+        "${canonical_url}exim-html-current/doc/html/spec_html/" . ( $type eq 'spec' ? 'index' : 'filter' ) . ".html" );
+
+    ## Add a url for the latest version of this document
+    if ( $version ne $opt{latest} ) {
+        $xml->documentElement()
+            ->appendTextChild( 'current_url',
+            "../../../../exim-html-current/doc/html/spec_html/" . ( $type eq 'spec' ? 'index' : 'filter' ) . ".html" );
+    }
+
+    ## Fixup the XML
+    xref_fixup( $xml, $prepend_chapter );
+
+    ## set the staticroot
+    my $staticroot =
+        $opt{localstatic}
+        ? File::Spec->catdir( $opt{docroot}, "exim-html-$version", 'doc', 'html', 'static' )
+        : $opt{staticroot};
+    unless ( -d $staticroot ) {
+        make_path( $staticroot, { verbose => $opt{verbose} } );
+        do_static($staticroot);
+    }
+
+    ## Generate the front page
+    {
+        my $path = "exim-html-$version/doc/html/spec_html/" . ( $type eq 'filter' ? $type : 'index' ) . ".html";
+        print "Generating  : docroot:/$path\n" if ( $opt{verbose} );
+        transform( $xml, "$opt{tmpl}/doc/index.xsl", "$opt{docroot}/$path", $staticroot );
+    }
+
+    ## Generate a Table of Contents XML file
+    {
+        my $path =
+            "exim-html-$version/doc/html/spec_html/" . ( $type eq 'filter' ? 'filter_toc' : 'index_toc' ) . ".xml";
+        print "Generating  : docroot:/$path\n" if ( $opt{verbose} );
+        transform( $xml, "$opt{tmpl}/doc/toc.xsl", "$opt{docroot}/$path", $staticroot );
+    }
+
+    ## Generate the chapters
+    my $counter = 0;
+    my @chapters = map { $_->cloneNode(1) } $xml->findnodes('/book/chapter');
+    my( $chapter_title, $chapter_title_prev, $chapter_title_next );
+    foreach my $chapter (@chapters) {
+
+        ## Add a <chapter_id>N</chapter_id> node for the stylesheet to use
+        $chapter->appendTextChild( 'chapter_id', ++$counter );
+
+        ## Get the current and surrounding chapter titles
+        $chapter_title_prev = $chapter_title;
+        $chapter_title      = $chapter_title_next || $chapter->findvalue('title_uri');
+        $chapter_title_next = $chapters[$counter]->findvalue('title_uri') if $counter < int(@chapters);
+
+        ## Add previous/next/canonical urls for nav
+        {
+            $chapter->appendTextChild( 'prev_url',
+                  $counter == 1
+                ? $type eq 'filter'
+                        ? 'filter.html'
+                        : 'index.html'
+                : sprintf( '%sch-%s.html', $prepend_chapter, $chapter_title_prev ) );
+            $chapter->appendTextChild( 'this_url', sprintf( '%sch-%s.html', $prepend_chapter, $chapter_title ) );
+            $chapter->appendTextChild( 'next_url', sprintf( '%sch-%s.html', $prepend_chapter, $chapter_title_next ) )
+                unless int(@chapters) == $counter;
+            $chapter->appendTextChild( 'toc_url', ( $type eq 'filter' ? 'filter' : 'index' ) . '.html' );
+            $chapter->appendTextChild(
+                'canonical_url',
+                sprintf(
+                    'https://www.exim.org/exim-html-current/doc/html/spec_html/%sch-%s.html',
+                    $prepend_chapter, $chapter_title
+                )
+            );
+            if ( $version ne $opt{latest} ) {
+                $chapter->appendTextChild(
+                    'current_url',
+                    sprintf(
+                        '../../../../exim-html-current/doc/html/spec_html/%sch-%s.html',
+                        $prepend_chapter, $chapter_title
+                    )
+                );
+            }
+        }
+
+        ## Create an XML document from the chapter
+        my $doc = XML::LibXML::Document->createDocument( '1.0', 'UTF-8' );
+        $doc->setDocumentElement($chapter);
+
+        ## Transform the chapter into html
+        {
+            my $real_path = sprintf( 'exim-html-%s/doc/html/spec_html/%sch-%s.html', $version, $prepend_chapter, $chapter_title );
+            my $link_path = sprintf( 'exim-html-%s/doc/html/spec_html/%sch%02d.html',  $version, $prepend_chapter, $counter );
+            print "Generating  : docroot:/$real_path\n" if ( $opt{verbose} );
+            transform( $doc, "$opt{tmpl}/doc/chapter.xsl", "$opt{docroot}/$real_path", $staticroot );
+            # Making a relative symlink to a file in the same directory.
+            # Extract just the filename portion of $real_path.
+            my $real_file = basename($real_path);
+            print "Symlinking  : docroot:/$link_path to $real_file\n" if ( $opt{verbose} );
+            if ( -f "$opt{docroot}/$link_path" ) {
+               unlink("$opt{docroot}/$link_path") or die "failed removing $opt{docroot}/$link_path: $!";
+            }
+            symlink( "$real_file", "$opt{docroot}/$link_path" ) || die "symlink to $opt{docroot}/$link_path failed: $!";
+        }
+    }
+}
+
+# ------------------------------------------------------------------
+## Fixup xref tags
+sub xref_fixup {
+    my ( $xml, $prepend_chapter ) = @_;
+
+    my %index = ();
+
+    ## Add the "prepend_chapter" info
+    ( $xml->findnodes('/book') )[0]->appendTextChild( 'prepend_chapter', $prepend_chapter );
+
+    ## Iterate over each chapter
+    my $chapter_counter = 0;
+    foreach my $chapter ( $xml->findnodes('/book/chapter') ) {
+        ++$chapter_counter;
+
+        my $chapter_id = $chapter->getAttribute('id');
+        unless ($chapter_id) {    # synthesise missing id
+            $chapter_id = sprintf( 'chapter_noid_%04d', $chapter_counter );
+            $chapter->setAttribute( 'id', $chapter_id );
+        }
+        my $chapter_title = $chapter->findvalue('title');
+
+        ## Set title_uri so we can use eg ch-introduction.html instead of ch01.html
+        $chapter->appendTextChild( 'title_uri', title_to_uri($chapter_title) );
+
+        $index{$chapter_id} = { chapter_id => $chapter_counter, chapter_title => $chapter_title };
+
+        ## Iterate over each section
+        my $section_counter = 0;
+        foreach my $section ( $chapter->findnodes('section') ) {
+            ++$section_counter;
+
+            my $section_id = $section->getAttribute('id');
+            unless ($section_id) {    # synthesise missing id
+                $section_id = sprintf( 'section_noid_%04d_%04d', $chapter_counter, $section_counter );
+                $section->setAttribute( 'id', $section_id );
+            }
+            my $section_title = $section->findvalue('title');
+
+            $index{$section_id} = {
+                chapter_id    => $chapter_counter,
+                chapter_title => $chapter_title,
+                section_id    => $section_counter,
+                section_title => $section_title
+            };
+        }
+    }
+    ## Build indexes as new chapters
+    build_indexes( $xml, $prepend_chapter, \%index );
+
+    ## Replace all of the xrefs in the XML
+    foreach my $xref ( $xml->findnodes('//xref') ) {
+        my $linkend = $xref->getAttribute('linkend');
+        if ( exists $index{$linkend} ) {
+            $xref->setAttribute( 'chapter_id',    $index{$linkend}{'chapter_id'} );
+            $xref->setAttribute( 'chapter_title', $index{$linkend}{'chapter_title'} );
+            $xref->setAttribute( 'section_id', $index{$linkend}{'section_id'} ) if ( $index{$linkend}{'section_id'} );
+            $xref->setAttribute( 'section_title', $index{$linkend}{'section_title'} )
+                if ( $index{$linkend}{'section_title'} );
+            $xref->setAttribute( 'url',
+                sprintf( '%sch-%s.html', $prepend_chapter, title_to_uri($index{$linkend}{'chapter_title'}) )
+                    . ( $index{$linkend}{'section_id'} ? '#' . $linkend : '' ) );
+        }
+    }
+}
+
+# ------------------------------------------------------------------
+## Build indexes
+sub build_indexes {
+    my ( $xml, $prepend_chapter, $xref ) = @_;
+
+    my $index_hash = {};
+    my $current_id;
+    foreach my $node ( $xml->findnodes('//section | //chapter | //indexterm') ) {
+        if ( $node->nodeName eq 'indexterm' ) {
+            my $role      = $node->getAttribute('role') || 'concept';
+            my $primary   = $node->findvalue('child::primary');
+            my $first     = ( $primary =~ /^[A-Za-z]/ ) ? uc( substr( $primary, 0, 1 ) ) : '';  # first letter or marker
+            my $secondary = $node->findvalue('child::secondary') || '';
+            next unless ( $primary || $secondary );    # skip blank entries for now...
+            $index_hash->{$role}{$first}{$primary}{$secondary} ||= [];
+            push @{ $index_hash->{$role}{$first}{$primary}{$secondary} }, $current_id;
+        }
+        else {
+            $current_id = $node->getAttribute('id');
+        }
+    }
+
+    # now we build a set of new chapters with the index data in
+    my $book = ( $xml->findnodes('/book') )[0];
+    foreach my $role ( sort { $a cmp $b } keys %{$index_hash} ) {
+        my $chapter = XML::LibXML::Element->new('chapter');
+        $book->appendChild($chapter);
+        $chapter->setAttribute( 'id', join( '_', 'index', $role ) );
+        $chapter->setAttribute( 'class', 'index' );
+        $chapter->appendTextChild( 'title', ( ucfirst($role) . ' Index' ) );
+        $chapter->appendTextChild( 'title_uri', title_to_uri(ucfirst($role) . ' Index') );
+
+        foreach my $first ( sort { $a cmp $b } keys %{ $index_hash->{$role} } ) {
+            my $section = XML::LibXML::Element->new('section');
+            my $list    = XML::LibXML::Element->new('variablelist');
+            $chapter->appendChild($section);
+            $section->setAttribute( 'id', join( '_', 'index', $role, $first ) );
+            $section->setAttribute( 'class', 'index' );
+            $section->appendTextChild( 'title', $first ? $first : 'Symbols' );
+            $section->appendChild($list);
+            foreach my $primary ( sort { $a cmp $b } keys %{ $index_hash->{$role}{$first} } ) {
+                my $entry = XML::LibXML::Element->new('varlistentry');
+                my $item  = XML::LibXML::Element->new('listitem');
+                $list->appendChild($entry)->appendTextChild( 'term', $primary );
+                $entry->appendChild($item);
+                my $slist;
+                foreach my $secondary ( sort { $a cmp $b } keys %{ $index_hash->{$role}{$first}{$primary} } ) {
+                    my $para = XML::LibXML::Element->new('para');
+                    if ( $secondary eq '' ) {
+                        $item->appendChild($para);    # skip having extra layer of heirarchy
+                    }
+                    else {
+                        unless ($slist) {
+                            $slist = XML::LibXML::Element->new('variablelist');
+                            $item->appendChild($slist);
+                        }
+                        my $sentry = XML::LibXML::Element->new('varlistentry');
+                        my $sitem  = XML::LibXML::Element->new('listitem');
+                        $slist->appendChild($sentry)->appendTextChild( 'term', $secondary );
+                        $sentry->appendChild($sitem)->appendChild($para);
+                    }
+                    my $count = 0;
+                    foreach my $ref ( @{ $index_hash->{$role}{$first}{$primary}{$secondary} } ) {
+                        $para->appendText(', ')
+                            if ( $count++ );
+                        my $xrefel = XML::LibXML::Element->new('xref');
+                        $xrefel->setAttribute( linkend => $ref );
+                        $xrefel->setAttribute( longref => 1 );
+                        $para->appendChild($xrefel);
+                    }
+                }
+            }
+        }
+    }
+}
+
+# ------------------------------------------------------------------
+## Handle the transformation
+sub transform {
+    my ( $xml, $xsl_path, $out_path, $staticroot_abs ) = @_;
+
+    ## make sure $staticroot is set
+    $staticroot_abs ||= $opt{staticroot};
+
+    ## Build an empty XML structure if an undefined $xml was passed
+    unless ( defined $xml ) {
+        $xml = XML::LibXML::Document->createDocument( '1.0', 'UTF-8' );
+        $xml->setDocumentElement( $xml->createElement('content') );
+    }
+
+    ## Add the current version of Exim to the XML
+    $xml->documentElement()->appendTextChild( 'current_version', $opt{latest} );
+
+    ## Add the old versions of Exim to the XML
+    $xml->documentElement()->appendTextChild( 'old_versions', $_ ) foreach old_docs_versions();
+
+    ## Parse the ".xsl" file as XML
+    my $xsl = XML::LibXML->new()->parse_file($xsl_path) or die $!;
+
+    ## Generate a stylesheet from the ".xsl" XML.
+    my $stylesheet = XML::LibXSLT->new()->parse_stylesheet($xsl);
+
+    ## work out the static root relative to the target
+    my $target_dir = ( File::Spec->splitpath($out_path) )[1];
+    my $staticroot = File::Spec->abs2rel( $staticroot_abs, $target_dir );
+
+    ## Generate a doc from the XML transformed with the XSL
+    my $doc = $stylesheet->transform( $xml, staticroot => sprintf( "'%s'", $staticroot ) );
+
+    ## Make the containing directory if it doesn't exist
+    make_path( ( $out_path =~ /^(.+)\/.+$/ )[0], { verbose => $opt{verbose} } );
+
+    ## Write out the document
+    open my $out, '>', $out_path or die "Unable to write $out_path - $!";
+    print $out $stylesheet->output_as_bytes($doc);
+    close $out;
+}
+
+# ------------------------------------------------------------------
+## Takes a chapter title and fixes it up so it is suitable for use in a URI
+sub title_to_uri {
+    my $title = lc(shift);
+    $title =~ s/[^a-z0-9\s]+//gi; # Only allow spaces, numbers and letters
+    $title =~ s/\s+/_/g;          # Replace spaces with underscores so URLs are easier to copy about
+    return $title;
+}
+
+# ------------------------------------------------------------------
+## Look in the docroot for old versions of the documentation
+sub old_docs_versions {
+    if ( !exists $cache{old_docs_versions} ) {
+        my @versions;
+        foreach ( glob("$opt{docroot}/exim-html-*") ) {
+            push @versions, $1 if /-(\d+(?:\.\d+)?)$/ && $1 lt $opt{latest} && -d $_;
+        }
+        $cache{old_docs_versions} = [ reverse sort { $a cmp $b } @versions ];
+    }
+    return @{ $cache{old_docs_versions} };
+}
+
+# ------------------------------------------------------------------
+## error_help
+sub error_help {
+    my $msg = shift;
+
+    warn $msg;
+    pod2usage( -exitval => 1, -verbose => 0 );
+}
+
+# ------------------------------------------------------------------
+## Parse arguments
+sub parse_arguments {
+
+    my %opt = ( spec => [], filter => [], help => 0, man => 0, web => 0, minify => 1, verbose => 0, localstatic => 0 );
+    GetOptions(
+        \%opt,      'help|h!', 'man!',      'web!',    'spec=s{1,}', 'filter=s{1,}',
+        'latest=s', 'tmpl=s',  'docroot=s', 'minify!', 'verbose!',   'localstatic!'
+    ) || pod2usage( -exitval => 1, -verbose => 0 );
+
+    ## --help
+    pod2usage(0) if ( $opt{help} );
+    pod2usage( -verbose => 2 ) if ( $opt{man} );
+
+    ## --spec and --filter lists
+    foreach my $set (qw[spec filter]) {
+        $opt{$set} =
+            [ map { my $f = File::Spec->rel2abs($_); error_help( 'No such file: ' . $_ ) unless -f $f; $f }
+                @{ $opt{$set} } ];
+    }
+    ## --latest
+    error_help('Missing value for latest') unless ( exists( $opt{latest} ) && defined( $opt{latest} ) );
+    error_help('Invalid value for latest') unless $opt{latest} =~ /^\d+(?:\.\d+)*$/;
+
+    ## --tmpl and --docroot
+    foreach my $set (qw[tmpl docroot]) {
+        error_help( 'Missing value for ' . $set ) unless ( exists( $opt{$set} ) && defined( $opt{$set} ) );
+        my $f = File::Spec->rel2abs( $opt{$set} );
+        error_help( 'No such directory: ' . $opt{$set} ) unless -d $f;
+        $opt{$set} = $f;
+    }
+    error_help('Excess arguments') if ( scalar(@ARGV) );
+
+    error_help('Must include at least one of --web, --spec or --filter')
+        unless ( $opt{web} || scalar( @{ $opt{spec} || [] } ) || scalar( @{ $opt{filter} || [] } ) );
+
+    return %opt;
+}
+
+# ------------------------------------------------------------------
+1;
+
+__END__
+
+=head1 NAME
+
+gen.pl - Generate exim html documentation and website
+
+=head1 SYNOPSIS
+
+gen.pl [options]
+
+ Options:
+   --help              display this help and exits
+   --man               displays man page
+   --spec file...      spec docbook/XML source files
+   --filter file...    filter docbook/XML source files
+   --web               Generate the general website pages
+   --latest VERSION    Required. Specify the latest stable version of Exim.
+   --tmpl PATH         Required. Path to the templates directory
+   --docroot PATH      Required. Path to the website document root
+   --[no-]minify       [Don't] minify CSS and Javascript
+   --localstatic       Makes the static files local to each doc ver
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display help and exits
+
+=item B<--man>
+
+Display man page
+
+=item B<--spec> I<file...>
+
+List of files that make up the specification documentation docbook/XML source
+files.
+
+=item B<--filter> I<file...>
+
+List of files that make up the filter documentation docbook/XML source files.
+
+=item B<--web>
+
+Generate the website from the template files.
+
+=item B<--latest> I<version>
+
+Specify the current exim version. This is used to create links to the current
+documentation.
+
+This option is I<required>
+
+=item B<--tmpl> I<directory>
+
+Specify the directory that the templates are kept in.
+
+This option is I<required>
+
+=item B<--docroot> I<directory>
+
+Specify the directory that the output should be generated into. This is the
+website C<docroot> directory.
+
+This option is I<required>
+
+=item B<--minify>
+
+If this option is set then both the CSS and Javascript files processed are
+minified using L<CSS::Minifier::XS> and L<JavaScript::Minifier::XS>
+respectively.
+
+This option is set by default - to disable it specify C<--no-minify>
+
+=item B<--localstatic>
+
+Makes the static files (CSS, images etc), local for each version of the
+documentation. This is more suitable for packaged HTML documentation.
+
+=back
+
+=head1 DESCRIPTION
+
+Generates the exim website and HTML documentation.
+
+=head1 EXAMPLE
+
+    script/gen.pl \
+      --web \
+      --spec docbook/*/spec.xml \
+      --filter  docbook/*/filter.xml \
+      --latest 4.72 \
+      --tmpl templates \
+      --docroot /tmp/website
+
+=head1 AUTHOR
+
+Mike Cardwell
+
+Nigel Metheringham <nigel@exim.org> - mostly broke the framework Mike produced.
+
+=head1 COPYRIGHT
+
+Copyright 2010-2012 Exim Maintainers. All rights reserved.
+
+=cut
deleted file mode 100755 (executable)
index dfb174eeb52c488ace69f0188211b349732617b1..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,611 +0,0 @@
-#!/usr/bin/env perl
-#
-use strict;
-use warnings;
-
-use CSS::Minifier::XS 0.07;
-use File::Basename;
-use File::Copy;
-use File::Find;
-use File::Path qw(make_path);
-use File::Slurp;
-use File::Spec;
-use Getopt::Long;
-use JavaScript::Minifier::XS;
-use Pod::Usage;
-use XML::LibXML;
-use XML::LibXSLT;
-
-my $canonical_url = 'https://www.exim.org/';
-
-## Parse arguments
-my %opt = parse_arguments();
-
-## setup static root location
-## TODO: for doc generation only this should be within the docs dir
-$opt{staticroot} = File::Spec->catdir( $opt{docroot}, 'static' );
-
-## Generate the pages
-my %cache;    # General cache object
-do_doc( 'spec',   $_ ) foreach @{ $opt{spec}   || [] };
-do_doc( 'filter', $_ ) foreach @{ $opt{filter} || [] };
-do_web() if ( $opt{web} );
-do_static() if ( $opt{web} or !$opt{localstatic} );    # need this for all other pages generated
-
-## Add the exim-html-current symlink
-foreach my $type (qw(html pdf)) {
-    print "Symlinking exim-$type-current to exim-$type-$opt{latest}\n" if ( $opt{verbose} );
-    unlink("$opt{docroot}/exim-$type-current");
-    symlink( "exim-$type-$opt{latest}", "$opt{docroot}/exim-$type-current" )
-        || warn "symlink to $opt{docroot}/exim-$type-current failed";
-}
-
-# ------------------------------------------------------------------
-## Generate the website files
-sub do_web {
-
-    ## copy these templates to docroot...
-    copy_transform_files( "$opt{tmpl}/web", $opt{docroot}, 0 );
-}
-
-# ------------------------------------------------------------------
-## Generate the static file set
-sub do_static {
-    my $staticroot = shift || $opt{staticroot};
-
-    ## make sure I have a directory
-    mkdir($staticroot) or die "Unable to make staticroot: $!\n" unless ( -d $staticroot );
-
-    ## copy these templates to docroot...
-    copy_transform_files( "$opt{tmpl}/static", $staticroot, 1 );
-}
-
-# ------------------------------------------------------------------
-## Generate the website files
-sub copy_transform_files {
-    my $source = shift;
-    my $target = shift;
-    my $static = shift;
-
-    ## Make sure the template web directory exists
-    die "No such directory: $source\n" unless ( -d $source );
-
-    ## Scan the web templates
-    find(
-        sub {
-            my ($path) = substr( $File::Find::name, length("$source"), length($File::Find::name) ) =~ m#^/*(.*)$#;
-
-            if ( -d "$source/$path" ) {
-
-                ## Create the directory in the target if it doesn't exist
-                if ( !-d "$target/$path" ) {
-                    mkdir("$target/$path") or die "Unable to make $target/$path: $!\n";
-                }
-
-            }
-            else {
-
-                ## Build HTML from XSL files and simply copy static files which have changed
-                if ( ( !$static ) and ( $path =~ /(.+)\.xsl$/ ) ) {
-                    print "Generating  : /$1.html\n" if ( $opt{verbose} );
-                    transform( undef, "$source/$path", "$target/$1.html" );
-                }
-                elsif ( -f "$source/$path" ) {
-
-                    ## Skip if the file hasn't changed (mtime/size based)
-                    return
-                        if (( -f "$target/$path" )
-                        and ( ( stat("$source/$path") )[9] == ( stat("$target/$path") )[9] )
-                        and ( ( stat("$source/$path") )[7] == ( stat("$target/$path") )[7] ) );
-
-                    if ( $path =~ /(.+)\.css$/ ) {
-                        print "CSS to  : /$path\n" if ( $opt{verbose} );
-                        my $content = read_file("$source/$path");
-                        write_file( "$target/$path", $opt{minify} ? CSS::Minifier::XS::minify($content) : $content );
-                    }
-                    elsif ( $path =~ /(.+)\.js$/ ) {
-                        print "JS to  : /$path\n" if ( $opt{verbose} );
-                        my $content = read_file("$source/$path");
-                        write_file( "$target/$path",
-                            $opt{minify} ? JavaScript::Minifier::XS::minify($content) : $content );
-                    }
-                    else {
-                        ## Copy
-                        print "Copying to  : /$path\n" if ( $opt{verbose} );
-                        copy( "$source/$path", "$target/$path" ) or die "$path: $!";
-                    }
-                    ## Set mtime
-                    utime( time, ( stat("$source/$path") )[9], "$target/$path" );
-                }
-            }
-
-        },
-        "$source"
-    );
-}
-
-# ------------------------------------------------------------------
-## Generate index/chapter files for a doc
-sub do_doc {
-    my ( $type, $xml_path ) = @_;
-
-    ## Read and validate the XML file
-    my $xml = XML::LibXML->new()->parse_file($xml_path) or die $!;
-
-    ## Get the version number
-    my $version = $xml->findvalue('/book/bookinfo/revhistory/revision/revnumber');
-    die "Unable to get version number\n" unless defined $version && $version =~ /^\d+(\.\d+)*$/;
-
-    ## Prepend chapter filenames?
-    my $prepend_chapter = $type eq 'filter' ? 'filter_' : '';
-
-    ## Add the canonical url for this document
-    $xml->documentElement()
-        ->appendTextChild( 'canonical_url',
-        "${canonical_url}exim-html-current/doc/html/spec_html/" . ( $type eq 'spec' ? 'index' : 'filter' ) . ".html" );
-
-    ## Add a url for the latest version of this document
-    if ( $version ne $opt{latest} ) {
-        $xml->documentElement()
-            ->appendTextChild( 'current_url',
-            "../../../../exim-html-current/doc/html/spec_html/" . ( $type eq 'spec' ? 'index' : 'filter' ) . ".html" );
-    }
-
-    ## Fixup the XML
-    xref_fixup( $xml, $prepend_chapter );
-
-    ## set the staticroot
-    my $staticroot =
-        $opt{localstatic}
-        ? File::Spec->catdir( $opt{docroot}, "exim-html-$version", 'doc', 'html', 'static' )
-        : $opt{staticroot};
-    unless ( -d $staticroot ) {
-        make_path( $staticroot, { verbose => $opt{verbose} } );
-        do_static($staticroot);
-    }
-
-    ## Generate the front page
-    {
-        my $path = "exim-html-$version/doc/html/spec_html/" . ( $type eq 'filter' ? $type : 'index' ) . ".html";
-        print "Generating  : docroot:/$path\n" if ( $opt{verbose} );
-        transform( $xml, "$opt{tmpl}/doc/index.xsl", "$opt{docroot}/$path", $staticroot );
-    }
-
-    ## Generate a Table of Contents XML file
-    {
-        my $path =
-            "exim-html-$version/doc/html/spec_html/" . ( $type eq 'filter' ? 'filter_toc' : 'index_toc' ) . ".xml";
-        print "Generating  : docroot:/$path\n" if ( $opt{verbose} );
-        transform( $xml, "$opt{tmpl}/doc/toc.xsl", "$opt{docroot}/$path", $staticroot );
-    }
-
-    ## Generate the chapters
-    my $counter = 0;
-    my @chapters = map { $_->cloneNode(1) } $xml->findnodes('/book/chapter');
-    my( $chapter_title, $chapter_title_prev, $chapter_title_next );
-    foreach my $chapter (@chapters) {
-
-        ## Add a <chapter_id>N</chapter_id> node for the stylesheet to use
-        $chapter->appendTextChild( 'chapter_id', ++$counter );
-
-        ## Get the current and surrounding chapter titles
-        $chapter_title_prev = $chapter_title;
-        $chapter_title      = $chapter_title_next || $chapter->findvalue('title_uri');
-        $chapter_title_next = $chapters[$counter]->findvalue('title_uri') if $counter < int(@chapters);
-
-        ## Add previous/next/canonical urls for nav
-        {
-            $chapter->appendTextChild( 'prev_url',
-                  $counter == 1
-                ? $type eq 'filter'
-                        ? 'filter.html'
-                        : 'index.html'
-                : sprintf( '%sch-%s.html', $prepend_chapter, $chapter_title_prev ) );
-            $chapter->appendTextChild( 'this_url', sprintf( '%sch-%s.html', $prepend_chapter, $chapter_title ) );
-            $chapter->appendTextChild( 'next_url', sprintf( '%sch-%s.html', $prepend_chapter, $chapter_title_next ) )
-                unless int(@chapters) == $counter;
-            $chapter->appendTextChild( 'toc_url', ( $type eq 'filter' ? 'filter' : 'index' ) . '.html' );
-            $chapter->appendTextChild(
-                'canonical_url',
-                sprintf(
-                    'https://www.exim.org/exim-html-current/doc/html/spec_html/%sch-%s.html',
-                    $prepend_chapter, $chapter_title
-                )
-            );
-            if ( $version ne $opt{latest} ) {
-                $chapter->appendTextChild(
-                    'current_url',
-                    sprintf(
-                        '../../../../exim-html-current/doc/html/spec_html/%sch-%s.html',
-                        $prepend_chapter, $chapter_title
-                    )
-                );
-            }
-        }
-
-        ## Create an XML document from the chapter
-        my $doc = XML::LibXML::Document->createDocument( '1.0', 'UTF-8' );
-        $doc->setDocumentElement($chapter);
-
-        ## Transform the chapter into html
-        {
-            my $real_path = sprintf( 'exim-html-%s/doc/html/spec_html/%sch-%s.html', $version, $prepend_chapter, $chapter_title );
-            my $link_path = sprintf( 'exim-html-%s/doc/html/spec_html/%sch%02d.html',  $version, $prepend_chapter, $counter );
-            print "Generating  : docroot:/$real_path\n" if ( $opt{verbose} );
-            transform( $doc, "$opt{tmpl}/doc/chapter.xsl", "$opt{docroot}/$real_path", $staticroot );
-            # Making a relative symlink to a file in the same directory.
-            # Extract just the filename portion of $real_path.
-            my $real_file = basename($real_path);
-            print "Symlinking  : docroot:/$link_path to $real_file\n" if ( $opt{verbose} );
-            if ( -f "$opt{docroot}/$link_path" ) {
-               unlink("$opt{docroot}/$link_path") or die "failed removing $opt{docroot}/$link_path: $!";
-            }
-            symlink( "$real_file", "$opt{docroot}/$link_path" ) || die "symlink to $opt{docroot}/$link_path failed: $!";
-        }
-    }
-}
-
-# ------------------------------------------------------------------
-## Fixup xref tags
-sub xref_fixup {
-    my ( $xml, $prepend_chapter ) = @_;
-
-    my %index = ();
-
-    ## Add the "prepend_chapter" info
-    ( $xml->findnodes('/book') )[0]->appendTextChild( 'prepend_chapter', $prepend_chapter );
-
-    ## Iterate over each chapter
-    my $chapter_counter = 0;
-    foreach my $chapter ( $xml->findnodes('/book/chapter') ) {
-        ++$chapter_counter;
-
-        my $chapter_id = $chapter->getAttribute('id');
-        unless ($chapter_id) {    # synthesise missing id
-            $chapter_id = sprintf( 'chapter_noid_%04d', $chapter_counter );
-            $chapter->setAttribute( 'id', $chapter_id );
-        }
-        my $chapter_title = $chapter->findvalue('title');
-
-        ## Set title_uri so we can use eg ch-introduction.html instead of ch01.html
-        $chapter->appendTextChild( 'title_uri', title_to_uri($chapter_title) );
-
-        $index{$chapter_id} = { chapter_id => $chapter_counter, chapter_title => $chapter_title };
-
-        ## Iterate over each section
-        my $section_counter = 0;
-        foreach my $section ( $chapter->findnodes('section') ) {
-            ++$section_counter;
-
-            my $section_id = $section->getAttribute('id');
-            unless ($section_id) {    # synthesise missing id
-                $section_id = sprintf( 'section_noid_%04d_%04d', $chapter_counter, $section_counter );
-                $section->setAttribute( 'id', $section_id );
-            }
-            my $section_title = $section->findvalue('title');
-
-            $index{$section_id} = {
-                chapter_id    => $chapter_counter,
-                chapter_title => $chapter_title,
-                section_id    => $section_counter,
-                section_title => $section_title
-            };
-        }
-    }
-    ## Build indexes as new chapters
-    build_indexes( $xml, $prepend_chapter, \%index );
-
-    ## Replace all of the xrefs in the XML
-    foreach my $xref ( $xml->findnodes('//xref') ) {
-        my $linkend = $xref->getAttribute('linkend');
-        if ( exists $index{$linkend} ) {
-            $xref->setAttribute( 'chapter_id',    $index{$linkend}{'chapter_id'} );
-            $xref->setAttribute( 'chapter_title', $index{$linkend}{'chapter_title'} );
-            $xref->setAttribute( 'section_id', $index{$linkend}{'section_id'} ) if ( $index{$linkend}{'section_id'} );
-            $xref->setAttribute( 'section_title', $index{$linkend}{'section_title'} )
-                if ( $index{$linkend}{'section_title'} );
-            $xref->setAttribute( 'url',
-                sprintf( '%sch-%s.html', $prepend_chapter, title_to_uri($index{$linkend}{'chapter_title'}) )
-                    . ( $index{$linkend}{'section_id'} ? '#' . $linkend : '' ) );
-        }
-    }
-}
-
-# ------------------------------------------------------------------
-## Build indexes
-sub build_indexes {
-    my ( $xml, $prepend_chapter, $xref ) = @_;
-
-    my $index_hash = {};
-    my $current_id;
-    foreach my $node ( $xml->findnodes('//section | //chapter | //indexterm') ) {
-        if ( $node->nodeName eq 'indexterm' ) {
-            my $role      = $node->getAttribute('role') || 'concept';
-            my $primary   = $node->findvalue('child::primary');
-            my $first     = ( $primary =~ /^[A-Za-z]/ ) ? uc( substr( $primary, 0, 1 ) ) : '';  # first letter or marker
-            my $secondary = $node->findvalue('child::secondary') || '';
-            next unless ( $primary || $secondary );    # skip blank entries for now...
-            $index_hash->{$role}{$first}{$primary}{$secondary} ||= [];
-            push @{ $index_hash->{$role}{$first}{$primary}{$secondary} }, $current_id;
-        }
-        else {
-            $current_id = $node->getAttribute('id');
-        }
-    }
-
-    # now we build a set of new chapters with the index data in
-    my $book = ( $xml->findnodes('/book') )[0];
-    foreach my $role ( sort { $a cmp $b } keys %{$index_hash} ) {
-        my $chapter = XML::LibXML::Element->new('chapter');
-        $book->appendChild($chapter);
-        $chapter->setAttribute( 'id', join( '_', 'index', $role ) );
-        $chapter->setAttribute( 'class', 'index' );
-        $chapter->appendTextChild( 'title', ( ucfirst($role) . ' Index' ) );
-        $chapter->appendTextChild( 'title_uri', title_to_uri(ucfirst($role) . ' Index') );
-
-        foreach my $first ( sort { $a cmp $b } keys %{ $index_hash->{$role} } ) {
-            my $section = XML::LibXML::Element->new('section');
-            my $list    = XML::LibXML::Element->new('variablelist');
-            $chapter->appendChild($section);
-            $section->setAttribute( 'id', join( '_', 'index', $role, $first ) );
-            $section->setAttribute( 'class', 'index' );
-            $section->appendTextChild( 'title', $first ? $first : 'Symbols' );
-            $section->appendChild($list);
-            foreach my $primary ( sort { $a cmp $b } keys %{ $index_hash->{$role}{$first} } ) {
-                my $entry = XML::LibXML::Element->new('varlistentry');
-                my $item  = XML::LibXML::Element->new('listitem');
-                $list->appendChild($entry)->appendTextChild( 'term', $primary );
-                $entry->appendChild($item);
-                my $slist;
-                foreach my $secondary ( sort { $a cmp $b } keys %{ $index_hash->{$role}{$first}{$primary} } ) {
-                    my $para = XML::LibXML::Element->new('para');
-                    if ( $secondary eq '' ) {
-                        $item->appendChild($para);    # skip having extra layer of heirarchy
-                    }
-                    else {
-                        unless ($slist) {
-                            $slist = XML::LibXML::Element->new('variablelist');
-                            $item->appendChild($slist);
-                        }
-                        my $sentry = XML::LibXML::Element->new('varlistentry');
-                        my $sitem  = XML::LibXML::Element->new('listitem');
-                        $slist->appendChild($sentry)->appendTextChild( 'term', $secondary );
-                        $sentry->appendChild($sitem)->appendChild($para);
-                    }
-                    my $count = 0;
-                    foreach my $ref ( @{ $index_hash->{$role}{$first}{$primary}{$secondary} } ) {
-                        $para->appendText(', ')
-                            if ( $count++ );
-                        my $xrefel = XML::LibXML::Element->new('xref');
-                        $xrefel->setAttribute( linkend => $ref );
-                        $xrefel->setAttribute( longref => 1 );
-                        $para->appendChild($xrefel);
-                    }
-                }
-            }
-        }
-    }
-}
-
-# ------------------------------------------------------------------
-## Handle the transformation
-sub transform {
-    my ( $xml, $xsl_path, $out_path, $staticroot_abs ) = @_;
-
-    ## make sure $staticroot is set
-    $staticroot_abs ||= $opt{staticroot};
-
-    ## Build an empty XML structure if an undefined $xml was passed
-    unless ( defined $xml ) {
-        $xml = XML::LibXML::Document->createDocument( '1.0', 'UTF-8' );
-        $xml->setDocumentElement( $xml->createElement('content') );
-    }
-
-    ## Add the current version of Exim to the XML
-    $xml->documentElement()->appendTextChild( 'current_version', $opt{latest} );
-
-    ## Add the old versions of Exim to the XML
-    $xml->documentElement()->appendTextChild( 'old_versions', $_ ) foreach old_docs_versions();
-
-    ## Parse the ".xsl" file as XML
-    my $xsl = XML::LibXML->new()->parse_file($xsl_path) or die $!;
-
-    ## Generate a stylesheet from the ".xsl" XML.
-    my $stylesheet = XML::LibXSLT->new()->parse_stylesheet($xsl);
-
-    ## work out the static root relative to the target
-    my $target_dir = ( File::Spec->splitpath($out_path) )[1];
-    my $staticroot = File::Spec->abs2rel( $staticroot_abs, $target_dir );
-
-    ## Generate a doc from the XML transformed with the XSL
-    my $doc = $stylesheet->transform( $xml, staticroot => sprintf( "'%s'", $staticroot ) );
-
-    ## Make the containing directory if it doesn't exist
-    make_path( ( $out_path =~ /^(.+)\/.+$/ )[0], { verbose => $opt{verbose} } );
-
-    ## Write out the document
-    open my $out, '>', $out_path or die "Unable to write $out_path - $!";
-    print $out $stylesheet->output_as_bytes($doc);
-    close $out;
-}
-
-# ------------------------------------------------------------------
-## Takes a chapter title and fixes it up so it is suitable for use in a URI
-sub title_to_uri {
-    my $title = lc(shift);
-    $title =~ s/[^a-z0-9\s]+//gi; # Only allow spaces, numbers and letters
-    $title =~ s/\s+/_/g;          # Replace spaces with underscores so URLs are easier to copy about
-    return $title;
-}
-
-# ------------------------------------------------------------------
-## Look in the docroot for old versions of the documentation
-sub old_docs_versions {
-    if ( !exists $cache{old_docs_versions} ) {
-        my @versions;
-        foreach ( glob("$opt{docroot}/exim-html-*") ) {
-            push @versions, $1 if /-(\d+(?:\.\d+)?)$/ && $1 lt $opt{latest} && -d $_;
-        }
-        $cache{old_docs_versions} = [ reverse sort { $a cmp $b } @versions ];
-    }
-    return @{ $cache{old_docs_versions} };
-}
-
-# ------------------------------------------------------------------
-## error_help
-sub error_help {
-    my $msg = shift;
-
-    warn $msg;
-    pod2usage( -exitval => 1, -verbose => 0 );
-}
-
-# ------------------------------------------------------------------
-## Parse arguments
-sub parse_arguments {
-
-    my %opt = ( spec => [], filter => [], help => 0, man => 0, web => 0, minify => 1, verbose => 0, localstatic => 0 );
-    GetOptions(
-        \%opt,      'help|h!', 'man!',      'web!',    'spec=s{1,}', 'filter=s{1,}',
-        'latest=s', 'tmpl=s',  'docroot=s', 'minify!', 'verbose!',   'localstatic!'
-    ) || pod2usage( -exitval => 1, -verbose => 0 );
-
-    ## --help
-    pod2usage(0) if ( $opt{help} );
-    pod2usage( -verbose => 2 ) if ( $opt{man} );
-
-    ## --spec and --filter lists
-    foreach my $set (qw[spec filter]) {
-        $opt{$set} =
-            [ map { my $f = File::Spec->rel2abs($_); error_help( 'No such file: ' . $_ ) unless -f $f; $f }
-                @{ $opt{$set} } ];
-    }
-    ## --latest
-    error_help('Missing value for latest') unless ( exists( $opt{latest} ) && defined( $opt{latest} ) );
-    error_help('Invalid value for latest') unless $opt{latest} =~ /^\d+(?:\.\d+)*$/;
-
-    ## --tmpl and --docroot
-    foreach my $set (qw[tmpl docroot]) {
-        error_help( 'Missing value for ' . $set ) unless ( exists( $opt{$set} ) && defined( $opt{$set} ) );
-        my $f = File::Spec->rel2abs( $opt{$set} );
-        error_help( 'No such directory: ' . $opt{$set} ) unless -d $f;
-        $opt{$set} = $f;
-    }
-    error_help('Excess arguments') if ( scalar(@ARGV) );
-
-    error_help('Must include at least one of --web, --spec or --filter')
-        unless ( $opt{web} || scalar( @{ $opt{spec} || [] } ) || scalar( @{ $opt{filter} || [] } ) );
-
-    return %opt;
-}
-
-# ------------------------------------------------------------------
-1;
-
-__END__
-
-=head1 NAME
-
-gen.pl - Generate exim html documentation and website
-
-=head1 SYNOPSIS
-
-gen.pl [options]
-
- Options:
-   --help              display this help and exits
-   --man               displays man page
-   --spec file...      spec docbook/XML source files
-   --filter file...    filter docbook/XML source files
-   --web               Generate the general website pages
-   --latest VERSION    Required. Specify the latest stable version of Exim.
-   --tmpl PATH         Required. Path to the templates directory
-   --docroot PATH      Required. Path to the website document root
-   --[no-]minify       [Don't] minify CSS and Javascript
-   --localstatic       Makes the static files local to each doc ver
-
-=head1 OPTIONS
-
-=over 4
-
-=item B<--help>
-
-Display help and exits
-
-=item B<--man>
-
-Display man page
-
-=item B<--spec> I<file...>
-
-List of files that make up the specification documentation docbook/XML source
-files.
-
-=item B<--filter> I<file...>
-
-List of files that make up the filter documentation docbook/XML source files.
-
-=item B<--web>
-
-Generate the website from the template files.
-
-=item B<--latest> I<version>
-
-Specify the current exim version. This is used to create links to the current
-documentation.
-
-This option is I<required>
-
-=item B<--tmpl> I<directory>
-
-Specify the directory that the templates are kept in.
-
-This option is I<required>
-
-=item B<--docroot> I<directory>
-
-Specify the directory that the output should be generated into. This is the
-website C<docroot> directory.
-
-This option is I<required>
-
-=item B<--minify>
-
-If this option is set then both the CSS and Javascript files processed are
-minified using L<CSS::Minifier::XS> and L<JavaScript::Minifier::XS>
-respectively.
-
-This option is set by default - to disable it specify C<--no-minify>
-
-=item B<--localstatic>
-
-Makes the static files (CSS, images etc), local for each version of the
-documentation. This is more suitable for packaged HTML documentation.
-
-=back
-
-=head1 DESCRIPTION
-
-Generates the exim website and HTML documentation.
-
-=head1 EXAMPLE
-
-    script/gen.pl \
-      --web \
-      --spec docbook/*/spec.xml \
-      --filter  docbook/*/filter.xml \
-      --latest 4.72 \
-      --tmpl templates \
-      --docroot /tmp/website
-
-=head1 AUTHOR
-
-Mike Cardwell
-
-Nigel Metheringham <nigel@exim.org> - mostly broke the framework Mike produced.
-
-=head1 COPYRIGHT
-
-Copyright 2010-2012 Exim Maintainers. All rights reserved.
-
-=cut
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..9eedfe1e34b99b8d7f05222bdbba67afbcff4ed6
--- /dev/null
@@ -0,0 +1 @@
+gen
\ No newline at end of file