#!/usr/bin/perl -CA
#BUGBUG -CAS should allow removing utf8::encode, utf8::decode from Translate.pm
# See: http://perldoc.perl.org/perlrun.html#*-C-[_number/list_]*
# or binmode STDIN, ":encoding(UTF-8)";
# http://en.wikibooks.org/wiki/Perl_Programming/Unicode_UTF-8

use warnings;
use strict;
use 5.008;
use Carp;
use Getopt::Long;
use Pod::Usage;
use Publican;
use Publican::CreateBook;
use Publican::CreateBrand;
use Publican::Builder;
use Publican::XmlClean;
use Publican::TreeView;
use Publican::WebSite;
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);
use File::Basename;

my $VERSION      = "$Publican::VERSION";
my $LANG_PATTERN = q|__LANG__|;
## TODO Consider making these parameters
my $IMPORT_SCRIPT     = '../common/cvs-import.sh';
my $WEB_TEMPLATE_PATH = '/usr/share/publican';

=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

Valid actions are:

    build         Transform XML to other formats (pdf, html, html-single, etc)
    bump          TECH PREVIEW: bump the pubsnumber [revision]. This feature is intended to have an additional argument to add a revision history entry and message. This feature is UNTESTED
    clean         Remove all temporary files and directories
    clean_ids     Run clean ids for source XML
    clean_set     Remove local copies of remote set books
    create        Create a new book, set, or article
    create_brand  Create a new brand
    create_site   Create a new WebSite in the supplied location.
    help_config   Display help text for the configuration file
    install_book  Install a book in to a WebSite.
    install_brand  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_tree    Print a tree of the xi:includes
    print_unused  Print a list of unused XML files
    remove_book   Remove a book from a WebSite.
    rename        Rename a publican book
    site_stats    Report on the contents of a WebSite
    update_db     Add or remove database entries. Used for processing pre-build books, such as when building packages.
    update_po     Update the PO files
    update_pot    Update the POT files
    update_site   Update an existing sites templates.


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

=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;
my $bash_comp    = undef;

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

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

#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.'),
## WebSite options
    'db_file=s'   => maketext('Override default database file.'),
    'toc_path=s'  => maketext('Override the default TOC path.'),
    'tmpl_path=s' => maketext('Override the default template path.'),
    'site_config=s' =>
        maketext('WebSite configuration file to use or create.'),
    'novalid'         => maketext('Do not run the DTD validation'),
    add               => maketext('Add a database entry'),
    del               => maketext('Delete a database entry'),
    'subtitle=s'      => maketext('Sub title for a book'),
    'abstract=s'      => maketext('Abstract for a book'),
    'product_label=s' => maketext('product label for a book'),
    'version_label=s' => maketext('version label for a book'),
    'name_label=s'    => maketext('name label for a book'),
);

# 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', 'novalid'
        ],
    },
    'bump' => {
        'brief' =>
            'TECH PREVIEW: bump the pubsnumber [revision]. This feature is intended to have an additional argument to add a revision history entry and message. This feature is UNTESTED',
        'options' => ['lang'],
    },
    'clean' => {
        'brief'   => maketext('Remove all temporary files and directories'),
        'options' => [],
    },

    'clean_set' => {
        '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' => [],
    },
    'print_tree' => {
        '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'],
    },
    'install_brand' => {
        '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'],
    },
## WebSite actions
    'create_site' => {
        'brief' => maketext('Create a new WebSite in the supplied location.'),
        'options' =>
            [ 'site_config', 'db_file', 'toc_path', 'tmpl_path', 'lang' ],
    },
    'update_site' => {
        'brief'   => maketext('Update an existing sites templates.'),
        'options' => ['site_config'],
    },
    'install_book' => {
        'brief'   => maketext('Install a book in to a WebSite.'),
        'options' => [ 'site_config', 'lang' ],
    },
    'remove_book' => {
        'brief'   => maketext('Remove a book from a WebSite.'),
        'options' => [ 'site_config', 'lang' ],
    },
    'site_stats' => {
        'brief'   => maketext('Report on the contents of a WebSite'),
        'options' => ['site_config'],
    },
    'update_db' => {
        'brief' => maketext(
            'Add or remove database entries. Used for processing pre-build books, such as when building packages.'
        ),
        'options' => [
            'site_config',   'add',
            'del',           'lang',
            'product',       'version',
            'name',          'formats',
            'subtitle',      'abstract',
            'product_label', 'version_label',
            'name_label'
        ],
    },
    'rename' => {
        'brief'   => maketext('Rename a publican book'),
        'options' => [ 'name', 'product', 'version' ],
    },
);

# 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;
}

if ($bash_comp) {
    _bash_completion();
    exit(0);
}

# 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 'create_site' ) {
    my $site_config = $opts{site_config}
        || croak( maketext( "[_1] is a required argument", 'site_config' ) );
    my $db_file   = $opts{db_file}   || undef;
    my $toc_path  = $opts{toc_path}  || undef;
    my $tmpl_path = $opts{tmpl_path} || undef;
    my $def_lang  = $opts{lang}      || undef;

    if ( -f $site_config ) {
        croak(
            maketext(
                "Config file exists, you must supply a non-existent filename."
            )
        );
    }

    my $config = new Config::Simple();
    $config->syntax('http');

    my ( $filename, $directories, $suffix ) = fileparse($db_file);
    mkpath($directories) unless -d $directories;
    $config->param( 'db_file', abs_path($db_file) ) if ($db_file);

    if ($toc_path) {
        mkpath($toc_path) unless -d $toc_path;
        $config->param( 'toc_path', abs_path($toc_path) );
        rcopy( "$WEB_TEMPLATE_PATH/sitetemplate/*", "$toc_path/." );
    }

    $config->param( 'tmpl_path', abs_path($tmpl_path) ) if ($tmpl_path);
    $config->param( 'def_lang',  $def_lang )            if ($def_lang);
    $config->write($site_config);

    my $ws = Publican::WebSite->new(
        { create => 1, site_config => $site_config } );
    $ws->regen_all_toc();

    exit(0);
}

if ( $action eq 'update_site' ) {
    my $site_config = $opts{site_config} || undef;
    local $File::Copy::Recursive::RMTrgFil = 1;
    my $ws = Publican::WebSite->new( { site_config => $site_config } );

    rcopy( "$WEB_TEMPLATE_PATH/sitetemplate/*", $ws->toc_path() . "/." );
    $ws->regen_all_toc( { force => 1 } );

    exit(0);
}

if ( $action eq 'site_stats' ) {
    my $site_config = $opts{site_config} || undef;
    my $ws = Publican::WebSite->new( { site_config => $site_config } );
    print( $ws->report() );

    exit(0);
}

# install/remove book for pre-build content (RPM, DEB, etc)
if ( $action eq 'update_db' ) {
    my $site_config = $opts{site_config} || undef;
    my $ws = Publican::WebSite->new( { site_config => $site_config } );

    my $add = $opts{add} || undef;
    my $del = $opts{del} || undef;

    croak(
        maketext("One of add or del is required when updating the database") )
        unless ( $add || $del );

    my $lang = $opts{lang}
        || croak( maketext( "[_1] is a required argument", 'lang' ) );
    my $product = $opts{product}
        || croak( maketext( "[_1] is a required argument", 'product' ) );
    my $version
        = defined $opts{version}
        ? $opts{version}
        : croak( maketext( "[_1] is a required argument", 'version' ) );
    my $name = $opts{name}
        || croak( maketext( "[_1] is a required argument", 'name' ) );
    my $formats = $opts{formats}
        || croak( maketext( "[_1] is a required argument", 'formats' ) );

    # required for add
    my $subtitle = $opts{subtitle} || undef;
    my $abstract = $opts{abstract} || undef;

    if ($add) {
        croak( maketext( "[_1] is a required argument", 'subtitle' ) )
            unless ($subtitle);
        croak( maketext( "[_1] is a required argument", 'abstract' ) )
            unless ($abstract);
    }

    # optional for add
    my $product_label = $opts{product_label} || undef;
    my $version_label = $opts{version_label} || undef;
    my $name_label    = $opts{name_label}    || undef;

    if ($add) {
        foreach my $format ( split( /,/, $formats ) ) {
            $ws->update_or_add_entry(
                {   language      => $lang,
                    product       => $product,
                    version       => $version,
                    name          => $name,
                    format        => $format,
                    product_label => $product_label,
                    version_label => $version_label,
                    name_label    => $name_label,
                    subtitle      => $subtitle,
                    abstract      => $abstract,
                }
            );
        }
    }
    else {
        foreach my $format ( split( /,/, $formats ) ) {

            $ws->del_entry(
                {   language => $lang,
                    product  => $product,
                    version  => $version,
                    name     => $name,
                    format   => "$format"
                }
            );
        }

    }

    $ws->regen_all_toc();

    exit(0);
}

## NOTE: All targets here MUST have a valid publican.cfg file
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 'install_brand' ) {
    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 'print_tree' ) {
    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 [_1] and publish directories.",
            $publican->param('tmp_dir') )
            . "\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 'clean_set' ) {
    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();
    if ( $opts{lang} eq 'all' ) {
        foreach my $lang ( split( /,/, get_all_langs() ) ) {
            $translater->po_report( { lang => $lang } );
        }
    }
    else {
        $translater->po_report( { lang => $opts{lang} } );
    }
}

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

## TODO handle Revision History entry
## Handle translations
if ( $action eq 'bump' ) {
    my $lang = $opts{lang}
        || croak maketext("--lang is a required option for bump");

    my $xml_lang = $publican->param('xml_lang');
    my $dtdver   = $publican->param('dtdver');
    my $docname  = $publican->param('docname');
    my $type     = $publican->param('type');

## TODO do this later
    if ( $type eq 'brand' ) {
        logger("bump does not support brands yet!");
        exit;
    }

    if ( $lang eq $xml_lang ) {

        my $xml_doc = new_tree(1);

        my $file = "$xml_lang/$type" . '_Info.xml';
        $xml_doc->parse_file($file);
        my $tag = $xml_doc->attr("_tag");

        my $node = $xml_doc->root()->look_down( "_tag", "pubsnumber" );
        my $release = $node->as_text();
        $release++;
        $node->delete_content();
        $node->push_content("$release");
        $publican->param( 'release', $release );
        $publican->{config}->param( 'release', $release );

        my $OUTDOC;

        open( $OUTDOC, ">:encoding(UTF-8)", "$file" )
            || croak( maketext( "Could not open [_1] for output!", $file ) );

        my $ent_file = undef;

        if ( -e "$xml_lang/$docname.ent" ) {
            $ent_file = "$xml_lang/$docname.ent";
        }

        print( $OUTDOC Publican::Builder::dtd_string(
                { tag => $tag, dtdver => $dtdver, ent_file => $ent_file }
            )
        );

## TODO BUGBUG move traverse code to Publican.pm and call from
## Builder, XmlClean, and here
        my $text = $xml_doc->root()->as_XML();
        $text =~ s/&#38;([a-zA-Z-_0-9]+;)/&$1/g;
        $text =~ s/&#38;/&amp;/g;
        $text =~ s/&#60;/&lt;/g;
        $text =~ s/&#62;/&gt;/g;
        $text =~ s/&#34;/"/g;
        $text =~ s/&#39;/'/g;
        $text =~ s/&quot;/"/g;
        $text =~ s/&apos;/'/g;

        print( $OUTDOC $xml_doc->root()->as_XML() );
        close($OUTDOC);
        $xml_doc->root()->delete();

    }
    else {
## TODO do this later
        logger("bump does not support translations yet!");
        exit;
    }

}

if ( $action eq 'package' ) {
    my $builder = Publican::Builder->new();
    if ( $publican->param('type') eq 'brand' ) {
        $builder->package_brand( { binary => $opts{binary} } );
    }
    elsif ( $publican->param('web_home') || $publican->param('web_type') ) {
        $builder->package_home( { 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} );
        if ( $opts{'wait'} ) {
            push( @brew_args, "--wait" );
        }
        else {
            push( @brew_args, "--nowait" );
        }

        push( @brew_args, $publican->param('brew_dist') );

        if ( -f "$srpm" ) {
            my $result = system( @brew_args, "$srpm" );
            if ($result) {
                croak( maketext("Brew died, error $result: '$!'") );
            }
        }
        else {
            croak( maketext("Can't locate srpm, packaging aborted") );
        }
    }

    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") );
        }

        # 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" );
        my $result = system(@cvs_args);
        if ($result) {
            croak( maketext("cvs died, error $result: '$!'") );
        }

        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" );

        $result = system( $IMPORT_SCRIPT, '--force', $srpm );
        if ($result) {
            croak( maketext("cvs import died, error $result: '$!'") );
        }

        $result = system("cvs up");
        if ($result) {
            croak( maketext("cvs up died, error $result: '$!'") );
        }

        @cvs_args = ('make');

        unless ( $opts{'wait'} ) {
            push( @cvs_args, "BREW_FLAGS=--nowait" );
        }

        push( @cvs_args, 'build' );

        $result = system(@cvs_args);
        if ($result) {
            croak( maketext("make died, error $result: '$!'") );
        }

        $dir = undef;
    }
}

if ( $action eq 'install_book' ) {
    my $site_config = $opts{site_config} || undef;
    my $ws = Publican::WebSite->new( { site_config => $site_config } );

    if ( $publican->param('web_home') || $publican->param('web_type') ) {
        croak( maketext("can't find 'publish' directory") )
            unless ( -d "publish/home" );
        rcopy( "publish/home/*", $ws->toc_path() . "/." );
    }
    else {
        my $lang = $opts{lang}
            || croak( maketext( "[_1] is a required argument", 'lang' ) );
        my $product       = $publican->param('product');
        my $version       = $publican->param('version');
        my $name          = $publican->param('docname');
        my $product_label = $publican->param('web_product_label');
        my $version_label = $publican->param('web_version_label');
        my $name_label    = $publican->param('web_name_label');

        my $subtitle = $publican->get_subtitle( { lang => $lang } );
        my $abstract = $publican->get_abstract( { lang => $lang } );

        croak( maketext( "can't find 'publish/[_1]' directory", $lang ) )
            unless ( -d "publish/$lang" );

        my $type     = $publican->param('type');
        my $xml_lang = $publican->param('xml_lang');
        my $tmp_dir  = $publican->param('tmp_dir');

        # get translated labels
## BUGBUG this is copied from Publican.pm, maybe a sub for these?
        if ( $lang ne $xml_lang ) {
            my $xml_file = "$tmp_dir/$lang/xml/$type" . '_Info.xml';
            croak( maketext( "Can't locate required file: [_1]", $xml_file ) )
                if ( !-f $xml_file );

            my $xml_doc = XML::TreeBuilder->new();
            $xml_doc->parse_file($xml_file);

            # BUGBUG can't translate overridden labels :(
            unless ($product_label) {
                $product_label = eval {
                    $xml_doc->root()->look_down( "_tag", "productname" )
                        ->as_text();
                };
                if ($@) {
                    croak maketext("productname not found in Info file");
                }
                $product_label =~ s/\s/_/g;

                $product_label = undef if ( $product_label eq $product );
            }

            unless ($name_label) {
                $name_label = eval {
                    $xml_doc->root()->look_down( "_tag", "title" )->as_text();
                };
                if ($@) {
                    croak maketext("title not found in Info file");
                }
                $name_label =~ s/\s/_/g;
                $name_label = undef if ( $name_label eq $name );
            }
        }

        mkpath( $ws->toc_path() . "/$lang" )
            unless ( -d $ws->toc_path() . "/$lang" );
        rcopy( "publish/$lang/*", $ws->toc_path() . "/$lang/." );

        my @formats = split( /,/, $publican->param('web_formats') );

        foreach my $format (@formats) {
            next unless ( -d "publish/$lang/$product/$version/$format" );

            $ws->update_or_add_entry(
                {   language      => $lang,
                    product       => $product,
                    version       => $version,
                    name          => $name,
                    format        => $format,
                    product_label => $product_label,
                    version_label => $version_label,
                    name_label    => $name_label,
                    subtitle      => $subtitle,
                    abstract      => $abstract,
                }
            );
        }
    }

    $ws->regen_all_toc();
}

if ( $action eq 'remove_book' ) {
    my $site_config = $opts{site_config} || undef;
    my $ws = Publican::WebSite->new( { site_config => $site_config } );

    my $lang = $opts{lang}
        || croak( maketext( "[_1] is a required argument", 'lang' ) );
    my $product = $publican->param('product');
    my $version = $publican->param('version');
    my $name    = $publican->param('docname');

    my @formats = split( /,/, $publican->param('web_formats') );
    foreach my $format (@formats) {

        # BUGBUG get these the normal way
        $ws->del_entry(
            {   language => $lang,
                product  => $product,
                version  => $version,
                name     => $name,
                format   => "$format"
            }
        );

        my $dir = "$ws->{toc_path}/$lang/$product/$version/$format/$name";
        rmtree($dir);

        # delete empty directories
        $dir = "$ws->{toc_path}/$lang/$product/$version/$format";
        unless ( File::Find::Rule->not_name(qr/(\.|\.\.)/)->in($dir) ) {
            rmtree($dir);
            $dir = "$ws->{toc_path}/$lang/$product/$version";
            unless ( File::Find::Rule->not_name(qr/(\.|\.\.)/)->in($dir) ) {
                rmtree($dir);
                $dir = "$ws->{toc_path}/$lang/$product";
                unless ( File::Find::Rule->not_name(qr/(\.|\.\.)/)->in($dir) )
                {
                    rmtree($dir);
                }
            }
        }
    }
    $ws->regen_all_toc();

}

if ( $action eq 'rename' ) {

    my $new_docname = $opts{name}
        || croak( maketext("name is a required parameter") );

    my $conf_file = ( $opts{config} || 'publican.cfg' );
    my $config = new Config::Simple($conf_file);

    $config->syntax('http');

    # set mainfile in publican.cfg if it's not set
    unless ( $config->param('mainfile') ) {
        $config->param( 'mainfile', $publican->{config}->param('docname') );
    }

    # delete docname
    $config->delete('docname');

    # load in $type_Info.xml
    my $file
        = $config->param('xml_lang') . '/'
        . ( $config->param('type') || 'Book' )
        . '_Info.xml';

    my $cleaner = Publican::XmlClean->new( { clean_id => 1 } );

    my $xml_doc = XML::TreeBuilder->new(
        { 'NoExpand' => "1", 'ErrorContext' => "2" } );
    $xml_doc->store_comments(1);
    $xml_doc->parse_file($file)
        || croak( maketext( "Can't open file '[_1]' [_2]", $file, $@ ) );

    # Change Title
    my $title = $xml_doc->root()->look_down( "_tag", "title" )
        || croak( maketext('Cannot locate title tag in Info file!') );
    $title->delete_content();
    $title->push_content($new_docname);

    # Change Product Name
    if ( $opts{product} ) {
        my $prod = $xml_doc->root()->look_down( "_tag", "productname" )
            || croak(
            maketext('Cannot locate productname tag in Info file!') );
        $prod->delete_content();
        $prod->push_content( $opts{product} );
    }

    # Change Product Number
    if ( $opts{version} ) {
        my $prod_num = $xml_doc->root()->look_down( "_tag", "productnumber" )
            || croak(
            maketext('Cannot locate productnumber tag in Info file!') );
        $prod_num->delete_content();
        $prod_num->push_content( $opts{version} );
    }

    # write out new files
    $config->write($conf_file);
    $cleaner->print_xml( { xml_doc => $xml_doc, out_file => $file } );

    # clean up memory
    $title->delete();
    $xml_doc->root()->delete();
}

# Secret action to create bash completion file.
# Nothing special just don't want to confuse people by discussing
# it in the general help

sub _bash_completion {

    my $actions = join( " ", sort( keys(%actions) ) );
    my $util_opts = "";
    foreach my $opto ( sort(@utility_opts) ) {
        $opto =~ s/=.*$//g;
        $util_opts .= "--$opto ";
    }

    my $top = <<TOP;
# publican completion

_publican()
{
	local cur prev commands options command

	COMPREPLY=()
	cur=`_get_cword`
        brands=`ls /usr/share/publican/Common_Content`

	commands='$actions'

	if [[ \$COMP_CWORD -eq 1 ]] ; then
		if [[ "\$cur" == -* ]]; then
			COMPREPLY=( \$( compgen -W '--help' -- \$cur ) )
		else
			COMPREPLY=( \$( compgen -W "\$commands" -- \$cur ) )
		fi
	else
		prev=\${COMP_WORDS[COMP_CWORD-1]}
		case \$prev in
			@(--site_config|--config))
				_filedir 'cfg'
				return 0;
				;;
			@(--lang|--langs))
				_filedir -d
				return 0;
				;;
			--formats)
				COMPREPLY=( \$( compgen -W "eclipse epub html html-single html-desktop man pdf txt xml" -- \$cur ) )
				return 0;
				;;
			--type)
				COMPREPLY=( \$( compgen -W "Article Book Set" -- \$cur ) )
				return 0;
				;;
			--brand)
				COMPREPLY=( \$( compgen -W "\$brands" -- \$cur ) )
				return 0;
				;;
		esac

		command=\${COMP_WORDS[1]}

		if [[ "\$cur" == -* ]]; then
			# possible options for the command
			case \$command in
TOP

    my $options = "";

    foreach my $action ( sort( keys(%actions) ) ) {
        my $opt = "";
        foreach my $option ( sort( @{ $actions{$action}{'options'} } ) ) {
            $opt .= "--$option ";
        }

        $options .= <<EOO;
				$action)
					options='$opt'
					;;
EOO

    }

    my $bottom = <<BOTTOM;
			esac
			options="\$options $util_opts"
			COMPREPLY=( \$( compgen -W "\$options" -- \$cur ) )
		else
			if [[ "\$command" == @(--help) ]]; then
				COMPREPLY=( \$( compgen -W "\$commands" -- \$cur ) )
			else
				_filedir
			fi
		fi
	fi

	return 0
}
complete -F _publican publican
BOTTOM

    my $COMP_FILE;
    open( $COMP_FILE, ">", "_publican" ) || croak("file open fail: @_");
    print( $COMP_FILE $top );
    print( $COMP_FILE $options );
    print( $COMP_FILE $bottom );
    close($COMP_FILE);
    return;
}

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.
        --novalid                 Do not run the DTD validation


bump
    TECH PREVIEW: bump the pubsnumber [revision]. This feature is intended to have an additional argument to add a revision history entry and message. This feature is UNTESTED

	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


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.


clean_set
    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


create_site
    Create a new WebSite in 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.
        --site_config=<SITE_CONFIG>    WebSite configuration file to use or create.
        --db_file=<DB_FILE>       Override default database file.
        --toc_path=<TOC_PATH>     Override the default TOC path.
        --tmpl_path=<TMPL_PATH>    Override the default template path.
        --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.


install_book
    Install a book in to a WebSite.

	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.
        --site_config=<SITE_CONFIG>    WebSite configuration file to use or create.
        --lang=<LANG>             The language the XML will be written in


install_brand
    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_tree
    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.


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.


remove_book
    Remove a book from a WebSite.

	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.
        --site_config=<SITE_CONFIG>    WebSite configuration file to use or create.
        --lang=<LANG>             The language the XML will be written in


rename
    Rename a publican book

	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
        --product=<PRODUCT>       The name of the product
        --version=<VERSION>       The version of the product


site_stats
    Report on the contents of a WebSite

	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.
        --site_config=<SITE_CONFIG>    WebSite configuration file to use or create.


update_db
    Add or remove database entries. Used for processing pre-build books, such as when building packages.

	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.
        --site_config=<SITE_CONFIG>    WebSite configuration file to use or create.
        --add                     Add a database entry
        --del                     Delete a database entry
        --lang=<LANG>             The language the XML will be written in
        --product=<PRODUCT>       The name of the product
        --version=<VERSION>       The version of the product
        --name=<NAME>             The name of the book, article, set, or brand
        --formats=<FORMATS>       Comma-separated list of formats, for example: html,pdf,html-single,html-desktop,txt,epub
        --subtitle=<SUBTITLE>     Sub title for a book
        --abstract=<ABSTRACT>     Abstract for a book
        --product_label=<PRODUCT_LABEL>    product label for a book
        --version_label=<VERSION_LABEL>    version label for a book
        --name_label=<NAME_LABEL>    name label for a book


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.


update_site
    Update an existing sites templates.

	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.
        --site_config=<SITE_CONFIG>    WebSite configuration file to use or create.

=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=Publican&amp;component=publican>.

=head1 AUTHOR

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