#!/usr/bin/perl use warnings; use strict; use Getopt::Long qw/GetOptions :config gnu_getopt/; use POSIX qw/strftime/; use Cwd qw/getcwd/; use File::Slurp qw/read_file/; use List::Util qw/first/; use File::Copy qw/move copy/; use File::Spec (); use Test::Pod; use OODoc (); # In any case, we want modules in this dir to prevail over installed # versions of the module. my $here = getcwd; unshift @INC, $here, "$here/lib"; #my $format_pod = 'pod3'; my $format_pod = 'pod2'; my $format_html = 'html'; my $tmpdir = $ENV{TMPDIR} || '/tmp'; my ($verbose, $test) = (0, 0); my ($rawdir, $distdir, $license); my ($workdir, $podtailfn, $pmheadfn, $readmefn, $extends); my ($firstyear, $email, $website, $skip_links); my ($html_templates, $html_output, $html_docroot, $html_package); my ($make_pod, $make_dist, $make_raw, $make_html) = (1, 1, 1, undef); Getopt::Long::Configure 'bundling'; GetOptions ( 'dist!' => \$make_dist , 'distdir|d=s' => \$distdir , 'email|e=s' => \$email , 'extents|x=s' => \$extends , 'firstyear|y=i' => \$firstyear , 'html!' => \$make_html , 'html-docroot=s' => \$html_docroot , 'html-output=s' => \$html_output , 'html-templates=s' => \$html_templates , 'html-package=s'=> \$html_package , 'license|l=s' => \$license , 'pod!' => \$make_pod , 'podtail' => \$podtailfn , 'pmhead' => \$pmheadfn , 'readme' => \$readmefn , 'raw!' => \$make_raw , 'rawdir|r=s' => \$rawdir , 'skip_links=s' => \$skip_links , 'test|t!' => \$test , 'verbose|v!' => \$verbose , 'website|u=s' => \$website , 'workdir|w=s' => \$workdir ) or die "ERROR: stopped"; my %licenses = ( artistic => <<__ARTISTIC, gpl => <<__GPL); This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See F __ARTISTIC This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details: F __GPL my $ooversion = $OODoc::VERSION || $ENV{OODOC_VERSION}; sub read_makefile($); sub parse_additional_dists(@); sub create_pod($$); sub create_html($$$$); sub create_dist($$); sub publish_dist($$); sub create_raw_dist($$); sub publish_raw_dist($$); sub create_html_dist($$); sub publish_html_dist($$); sub create_heads_and_tails($); sub create_readme($$$); sub skip_links(@); # # collect some project info # system "perl Makefile.PL" and die "ERROR: Cannot run Makefile.PL for basic information.\n"; my $makefile = read_makefile 'Makefile'; my $project = $makefile->{DISTNAME}; my $version = $makefile->{VERSION}; $firstyear ||= $makefile->{FIRST_YEAR}; $rawdir ||= $makefile->{RAWDIR} || $ENV{OODIST_RAWDIR} || '.'; $distdir ||= $makefile->{DISTDIR} || $ENV{OODIST_DISTDIR} || '.'; $license ||= $makefile->{LICENSE} || $ENV{OODIST_LICENSE} || 'artistic'; $email ||= $makefile->{EMAIL}; $website ||= $makefile->{WEBSITE}; $extends ||= $makefile->{EXTENDS} || '';; $podtailfn ||= $makefile->{PODTAIL}; $pmheadfn ||= $makefile->{PMHEAD}; $html_templates ||= $makefile->{HTML_TEMPLATES} || 'html'; $html_output ||= $makefile->{HTML_OUTPUT} || 'public_html'; $html_docroot ||= $makefile->{HTML_DOCROOT} || '/'; $html_package ||= $makefile->{HTML_PACKAGE}; $make_html = -d $html_templates unless defined $make_html; $workdir ||= "$tmpdir/$project"; my @extends = split /\:/, $extends; # make directories absolute, because we will chdir for($workdir, $rawdir, $distdir, $html_package, $html_templates, @extends) { $_ = File::Spec->rel2abs($_, $here); } print "*** Producing $project version $version\n" if $verbose; my $lictext = $licenses{$license} || ''; if(!$lictext && $license) { $lictext = read_file $license or die "ERROR: cannot read from licence file $license: $!\n"; } ### ### Start OODoc ### push @INC, map {"$_/lib"} @extends; my $doc = OODoc->new ( distribution => $project , version => $version # version of whole , verbose => ($verbose ? 2 : 0) ); # # Reading all the manual pages # This could be called more than once to combine different sets of # manual pages in different formats. # print "* Processing files of $project $version\n" if $verbose; -d $workdir or mkdir $workdir or die "ERROR: cannot create $workdir: $!\n"; my ($pmhead, $podtail) = create_heads_and_tails $makefile; $doc->processFiles ( workdir => $workdir , version => $version # version on each file , notice => $pmhead , skip_links => skip_links($makefile->{SKIP_LINKS}, $skip_links) ); parse_additional_dists @extends if $make_pod || $make_html; create_readme $doc, $readmefn, $workdir unless defined $readmefn && ! length $readmefn; create_pod $doc, $podtail if $make_pod; create_html $doc, $html_templates, $html_output, $html_docroot if $make_html; # Dist package chdir $workdir or die "ERROR: cannot go to my $workdir: $!"; my $temp_distfn = create_dist $distdir, $makefile if $make_dist; if(!$make_dist) {;} elsif($test) { print "* test mode: dist not published\n" if $verbose } else { publish_dist $distdir, $temp_distfn } # RAW package chdir $here or die "ERROR: cannot go back to $here; $!\n"; my $temp_rawfn = create_raw_dist $rawdir, $makefile if $make_raw; if(!$make_raw) { ; } elsif($test) { print "* test mode: raw not published\n" if $verbose } else { publish_raw_dist $rawdir, $temp_rawfn } # HTML package if($make_html && $html_package) { chdir $html_output or die "ERROR: cannot go to produced html $html_output\n"; my $temp_htmlfn = create_html_dist $html_package, $makefile; if($test) { print "* test mode: html not published\n" if $verbose } else { publish_html_dist $html_package, $temp_htmlfn } } print "* Ready\n" if $verbose; exit 0; # # Parse additional distributions # sub parse_additional_dists(@) { my @extras = @_; # Read all files which are in other modules, but contain information # which is required to produce the pod. foreach my $extra (@extras) { print "* Processing files in $extra\n" if $verbose; system "cd $extra && perl Makefile.PL" and die "ERROR: failed to collect from $extra\n"; my $other = read_makefile "$extra/Makefile"; my $dist = $other->{DISTNAME}; my $distv = $other->{VERSION}; print "Including files from $dist $distv\n"; $doc->processFiles ( workdir => undef , source => $extra , select => qr[^ lib/ .*? \.(pod|pm) $ ]x , version => $distv , distribution => $dist , skip_links => skip_links($other->{SKIP_LINKS}, $skip_links) ); } print "* Preparation phase\n" if $verbose; $doc->prepare; } # # Create pods # sub create_pod($$) { my ($doc, $podtail) = @_; print "* Creating POD files\n" if $verbose; $doc->create ( $format_pod , workdir => $workdir , select => sub {my $manual = shift; $manual->distribution eq $project} , append => $podtail ); } # # Create html # sub create_html($$$$) { my ($doc, $templates, $output, $url) = @_; print "* Creating HTML files in $output\n" if $verbose; $doc->create ( $format_html , workdir => $output , format_options => [ html_root => $url , html_meta_data => qq[] ] , manual_format => [] ); } sub create_heads_and_tails($) { my $makefile = shift; my $today = strftime "%B %d, %Y", localtime; my $year = strftime "%Y", localtime; if($firstyear) { $year = $firstyear =~ m/$year$/ ? $firstyear : $firstyear =~ m/\D$/ ? $firstyear.$year : "$firstyear-$year"; } my $changelog = first { m/^(?:change|contrib)/i } glob '*'; my $contrib = $changelog ? <<__CONTRIB : ''; For other contributors see $changelog. __CONTRIB my $web = $website ? " Website: F<$website>" : ''; my $author = $makefile->{AUTHOR} ? " by $makefile->{AUTHOR}" : ''; my $pmheadsrc = defined $pmheadfn ? read_file $pmheadfn : -f 'PMHEAD.txt' ? read_file 'PMHEAD.txt' : <<'__PM_HEAD'; Copyrights $year$author. ${contrib}See the manual pages for details on the licensing terms. Pod stripped from pm file by OODoc $ooversion. __PM_HEAD my $podtailsrc = defined $podtailfn ? read_file $podtailfn : -f 'PODTAIL.txt' ? read_file 'PODTAIL.txt' : <<'__POD_TAIL'; =head1 SEE ALSO This module is part of $project distribution version $version, built on $today.$web =head1 LICENSE Copyrights $year$author.$contrib $lictext __POD_TAIL my $pmhead = eval qq{"$pmheadsrc"}; die "ERROR in pmhead: $@" if $@; my $podtail = eval qq{"$podtailsrc"}; die "ERROR in podtail: $@" if $@; ($pmhead, $podtail); } sub create_readme($$$) { my ($doc, $readmefn, $workdir) = @_; my @toplevel = glob "$workdir/*"; my $readme = first { /\breadme$/i } @toplevel; return 1 if $readme; print "adding README\n" if $verbose; my $manifest = first { /\bmanifest$/i } @toplevel; open MANIFEST, '>>', $manifest or die "ERROR: cannot append to $manifest: $!\n"; print MANIFEST "README\n"; close MANIFEST; $readme = File::Spec->catfile($workdir, 'README'); if($readmefn) { # # user provided README # print "copying $readmefn as README\n" if $verbose; copy $readmefn, $readme or die "ERROR: cannot copy $readmefn to $readme: $!\n"; return 1; } # # Produce a README text # open README, '>', $readme or die "ERROR: cannot write to $readme: $!\n"; my $date = localtime; print README <<__README; === README for $project version $version = Generated on $date by OODoc $ooversion There are various ways to install this module: (1) if you have a command-line, you can do: perl -MCPAN -e 'install ' (2) if you use Windows, have a look at http://ppm.activestate.com/ (3) if you have downloaded this module manually (as root/administrator) gzip -d $project-$version.tar.gz tar -xf $project-$version.tar cd $project-$version perl Makefile.PL make # optional make test # optional make install For usage, see the included manual-pages or http://search.cpan.org/dist/$project-$version/ Please report problems to http://rt.cpan.org/Dist/Display.html?Queue=$project __README close README; 1; } # # Create a distribution # sub create_dist($$) { my ($dest, $makefile) = @_; print "* Preparing test\n" if $verbose; system "perl Makefile.PL" and die "ERROR: perl Makefile.PL in $workdir failed: $!\n"; system "make clean >/dev/null" and die "ERROR: make clean in $workdir failed: $!\n"; move 'Makefile.old', 'Makefile' or die "ERROR: cannot reinstate Makefile: $!\n"; print "* Running make\n" if $verbose; system "make distdir >/dev/null" and die "ERROR: make in $workdir failed: $!\n"; if(-d 't' || -d 'tests') { print "* Running tests\n" if $verbose; system "make disttest" and die "ERROR: make test in $workdir failed: $!\n"; } else { print "* No tests to run\n" if $verbose; } my $distfile = $makefile->{DISTVNAME}. '.tar.gz'; print "* Building distribution in $distfile\n" if $verbose; unlink $distfile; system "make dist >/dev/null" and die "ERROR: make dist in $workdir failed: $!\n"; $distfile; } sub publish_dist($$) { my ($dest, $distfile) = @_; return if $dest eq $workdir; print "* Distributed package in $dest/$distfile\n" if $verbose; -f $distfile or die "ERROR: cannot find produced $distfile"; -d $dest or mkdir $dest or die "ERROR: cannot create $dest: $!\n"; move $distfile, $dest or die "ERROR: cannot move $distfile to $dest: $!\n"; } # # RAW # sub create_raw_dist($$) { my ($dest, $makefile) = @_; my $rawname = $makefile->{DISTVNAME}.'-raw'; my $rawfile = $rawname. '.tar.gz'; print "* Building raw package $rawfile\n" if $verbose; unlink $rawfile; my %manifests; foreach my $man (glob "MANIFEST*") { foreach (read_file $man) { s/\s{3,}.*$//; next if m/^#/; next unless length; chomp; $manifests{$_}++; } } my @manifests = map { "$rawname/$_" } sort keys %manifests; symlink('.', $rawname) || readlink $rawname eq '.' or die "ERROR: cannot create temp symlink $rawname: $!\n"; local $" = ' '; system "tar czf $rawfile @manifests" and die "ERROR: cannot produce $rawfile with tar\n"; unlink $rawname; $rawfile; } sub publish_raw_dist($$) { my ($dest, $rawfile) = @_; return if $dest eq $html_output; -d $dest or mkdir $dest or die "ERROR: cannot create $dest: $!\n"; print "* Raw package to $dest/$rawfile\n" if $verbose; move $rawfile, $dest or die "ERROR: cannot move $rawfile to $dest: $!\n"; } # # HTMLPKG # sub create_html_dist($$) { my ($dest, $makefile) = @_; my $htmlfile = $makefile->{DISTVNAME}.'-html.tar.gz'; print "* Building html package $htmlfile\n" if $verbose; unlink $htmlfile; local $" = ' '; system "tar czf $htmlfile *" and die "ERROR: cannot produce $htmlfile with tar\n"; $htmlfile; } sub publish_html_dist($$) { my ($dest, $htmlfile) = @_; return if $dest eq $html_output; -d $dest or mkdir($dest) or die "ERROR: cannot create $dest: $!\n"; print "* HTML package to $dest/$htmlfile\n" if $verbose; move $htmlfile, $dest or die "ERROR: cannot move $htmlfile to $dest: $!\n"; } # # read_makefile MAKEFILE # Collect values of variable defined in the specified MAKEFILE, which was # produced by "perl Makefile.PL" # sub read_makefile($) { my $makefile = shift; open MAKEFILE, '<', $makefile or die "ERROR: cannot open produced Makefile: $makefile"; my %makefile; while( ) { $_ .= while !eof MAKEFILE && s/\\$//; # continuations s/\n\t*/ /g; $makefile{$1} = $2 if m/^([A-Z_][A-Z\d_]+)\s*\=\s*(.*?)\s*$/; if(m/^#\s+([A-Z_][A-Z\d_]+)\s*\=>\s*(.*?)\s*$/) { # important information which ended-up in comments ;( my ($key, $v) = ($1, $2); $v =~ s/q\[([^\]]*)\]/$1/g; # remove q[] $makefile{$key} = $v; } } close MAKEFILE; \%makefile; } # # skip_links STRINGS # Split list of package names to avoid # sub skip_links(@) { my @links = map { defined $_ ? split(/[\, ]/, $_) : () } @_; \@links; } __END__ =head1 NAME oodist - create perl distributions with OODoc =head1 SYNOPSIS cd oodist [OPTIONS] OPTION: DEFAULT: --pod or --nopod produce pod: yes --dist or --nodist produce distribution: yes --html or --nohtml produce html: yes if templates --raw or --noraw produce package with raw files: yes OPTIONS general: --distdir | -d makefile:DISTDIR || $ENV{OODOC_DISTDIR} || --extends | -x empty --project | -p makefile:DISTNAME --rawdir | -r makefile:RAWDIR || $ENV{OODOC_RAWDIR} || --readme constructured --workdir | -w /tmp/ --verbose | -v true when specified OPTIONS for parsers: --skip_links makefile:SKIP_LINKS OPTIONS for POD: --email | -e makefile:EMAIL --firstyear| -y makefile:FIRST_YEAR --license | -l makefile:LICENSE || $ENV{OODOC_LICENSE} || 'artistic' --pmhead | -h makefile:PMHEAD || 'PMHEAD.txt' || constructed --podtail | -t makefile:PODTAIL || 'PODTAIL.txt' || constructed --website | -u makefile:WEBSITE OPTIONS for HTML: --html-templates makefile:HTML_TEMPLATES || 'html' --html-output makefile:HTML_OUTPUT || 'public_html' --html-docroot makefile:HTML_DOCROOT || '/' --html-package makefile:HTML_PACKAGE =head1 DESCRIPTION The C script is currently only usable on UNIX in combination with Makefile.PL. It is a smart wrapper around the OODoc module, to avoid start work to produce real POD for modules which use OODoc's POD extensions. HTML is not (yet) supported. =head2 Configuring All OPTIONS can be specified on the command-line, but you do not want to specify them explicitly each time you produce a new distribution for your product. The options come in two kinds: those which are environment independent and those which are. The first group can be set via the Makefile.PL, the second can be set using environment variables as well. Example: add to the end of your C sub MY::postamble { <<'__POSTAMBLE' } FIRST_YEAR = 2001 WEBSITE = http://perl.overmeer.net/oodoc EMAIL = oodoc@overmeer.net __POSTAMBLE =head2 Main options The process is controlled by four main options. All options are by default C. =over 4 =item . --pod or --nopod Produce pod files in the working directory and in the distribution. =item . --dist or --nodist Create a distribution, containing all files from the MANIFEST plus produced files. =item . --html or --nohtml Create html manual pages. The C<--html-templates> options must point to an existing directory (defaults to the C sub-directory). =item . --raw or --noraw Create a package which contains the files which are needed to produce the distribution: the pm files still including the oodoc markup. =back =head2 General options The other OPTIONS in detail =over 4 =item . --readme Copy the specified README file into the distribution, if there is no README yet. The name will be added to the MANIFEST. If the option is not specified, a simple file will be created. If this option is specified, but the filename is empty, then the README will not be produced. =item . --distdir | -d The location where the final file to be distributed is placed, by default in the source directory. Ignored when C<--test> is used. =item . --extends | -x A colon seperated list of directories which contains "raw oodoc" pm and pod files which are (in some cases) used to provide data for base-classes of this module. =item . --rawdir | --r The location where a I version of the distribution is made. The normal distribution contains expanded POD and stripped PM files. For that reason, you can not use it a backup for your files. If you have co-workers on the module, you may wish to give them the originals. gnored when C<--test> is used. =item . --test | -t Run in test mode: the raw and real distributions are produced, but not moved to their final location. You will end-up with these archives in the work-directory (see C<--workdir>). =item . --verbose | --v Shows what happens during the process. =item . --workdir | -w The processing will take place in a seperate directory: the stripped pm's and produced pod files will end-up there. If not provided, that directory will be named after the project, and located in C<$ENV{TMPDIR}>, which defaults to C. For instance C =back =head2 Options for parsers =over 4 =item . --skip_links A blank or comma separated list of packages which will have a manual page, but cannot be loaded under their name. Sub-packages are excluded as well. For instance, XML::LibXML has many manual-pages without a package with the same name. =back =head2 Options to produce POD =over 4 =item . --email | -e The email address which can be used to contact the authors. Used in the automatic podtail. =item . --firstyear| -y The first year that a release for this software was made. Used in the automatic podtail. The specified string can be more complex than a simple year, for instance C<1998-2000,2003>. The last year will be automatically added like C<-2006>, which results in C<1998-2000,2003-2006>. When the current year is detected at the end of the string, the year will not be added again. =item . --license | -l ['gpl'|'artistic'|filename] The lisense option is a special case: it can contain either the strings C or C, or a filename which is used to read the actual license text from. The default is C =item . --pmhead Each of the stripped C files will have content of the file added at the top. Each line will get a comment '# ' marker before it. If not specified, a short notice will be produced automatically. Implicitly, if a file C exists, that will be used. variables found in the text will be filled-in. =item . --podtail Normally, a trailing text is automatically produced, based on all kinds of facts. However, you can specify your own file. If exists, the content is read from a file named C. After reading the file, variables will be filled in: scalars like C<$version>, C<$project>, C<$website> etc are to your disposal. =item . --website | -u Where the HTML documentation related to this module is publicly visible. =back =head2 Options to produce HTML =over 4 =item . --html-docroot This (usually relative) URL is prepended to all absolute links in the HTML output. =item . --html-output The directory where the produced HTML pages are written to. =item . --html-templates The directory which contains the templates for HTML pages which are used to construct the html version of the manual-pages. =item . --html-package When specified, the html-output directory will get packaged into a a tar achive in this specified directory. =back