10 my $canonical_url = 'http://www.exim.org/';
13 my %opt = parse_arguments();
16 do_doc( 'spec', $_ ) foreach @{$opt{spec}||[]};
17 do_doc( 'filter', $_ ) foreach @{$opt{filter}||[]};
18 do_web( ) if exists $opt{web};
20 ## Add the exim-html-current symlink
21 print "Symlinking exim-html-current to exim-html-$opt{latest}\n";
22 symlink( "$opt{docroot}/exim-html-$opt{latest}", "$opt{docroot}/exim-html-current" );
24 ## Generate the website files
27 ## Make sure the template web directory exists
28 die "No such directory: $opt{tmpl}/web\n" unless -d "$opt{tmpl}/web";
30 ## Scan the web templates
32 my( $path ) = substr( $File::Find::name, length("$opt{tmpl}/web"), length($File::Find::name) ) =~ m#^/*(.*)$#;
34 if( -d "$opt{tmpl}/web/$path" ){
36 ## Create the directory in the doc root if it doesn't exist
37 if( ! -d "$opt{docroot}/$path" ){
38 mkdir( "$opt{docroot}/$path" ) or die "Unable to make $opt{docroot}/$path: $!\n";
43 ## Build HTML from XSL files and simply copy static files which have changed
44 if( $path =~ /(.+)\.xsl$/ ){
45 print "Generating : docroot:/$1.html\n";
46 transform( undef, "$opt{tmpl}/web/$path", "$opt{docroot}/$1.html" );
47 } elsif( -f "$opt{tmpl}/web/$path" ){
49 ## Skip if the file hasn't changed (mtime based)
50 return if -f "$opt{docroot}/$path" && (stat("$opt{tmpl}/web/$path"))[9] == (stat("$opt{docroot}/$path"))[9];
53 print "Copying to : docroot:/$path\n";
54 copy( "$opt{tmpl}/web/$path", "$opt{docroot}/$path" ) or die "$path: $!";
57 utime( time, (stat("$opt{tmpl}/web/$path"))[9], "$opt{docroot}/$path" );
64 ## Generate index/chapter files for a doc
66 my( $type, $xml_path ) = @_;
68 ## Read and validate the XML file
69 my $xml = XML::LibXML->new()->parse_file( $xml_path ) or die $!;
71 ## Get the version number
72 my $version = $xml->findvalue('/book/bookinfo/revhistory/revision/revnumber');
73 die "Unable to get version number\n" unless defined $version && $version =~ /^\d+(\.\d+)*$/;
75 ## Prepend chapter filenames?
76 my $prepend_chapter = $type eq 'filter' ? 'filter_' : '';
78 ## Add the canonical url for this document
79 $xml->documentElement()->appendTextChild('canonical_url',"${canonical_url}exim-html-current/doc/html/spec_html/".($type eq 'spec'?'index':'filter').".html");
82 xref_fixup( $xml, $prepend_chapter );
84 ## Generate the front page
86 my $path = "exim-html-$version/doc/html/spec_html/".($type eq 'filter'?$type:'index').".html";
87 print "Generating : docroot:/$path\n";
89 "$opt{tmpl}/doc/index.xsl",
90 "$opt{docroot}/$path",
94 ## Generate a Table of Contents XML file
96 my $path = "exim-html-$version/doc/html/spec_html/".($type eq 'filter'?'filter_toc':'index_toc').".xml";
97 print "Generating : docroot:/$path\n";
99 "$opt{tmpl}/doc/toc.xsl",
100 "$opt{docroot}/$path",
104 ## Generate the chapters
106 foreach my $chapter ( map {$_->cloneNode(1)} $xml->findnodes('/book/chapter') ){
108 ## Add a <chapter_id>N</chapter_id> node for the stylesheet to use
109 $chapter->appendTextChild( 'chapter_id', ++$counter );
111 ## Add previous/next/canonical urls for nav
113 $chapter->appendTextChild( 'prev_url',
118 : sprintf('%sch%02d.html',$prepend_chapter,$counter-1)
120 $chapter->appendTextChild( 'next_url', sprintf('%sch%02d.html',$prepend_chapter,$counter+1) );
121 $chapter->appendTextChild( 'canonical_url', sprintf('http://www.exim.org/exim-html-current/doc/html/spec_html/%sch%02d.html',$prepend_chapter,$counter) );
124 ## Create an XML document from the chapter
125 my $doc = XML::LibXML::Document->createDocument( '1.0', 'UTF-8' );
126 $doc->setDocumentElement( $chapter );
128 ## Transform the chapter into html
130 my $path = sprintf('exim-html-%s/doc/html/spec_html/%sch%02d.html', $version, $prepend_chapter, $counter );
131 print "Generating : docroot:/$path\n";
133 "$opt{tmpl}/doc/chapter.xsl",
134 "$opt{docroot}/$path",
142 my( $xml, $prepend_chapter ) = @_;
146 ## Add the "prepend_chapter" info
147 ($xml->findnodes('/book'))[0]->appendTextChild( 'prepend_chapter', $prepend_chapter );
149 ## Iterate over each chapter
150 my $chapter_counter = 0;
151 foreach my $chapter ( $xml->findnodes('/book/chapter') ){
154 my $chapter_id = $chapter->getAttribute('id');
155 my $chapter_title = $chapter->findvalue('title');
157 $index{$chapter_id} = { chapter_id => $chapter_counter, chapter_title => $chapter_title };
159 ## Iterate over each section
160 my $section_counter = 0;
161 foreach my $section ( $chapter->findnodes('section') ){
164 my $section_id = $section->getAttribute('id');
165 my $section_title = $section->findvalue('title');
167 $index{$section_id} = { chapter_id => $chapter_counter, chapter_title => $chapter_title, section_id => $section_counter };
171 ## Replace all of the xrefs in the XML
172 foreach my $xref ( $xml->findnodes('//xref') ){
173 my $linkend = $xref->getAttribute('linkend');
174 if( exists $index{$linkend} ){
175 $xref->setAttribute( 'chapter_id', $index{$linkend}{'chapter_id'} );
176 $xref->setAttribute( 'chapter_title', $index{$linkend}{'chapter_title'} );
177 $xref->setAttribute( 'section_id', $index{$linkend}{'section_id'} ) if $index{$linkend}{'section_id'};
178 $xref->setAttribute( 'url', sprintf('%sch%02d.html',$prepend_chapter, $index{$linkend}{'chapter_id'}).($index{$linkend}{'section_id'}?'#'.$linkend:'') );
183 ## Handle the transformation
185 my( $xml, $xsl_path, $out_path ) = @_;
187 ## Build an empty XML structure if an undefined $xml was passed
188 unless( defined $xml ){
189 $xml = XML::LibXML::Document->createDocument( '1.0', 'UTF-8' );
190 $xml->setDocumentElement( $xml->createElement('content') );
193 ## Add the current version of Exim to the XML
194 $xml->documentElement()->appendTextChild( 'current_version', $opt{latest} );
196 ## Parse the ".xsl" file as XML
197 my $xsl = XML::LibXML->new()->parse_file( $xsl_path ) or die $!;
199 ## Generate a stylesheet from the ".xsl" XML.
200 my $stylesheet = XML::LibXSLT->new()->parse_stylesheet( $xsl );
202 ## Generate a doc from the XML transformed with the XSL
203 my $doc = $stylesheet->transform( $xml );
205 ## Make the containing directory if it doesn't exist
206 mkdirp( ($out_path =~ /^(.+)\/.+$/)[0] );
208 ## Write out the document
209 open my $out, '>', $out_path or die $!;
210 print $out $stylesheet->output_as_bytes( $doc );
219 foreach( split(/\//, $path) ){
221 my $make = join('/',@parts);
222 next unless length($make);
224 mkdir( $make ) or die "Unable to mkdir $make: $!\n";
229 sub parse_arguments {
233 help(0) if int(@ARGV) == 0 || grep(/^--help|-h$/,@ARGV);
235 my @collection = @ARGV;
236 while( @collection ){
237 my $key = shift @collection;
239 if( $key eq '--web' ){
243 } elsif( $key =~ /^--(spec|filter)$/ ){
245 ## --spec and --filter
247 while( $continue && @collection ){
248 my $value = shift @collection;
250 if( $value =~ /^--/ ){
251 unshift @collection, $value;
254 $value = File::Spec->rel2abs( $value );
255 help( 1, 'No such file: '.$value ) unless -f $value;
256 push @{$opt{$1}}, $value unless grep( $_ eq $value, @{$opt{$1}} );
259 help( 1, 'Missing value for '.$key ) unless exists $opt{$1};
260 } elsif( $key eq '--latest' ){
263 my $value = shift @collection;
264 help( 1, 'Missing value for '.$key ) unless defined $value;
265 help( 1, 'Invalid value for '.$key ) unless $value =~ /^\d+(?:\.\d+)*$/;
266 $opt{latest} = $value;
267 } elsif( $key =~ /^--(tmpl|docroot)$/ ){
269 ## --tmpl and --docroot
270 my $value = shift @collection;
271 help( 1, 'Missing value for '.$key ) unless defined $value;
272 $value = File::Spec->rel2abs( $value );
273 help( 1, 'No such directory: '.$value ) unless -d $value;
277 help( 1, 'Bad argument: '.$key );
281 help( 1, 'Must include at least one of --web, --spec or --filter' ) unless exists $opt{web} || exists $opt{spec} || exists $opt{filter};
282 foreach(qw( latest tmpl docroot )){
283 help( 1, 'Missing argument: --'.$_ ) unless exists $opt{$_};
291 my( $exit_code, $msg ) = @_;
293 print "$msg\n\n" if defined $msg;
297 --help or -h : Print this help information and then exit
299 --web : Generate the general website pages
300 --spec PATH : Generate the spec pages. PATH is the path to the spec.xml
301 --filter PATH : Generate the filter pages. PATH is the path to the filter.xml
303 One or more of the above three options are required. --spec and --filter can
304 take multiple values to generate different sets of documentation for
305 different versions at the same time.
307 --latest VERSION : Required. Specify the latest stable version of Exim.
308 --tmpl PATH : Required. Path to the templates directory
309 --docroot PATH : Required. Path to the website document root
311 If CSS::Minifier::XS is installed, then CSS will be minified.
312 If JavaScript::Minifier::XS is installed, then JavaScript will be minified.
316 ./gen.pl --latest 4.72
318 --spec spec.xml 4.71/spec.xml
319 --filter filter.xml 4.71/filter.xml
320 --tmpl ~/www/templates
321 --docroot ~/www/docroot