#!/usr/bin/env perl

#use utf8;
#use encoding 'utf8';
use warnings;
use strict;
use Carp;
use version;
use Getopt::Long;
use Pod::Usage;
use Publican;
use Publican::CreateBook;
use Publican::CreateBrand;
use Publican::Builder;
use Publican::XmlClean;
use Publican::TreeView;
use File::Path;
use File::Copy::Recursive qw(dircopy rcopy);
use File::Find::Rule;
use File::pushd;
use Archive::Tar;
use Term::ANSIColor qw(:constants);
use Cwd qw(abs_path);

#binmode STDOUT, ":utf8";

my $VERSION      = version->new($Publican::VERSION);
my $LANG_PATTERN = q|__LANG__|;
## TODO Consider making this a parameter
my $IMPORT_SCRIPT = '../common/cvs-import.sh';

=head1 NAME

publican - a DocBook XML publishing tool.

=head1 VERSION

This document describes publican version 1.0

=head1 SYNOPSIS

publican <command options>

publican <action> <action options>

Command Options

    --help 		Display help message
    --man		Display the man page
    --help_actions	Display a list of valid actions
    -v			Display the version of Publican

Run: 'publican <action> --help' for details on action usage

Valid actions are:

    build         Transform XML to other formats (pdf, html, html-single, etc)
    clean         Remove all temporary files and directories
    clean_ids     Run clean ids for source XML
    cleanset      Remove local copies of remote set books
    create        Create a new book, set, or article
    create_brand  Create a new brand
    help_config   Display help text for the configuration file
    installbrand  Install a brand to the supplied location
    lang_stats    report PO statistics
    old2new       Create a publican.cfg file from the Makefile of an old book, set, or article
    package       Package a language for shipping
    print_banned  Print a list of banned DocBook tags
    print_known   Print a list of QA'd DocBook tags
    print_unused  Print a list of unused XML files
    printtree     Print a tree of the xi:includes
    update_po     Update the PO files
    update_pot    Update the POT files

=head1 DESCRIPTION

Publican is a DocBook publication system, not just a DocBook processing tool. As well as ensuring your DocBook XML is valid, publican works to ensure your XML is up to publishable standard.

=cut

### Code goes here
my $man          = 0;
my $help         = 0;
my $action       = "";
my $help_actions = 0;
my $show_version = 0;

#Getopt::Long::Configure("debug");

# Global options
my %opts = (
    'help'         => \$help,
    'man'          => \$man,
    'v'            => \$show_version,
    'help_actions' => \$help_actions,
);

#Action options
my %options = (
    'help'            => maketext('Display help message'),
    'man'             => maketext('Display full man page'),
    'v'               => maketext('Display the version of Publican'),
    'config=s'        => maketext('Use a nonstandard config file'),
    'binary'          => maketext('Build binary rpm when running package'),
    'brew'            => maketext('Push SRPM to brew'),
    'scratch'         => maketext('Use scratch instead of tag build'),
    'wait'            => maketext('Wait for brew to finish building'),
    'common_config=s' => maketext('Override path to Common_Config directory'),
    'common_content=s' =>
        maketext('Override path to Common_Content directory'),
    'formats=s' => maketext(
        'Comma-separated list of formats, for example: html,pdf,html-single,html-desktop,txt,epub'
    ),
    'langs=s' => maketext(
        'Comma-separated list of languages, for example: en-US,de-DE,all'),
    'embedtoc' =>
        maketext('Embed the web site TOC object in the generated HTML'),
    'name=s'    => maketext('The name of the book, article, set, or brand'),
    'version=s' => maketext('The version of the product'),
    'edition=s' => maketext('The edition of the book, article, or set'),
    'product=s' => maketext('The name of the product'),
    'brand=s'   => maketext('The brand to use'),
    'lang=s'    => maketext('The language the XML will be written in'),
    'type=s'    => maketext('The type (book, article, or set)'),
    'publish'   => maketext('Set up built content for publishing'),
    'desktop'   => maketext('Create desktop instead of web package'),
    'short_sighted' =>
        maketext('Create package without using version in package name'),
    'path=s'          => maketext('/path/to/install/to'),
    'distributed_set' => maketext(
        qq|This flag tells publican the data being processed is a distributed set. Note: do not use distributed_set on the command line. Publican uses this flag when calling itself to process distributed sets. This is the only safe way this flag can be used.|
    ),
    'cvs' => maketext(
        'Import the SRPM in to CVS, the run make tag and make build.'),
    'nocolours' => maketext('Disable ANSI colourisation of logging.'),
    'quiet'     => maketext('Disable all logging.'),
);

# Options all actions use
my @utility_opts = (
    'help',            'config=s',
    'common_config=s', 'common_content=s',
    'nocolours',       'quiet',
);

# Actions
my %actions = (
    'build' => {
        'brief' => maketext(
            'Transform XML to other formats (pdf, html, html-single, etc)'),
        'options' =>
            [ 'formats', 'langs', 'publish', 'embedtoc', 'distributed_set' ],
    },
    'clean' => {
        'brief'   => maketext('Remove all temporary files and directories'),
        'options' => [],
    },

    'cleanset' => {
        'brief'   => maketext('Remove local copies of remote set books'),
        'options' => [],
    },

    'clean_ids' => {
        'brief'   => maketext('Run clean ids for source XML'),
        'options' => [],
    },
    'old2new' => {
        'brief' => maketext(
            'Create a publican.cfg file from the Makefile of an old book, set, or article'
        ),
        'options' => [],
    },
    'printtree' => {
        'brief'   => maketext('Print a tree of the xi:includes'),
        'options' => [],
    },
    'print_banned' => {
        'brief'   => maketext('Print a list of banned DocBook tags'),
        'options' => [],
    },
    'print_known' => {
        'brief'   => maketext("Print a list of QA'd DocBook tags"),
        'options' => [],
    },
    'print_unused' => {
        'brief'   => maketext('Print a list of unused XML files'),
        'options' => [],
    },
    'create' => {
        'brief'   => maketext('Create a new book, set, or article'),
        'options' => [
            'name',  'version', 'edition', 'product',
            'brand', 'lang',    'type'
        ],
    },
    'create_brand' => {
        'brief'   => maketext('Create a new brand'),
        'options' => [ 'name', 'lang' ],
    },
    'package' => {
        'brief'   => maketext('Package a language for shipping'),
        'options' => [
            'lang',          'desktop', 'brew', 'scratch',
            'short_sighted', 'binary',  'wait', 'cvs'
        ],
    },
    'update_pot' => {
        'brief'   => maketext('Update the POT files'),
        'options' => [],
    },
    'update_po' => {
        'brief'   => maketext('Update the PO files'),
        'options' => ['langs'],
    },
    'installbrand' => {
        'brief'   => maketext('Install a brand to the supplied location'),
        'options' => ['path'],
    },
    'help_config' => {
        'brief'   => maketext('Display help text for the configuration file'),
        'options' => [],
    },
    'lang_stats' => {
        'brief'   => maketext('report PO statistics'),
        'options' => ['lang'],
    },
);

# Grab to limit options to the valid ones for this action
# TODO should we croak on more than one action? Handle more than one?
$action = ( $ARGV[0] || "" );

# Getopt: standard + the options for the supplied action
my @optns = ( keys(%opts), @utility_opts );

if ( defined $actions{$action} ) {
    foreach my $opt ( @{ $actions{$action}->{options} } ) {
        if ( $options{$opt} ) {
            push( @optns, $opt );
        }

        # match options that take a String
        # this will need to be exapnded if other types are used
        elsif ( $options{"$opt=s"} ) {
            push( @optns, "$opt=s" );
        }
        else {

            # This should never happen
            croak(
                maketext(
                    "Invalid option '[_1]' in \$actions{\$action}->{options}",
                    $opt
                )
            );
        }
    }
}

GetOptions( \%opts, @optns, )
    or pod2usage( -msg => "\n", -verbose => 1, -exit => 1 );

# Getopt will remove the options leaving only the action
$action = ( $ARGV[0] || "" );

if ($show_version) {
    print("version=$VERSION\n");
    exit(0) if ( $action eq "" );
}

# Undocumented way of getting help for all actions
if ( $help and ( $action eq "all" ) ) {
    foreach my $cmd ( sort( keys(%actions) ) ) {
        _help_action($cmd);
        print("\n");
    }
    exit(0);
}

sub _help_actions {
    print( "\n", maketext("Valid actions are:"), "\n\n" );
    foreach my $cmd ( sort( keys(%actions) ) ) {
        printf( "    %-12s  %s\n", $cmd, $actions{$cmd}->{brief} );
    }
    print("\n\n");
    print(
        maketext(
            "Run: '[_1] <action> --help' for details on action usage", $0
        ),
        "\n\n"
    );

    return;
}

# catch no action set
if ( $action eq "" ) {
    pod2usage( -msg => "\n", -verbose => 1, -exit => 0 ) if $help;
    pod2usage( -verbose => 2, -exit => 0 ) if $man;

    if ($help_actions) {
        _help_actions();
        exit(0);
    }

    pod2usage(
        -msg     => "\n" . maketext("Action required!") . "\n",
        -verbose => 1,
        -exit    => 1
    );

    exit(1);
}

# Catch bogus action
if ( not defined( $actions{$action} ) ) {
    print( maketext( "'[_1]' is an unknown action!", $action ), "\n\n" );
    _help_actions();

    exit(1);

}

sub _help_action {
    my $cmd = shift || croak maketext( "[_1] is a required argument", 'cmd' );

    print( "$cmd\n    " . $actions{$cmd}->{brief} );
    print( "\n\n\t", maketext("Options:"), "\n" );
    foreach my $option ( @utility_opts, @{ $actions{$cmd}->{options} } ) {
        debug_msg("TODO: does printf work for right to left languages?\n");
        if ( $options{$option} ) {
            printf( "        --%-20s    %s\n", $option, $options{$option} );
        }
        else {
            printf(
                "        --%-20s    %s\n",
                "$option=<" . uc($option) . ">",
                $options{"$option=s"}
            );
        }
    }
    print("\n");

    return;
}

# $action must  be set to get here
if ($help) {
    _help_action($action);
    exit(0);
}

#######################################################################
#
# Start processing actions
#
#######################################################################

#pod2usage(1) if ( !$name || $type !~ /[Book|Set|Article]/);
if ( $action eq 'create' ) {

    my $docname = $opts{name}
        || croak( maketext("name is a required parameter") );
    $docname =~ s/\s/_/g;

    my $creator = Publican::CreateBook->new(
        {   name    => $docname,
            version => $opts{version},
            edition => $opts{edition},
            product => $opts{product},
            brand   => $opts{brand},
            lang    => $opts{lang},
            type    => $opts{type}
        }
    );
    $creator->create();

    my $dir = pushd($docname);

    my $publican = Publican->new(
        {   'configfile'   => $opts{config},
            common_config  => $opts{common_config},
            common_content => $opts{common_content},
            QUIET          => $opts{quiet},
            NOCOLOURS      => $opts{nocolours},
        }
    );

    my $builder = Publican::Builder->new();
    $builder->clean_ids();

    $dir = undef;

    exit(0);
}

if ( $action eq 'create_brand' ) {
    my $creator = Publican::CreateBrand->new(
        {   name => $opts{name},
            lang => $opts{lang},
        }
    );
    $creator->create();

    exit(0);
}

if ( $action eq 'old2new' ) {
    old2new();
    exit(0);
}

if ( $action eq 'print_known' ) {
    Publican::XmlClean::print_known_tags();
    exit(0);
}

if ( $action eq 'print_banned' ) {
    Publican::XmlClean::print_banned_tags();
    exit(0);
}

my %args = ( 'configfile' => $opts{config} );

my $publican = Publican->new(
    {   'configfile'   => $opts{config},
        common_config  => $opts{common_config},
        common_content => $opts{common_content},
        QUIET          => $opts{quiet},
        NOCOLOURS      => $opts{nocolours},
    }
);

if ( $action eq 'installbrand' ) {
    my $brand = $publican->param('brand') || croak("Can't find brand name");

    croak( maketext( "[_1] is a required argument", 'path' ) )
        unless ( $opts{path} );
    croak( maketext("you need to publish the brand first") )
        unless ( -d "publish" );
    croak( maketext("destination must exist") ) unless ( -d $opts{path} );

    rcopy( "publish/*",    "$opts{path}/." );
    rcopy( "publican.cfg", "$opts{path}/$brand/." );
    rcopy( "defaults.cfg", "$opts{path}/$brand/." )
        if ( -f "defaults.cfg" );
    rcopy( "overrides.cfg", "$opts{path}/$brand/." )
        if ( -f "overrides.cfg" );

    exit(0);
}

if ( $action eq 'help_config' ) {
    $publican->help_config();

    exit(0);
}

if ( $action eq 'printtree' ) {
    my $treeview = Publican::TreeView->new();
    $treeview->print_tree();
    exit(0);
}

if ( $action eq 'print_unused' ) {
    my $treeview = Publican::TreeView->new();
    $treeview->print_unused();
    exit(0);
}

if ( $action eq 'clean' || $action eq 'package' ) {
    if ( $action eq 'package' ) {
        logger(
            maketext(
                "Running clean process to ensure stale content is not bundled in packages."
                )
                . "\n",
            RED
        );
    }
    logger( maketext("Clean: Removing temporary and publish content.") . "\n",
    );

    my $error;

    rmtree( $publican->param('tmp_dir') );
    rmtree("publish");

    my $books = $publican->param('books') || "";
    foreach my $book ( split( " ", $books ) ) {
        rmtree("$book/tmp");
        rmtree("$book/publish");
    }
}

if ( $action eq 'cleanset' ) {
    my $books = $publican->param('books') || "";
    foreach my $book ( split( " ", $books ) ) {
        rmtree("$book");
    }
}

if ( $action eq 'clean_ids' ) {
    my $builder = Publican::Builder->new();
    $builder->clean_ids();
}

if ( $action eq 'update_pot' ) {
    my $translater = Publican::Translate->new();
    $translater->update_pot();
}

if ( $action eq 'update_po' ) {
    my $translater = Publican::Translate->new();
    if ( $opts{langs} eq 'all' ) {
        $translater->update_po_all();
    }
    else {
        $translater->update_po( { langs => $opts{langs} } );
    }
}

if ( $action eq 'lang_stats' ) {
    my $translater = Publican::Translate->new();
    $translater->po_report( { lang => $opts{lang} } );
}

if ( $action eq 'build' ) {
    my $builder = Publican::Builder->new();
    $builder->build(
        {   formats         => $opts{formats},
            langs           => $opts{langs},
            publish         => $opts{publish},
            embedtoc        => $opts{embedtoc},
            distributed_set => $opts{distributed_set},
        }
    );
}

if ( $action eq 'package' ) {
    my $builder = Publican::Builder->new();
    if ( $publican->param('type') eq 'brand' ) {
        $builder->package_brand( { binary => $opts{binary} } );
    }
    else {
        $builder->package(
            {   lang          => $opts{lang},
                desktop       => $opts{desktop},
                short_sighted => $opts{short_sighted},
                binary        => $opts{binary}
            }
        );
    }

    if ( $opts{brew} ) {

        my @filelist = File::Find::Rule->file->name('*.src.rpm')
            ->in( $publican->param('tmp_dir') );

        my $srpm = pop(@filelist);

        my @brew_args = ( "brew", "build" );
        push( @brew_args, "--scratch" ) if ( $opts{scratch} );
        push( @brew_args, "--wait" )    if ( $opts{'wait'} );
        push( @brew_args, $publican->param('brew_dist') );

        if ( -f "$srpm" ) {
            system( @brew_args, "$srpm" );
        }
        else {
            croak( maketext("Can't locate srpm, packaging aborted") . "\n",
                RED );
        }
    }

    if ( $opts{cvs} ) {

        my $dir = pushd( $publican->param('tmp_dir') );
        print( "pushd " . $publican->param('tmp_dir') . "\n" );

        my @filelist = File::Find::Rule->file->name('*.src.rpm')->in('rpm');

        my $srpm = abs_path( pop(@filelist) );

        unless ( -f "$srpm" ) {
            croak( maketext("Can't locate srpm, CVS import aborted") . "\n" );
        }

        # TODO consider making this optional?
        my $cvs_root = $publican->param('cvs_root')
            || croak(
            maketext("--cvs requires cvs_root to be set in the cfg file") );

        my $cvs_pkg = $publican->param('cvs_pkg')
            || croak(
            maketext("--cvs requires cvs_pkg to be set in the cfg file") );
        $cvs_pkg =~ s/$LANG_PATTERN/$opts{lang}/g;

        my $cvs_branch = $publican->param('cvs_branch')
            || croak(
            maketext("--cvs requires cvs_branch to be set in the cfg file") );

        my $cvs_dir = "$cvs_pkg-$cvs_branch";

        my @cvs_args = ("cvs");
        push( @cvs_args, ( "-d", $cvs_root ) ) if ($cvs_root);
        push( @cvs_args, ( "co", $cvs_dir ) );

        print( "cvs command: " . join( " ", @cvs_args ), "\n" );
        system(@cvs_args);
        croak($@) if ($@);

        print(qq|pushd("$cvs_dir/$cvs_pkg"\n|);
        ## BUGBUG this breaks if you undef $dir first ...
        $dir = undef;
        $dir = pushd( $publican->param('tmp_dir') . "/$cvs_dir/$cvs_pkg" );

        system("$IMPORT_SCRIPT $srpm");
        croak($@) if ($@);

        system("cvs up");
        croak($@) if ($@);

        system("make tag");
        croak($@) if ($@);

        system("make build");
        croak($@) if ($@);

        $dir = undef;
    }
}

exit(0);

__END__

=head1 INTERFACE 

build
    Transform XML to other formats (pdf, html, html-single, etc)

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.
        --formats=<FORMATS>       Comma-separated list of formats, for example: html,pdf,html-single,html-desktop,txt,epub
        --langs=<LANGS>           Comma-separated list of languages, for example: en-US,de-DE,all
        --publish                 Set up built content for publishing
        --embedtoc                Embed the web site TOC object in the generated HTML
        --distributed_set         This flag tells publican the data being processed is a distributed set. Note: do not use distributed_set on the command line. Publican uses this flag when calling itself to process distributed sets. This is the only safe way this flag can be used.


clean
    Remove all temporary files and directories

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.


clean_ids
    Run clean ids for source XML

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.


cleanset
    Remove local copies of remote set books

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.


create
    Create a new book, set, or article

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.
        --name=<NAME>             The name of the book, article, set, or brand
        --version=<VERSION>       The version of the product
        --edition=<EDITION>       The edition of the book, article, or set
        --product=<PRODUCT>       The name of the product
        --brand=<BRAND>           The brand to use
        --lang=<LANG>             The language the XML will be written in
        --type=<TYPE>             The type (book, article, or set)


create_brand
    Create a new brand

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.
        --name=<NAME>             The name of the book, article, set, or brand
        --lang=<LANG>             The language the XML will be written in


help_config
    Display help text for the configuration file

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.


installbrand
    Install a brand to the supplied location

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.
        --path=<PATH>             /path/to/install/to


lang_stats
    report PO statistics

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.
        --lang=<LANG>             The language the XML will be written in


old2new
    Create a publican.cfg file from the Makefile of an old book, set, or article

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.


package
    Package a language for shipping

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.
        --lang=<LANG>             The language the XML will be written in
        --desktop                 Create desktop instead of web package
        --brew                    Push SRPM to brew
        --scratch                 Use scratch instead of tag build
        --short_sighted           Create package without using version in package name
        --binary                  Build binary rpm when running package
        --wait                    Wait for brew to finish building
        --cvs                     Import the SRPM in to CVS, the run make tag and make build.

print_banned
    Print a list of banned DocBook tags

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.


print_known
    Print a list of QA'd DocBook tags

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.


print_unused
    Print a list of unused XML files

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.


printtree
    Print a tree of the xi:includes

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.


update_po
    Update the PO files

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.
        --langs=<LANGS>           Comma-separated list of languages, for example: en-US,de-DE,all


update_pot
    Update the POT files

        Options:
        --help                    Display help message
        --config=s                Use a nonstandard config file
        --common_config=s         Override path to Common_Config directory
        --common_content=s        Override path to Common_Content directory
        --nocolours               Disable ANSI colourisation of logging.
        --quiet                   Disable all logging.

=head1 CONFIGURATION AND ENVIRONMENT

Publican requires access to GetText msgmerge for merging updated POT files with PO files.

Publican requires access to Apache FOP for creating PDF files.


=head1 DEPENDENCIES

Archive::Tar
Carp
Config::Simple
Cwd
DateTime
DateTime::Format::DateParse
Encode
File::Copy::Recursive
File::Find
File::Find::Rule
File::Path
File::pushd
File::Spec
Getopt::Long
HTML::FormatText
HTML::TreeBuilder
I18N::LangTags::List
Image::Magick
Image::Size
Locale::PO
Makefile::Parser
Module::Build
Pod::Usage
Publican
Publican::Builder
Publican::CreateBook
Publican::CreateBrand
Publican::Localise
Publican::Translate
Publican::TreeView
Publican::XmlClean
Syntax::Highlight::Engine::Kate
Term::ANSIColor
Test::More
Text::Wrap
XML::LibXML
XML::LibXSLT
XML::TreeBuilder


=head1 INCOMPATIBILITIES

None reported.


=head1 BUGS AND LIMITATIONS

No bugs have been reported.

Please report any bugs or feature requests to
C<publican-list@redhat.com>, or through the web interface at
L<https://bugzilla.redhat.com/bugzilla/enter_bug.cgi?product=Fedora&amp;version=rawhide&amp;component=publican>.

=head1 AUTHOR

Jeff Fearn  C<< <jfearn@redhat.com> >>
