The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w

###############################################################################
#
# pod2epub - A utility to convert Pod to an ePub eBook.
#
# Copyright 2010-2012, John McNamara, jmcnamara@cpan.org
#
# Documentation after __END__
#

use strict;
use App::Pod2Epub;
use Getopt::Long;
use Pod::Usage;
use EBook::EPUB;
use Data::UUID;
use File::Temp 'tempfile';


# Set some defaults for the commandline options.
my $author         = 'Perl Author';
my $title          = 'Pod Document';
my $language       = 'en';
my $out_filename   = '';
my $css_filename   = '';
my $cover_filename = '';
my $user_css       = 0;
my $xhtml_only     = 0;
my $version        = 0;
my $man            = 0;
my $help           = 0;
my $whine          = 1;
my $errata         = 1;
my $complain       = 0;


main();

###############################################################################
#
# main()
#
# The main application function.
#
sub main {

    my $in_fh;
    my $in_filename;
    my $xhtml_fh;
    my $xhtml_filename;

    # Read any commandline options.
    get_commandline_options();

    # Create the pod2epub parser.
    my $parser = App::Pod2Epub->new();

    # Set the input filehandle.
    if ( $in_filename = $ARGV[0] ) {
        open $in_fh, '<', $in_filename
          or die "Couldn't open $in_filename: $!\n";
    }
    else {
        $in_fh = *STDIN;
    }


    if ( $xhtml_only ) {

        # Output the XHTML doc only. This is mainly use for debugging
        # the XHTML output.
        if ( $out_filename ) {
            open $xhtml_fh, '>', $out_filename
              or die "Couldn't open $out_filename: $!\n";
        }
    }
    else {

        # Write the XHTML to a temp file. This will be added to the ePub doc.
        ( $xhtml_fh, $xhtml_filename ) = tempfile();
    }

    # Parse the Pod doc and output the XHTML file.
    $parser->output_fh( $xhtml_fh );
    $parser->parse_file( $in_fh );

    close $xhtml_fh;
    close $in_fh;

    # If using the --xhtml-only option exit before creating an ePub doc.
    return if $xhtml_only;


    # Create EPUB object
    my $epub = EBook::EPUB->new();

    # Set the ePub metadata.
    $epub->add_title( $title );
    $epub->add_author( $author );
    $epub->add_language( $language );


    # Add user defined cover image if it supplied.
    add_cover( $epub, $cover_filename ) if $cover_filename;

    # Add the Dublin Core UUID.
    my $du = Data::UUID->new();
    my $uuid = $du->create_from_name_str( NameSpace_URL, 'www.perl.org' );

    {

        # Ignore overridden UUID warning form EBook::EPUB.
        local $SIG{__WARN__} = sub { };
        $epub->add_identifier( "urn:uuid:$uuid" );
    }

    # Add some other metadata to the OPF file.
    $epub->add_meta_item( 'Pod2epub version',    $App::Pod2Epub::VERSION );
    $epub->add_meta_item( 'EBook::EPUB version', $EBook::EPUB::VERSION );


    # Get the user supplied or default css file name.
    $css_filename = get_css_file( $css_filename );


    # Add package content: stylesheet, font, xhtml
    $epub->copy_stylesheet( $css_filename, 'styles/style.css' );
    $epub->copy_xhtml( $xhtml_filename, 'text/main.xhtml', linear => 'no' );


    # Add Pod headings to table of contents.
    set_table_of_contents( $epub, $parser->{'to_index'} );


    # Generate the ePub eBook.
    $epub->pack_zip( $out_filename );

    # Clean up any temporary files.
    unlink $xhtml_filename;
    unlink $css_filename if !$user_css;

}


###############################################################################
#
# set_table_of_contents()
#
# Add the Pod headings to the NCX <navMap> table of contents.
#
sub set_table_of_contents {

    my $epub         = shift;
    my $pod_headings = shift;

    my $play_order   = 1;
    my $navpoint_obj = $epub;


    for my $heading ( @$pod_headings ) {

        my $heading_level = $heading->[0];
        my $section       = $heading->[1];
        my $label         = $heading->[2];
        my $content       = 'text/main.xhtml';

        # Only deal with head1 and head2 headings.
        next if $heading_level > 2;

        # Add the pod section to the NCX data, Except for the root heading.
        $content .= '#' . $section if $play_order > 1;

        my %options = (
            content    => $content,
            id         => 'navPoint-' . $play_order,
            play_order => $play_order,
            label      => $label,
        );

        $play_order++;

        # Add the navpoints at the correct nested level.
        if ( $heading_level == 1 ) {
            $navpoint_obj = $epub->add_navpoint( %options );
        }
        else {
            $navpoint_obj->add_navpoint( %options );
        }
    }
}

###############################################################################
#
# get_css_file()
#
# Get the user supplied or default css file name.
#
sub get_css_file {

    my $css_filename = shift;
    my $css_fh;

    # If the user supplied the css filename check if it exists.
    if ( $css_filename ) {
        if ( -e $css_filename ) {
            $user_css = 1;
            return $css_filename;
        }
        else {
            warn "CSS file $css_filename not found.\n";
        }
    }

    # If the css file doesn't exist or wasted supplied create a default.
    ( $css_fh, $css_filename ) = tempfile();

    print $css_fh "h1         { font-size: 110%; }\n";
    print $css_fh "h2, h3, h4 { font-size: 100%; }\n";

    close $css_fh;

    return $css_filename;
}


###############################################################################
#
# add_cover()
#
# Add a cover image to the eBook. Add cover metadata for iBooks and add an
# additional cover page for other eBook readers.
#
sub add_cover {

    my $epub           = shift;
    my $cover_filename = shift;


    # Check if the cover image exists.
    if ( !-e $cover_filename ) {
        warn "Cover image $cover_filename not found.\n";
        return undef;
    }

    # Add cover metadata for iBooks.
    my $cover_id = $epub->copy_image( $cover_filename, 'images/cover.jpg' );
    $epub->add_meta_item( 'cover', $cover_id );

    # Add an additional cover page for other eBook readers.
    my $cover_xhtml =
        qq[<?xml version="1.0" encoding="UTF-8"?>\n]
      . qq[<!DOCTYPE html\n]
      . qq[     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n]
      . qq[    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n\n]
      . qq[<html xmlns="http://www.w3.org/1999/xhtml">\n]
      . qq[<head>\n]
      . qq[<title></title>\n]
      . qq[<meta http-equiv="Content-Type" ]
      . qq[content="text/html; charset=iso-8859-1"/>\n]
      . qq[<style type="text/css"> img { max-width: 100%; }</style>\n]
      . qq[</head>\n]
      . qq[<body>\n]
      . qq[    <img alt="" src="../images/cover.jpg" />\n]
      . qq[</body>\n]
      . qq[</html>\n\n];

    # Crete a temp file for the cover xhtml.
    my ( $tmp_fh, $tmp_filename ) = tempfile();

    print $tmp_fh $cover_xhtml;
    close $tmp_fh;

    # Add the cover page to the ePub doc.
    $epub->copy_xhtml( $tmp_filename, 'text/cover.xhtml', linear => 'no' );

    # Add the cover to the OPF guide.
    my $guide_options = {
        type  => 'cover',
        href  => 'text/cover.xhtml',
        title => 'Cover',
    };

    $epub->guide->add_reference( $guide_options );

    # Cleanup the temp file.
    unlink $cover_xhtml;

    return $cover_id;
}


###############################################################################
#
# get_commandline_options()
#
# Parse options from the commandline using GetOptions.
#
sub get_commandline_options {

    # Use GetOptions for the commandline parsing.
    GetOptions(
        'a|author=s'     => \$author,
        't|title=s'      => \$title,
        'l|language=s'   => \$language,
        'o|outfile=s'    => \$out_filename,
        's|stylesheet=s' => \$css_filename,
        'c|cover=s'      => \$cover_filename,
        'v|version'      => \$version,
        'x|xhtml-only'   => \$xhtml_only,
        'h|help'         => \$help,
        'man'            => \$man,
        'whine!'         => \$whine,
        'errata!'        => \$errata,
        'complain!'      => \$complain,
    ) or pod2usage( 2 );

    # Print the application version and exit.
    die "pod2epub, version $App::Pod2Epub::VERSION\n" if $version;

    # Print the application usage or manpage.
    pod2usage( 1 ) if $help;
    pod2usage( -verbose => 2 ) if $man;

    # Require the outfile argument.
    pod2usage( 1 ) if !$out_filename;

    # Print usage when there are no options used.
    pod2usage() if @ARGV == 0 && -t STDIN;
}


__END__

=pod

=head1 NAME

pod2epub - A utility to convert Pod to an ePub eBook.

=head1 SYNOPSIS

pod2epub [options] podfile --outfile file.epub

    Main options (see manpage for all options):
        -o --outfile    Name of output ePub file.
        -t --title      Add ePub book title.
        -a --author     Add ePub book author.
        -s --stylesheet Change the default stylesheet.
        -c --cover      Add cover image.
        -h --help       Print a brief help message.
        -m --man        Print the full manpage.

    Example:
        pod2epub some_module.pm -o some_module.epub

=head1 DESCRIPTION

This program is used for converting Pod documents to ePub eBooks. The output eBook can be read on a variety of hardware and software eBook readers.

Pod is Perl's I<Plain Old Documentation> format, see L<http://perldoc.perl.org/perlpod.html>. EPub is an eBook format, see L<http://en.wikipedia.org/wiki/Epub>.


=head1 OPTIONS

=over 4

=item B<podfile>

The input file containing Pod documentation.

=item B<-o or --outfile>

The output ePub file. This is a required argument.

=item B<-s or --stylesheet>

Specify the CSS stylesheet that the ePub eBook will use. If none is supplied the following minimal stylesheet is used:

    h1         { font-size: 110%; }
    h2, h3, h4 { font-size: 100%; }

=item B<-c or --cover>

Add an image for use as a book cover.

=item B<-t or --title>

Add the ePub book title. Defaults to "Pod Document". A future release will parse this from the Pod document where possible.

=item B<-a or --author>

Add the ePub book author. Defaults to "Perl Author". A future release will parse this from the Pod document where possible.

=item B<-h or --help>

Print a brief help message and exit.

=back

=head1 SECONDARY OPTIONS

=over 4

=item B<-x or --xhtml-only>

Output the Pod documentation in XHTML format without converting it ePub. This can be useful for debugging XHTML errors.

=item B<-l or --language>

Add the ePub book language. Defaults to "en".

=item B<-m or --man>

Prints the manpage and exit.

=item B<-v or --version>

Prints the version of the C<pod2epub> application.

=item B<-e or --errata>

Generate a "Pod Errors" section at the end of the document if there are Pod errors. This is the default. To turn this feature off use C<--no-errata> or C<-no-e>. Equivalent to the L<Pod::Simple> C<no_errata_section()> method.

=back


=head1 TODO

=over 4

=item *

Parse the document title from the Pod doc, where possible.

=item *

Parse the author name from the Pod doc, where possible.

=item *

Add an XHTML validation option.

=item *

Add controls and defaults for internal links.

=back

=head1 AUTHOR

John McNamara jmcnamara@cpan.org


=head1 COPYRIGHT

© MMXII, John McNamara.

All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself.

=cut