#!/usr/bin/perl
use Cwd qw(cwd realpath);
use Getopt::Long;
use Pod::Usage;
use Code::TidyAll::Util qw(can_load dirname);
use Hash::MoreUtils qw(slice_def);
use strict;
use warnings;

sub usage {
    my $msg = shift;
    print "$msg\n" if $msg;
    require Pod::Usage;
    Pod::Usage::pod2usage( { verbose => 1 } );
}

my ( %params, $help, $all );
my $class = 'Code::TidyAll';

GetOptions(
    'backup-ttl=i' => \$params{backup_ttl},
    'class=s'      => \$class,
    'conf-file=s'  => \$params{conf_file},
    'data-dir=s'   => \$params{data_dir},
    'no-backups'   => \$params{no_backups},
    'no-cache'     => \$params{no_cache},
    'root-dir=s'   => \$params{root_dir},
    'a|all'        => \$all,
    'h|help'       => \$help,
    'v|verbose'    => \$params{verbose},
) or usage();

Pod::Usage::pod2usage( { verbose => 2 } ) if $help;

die "cannot load '$class'" unless can_load($class);

%params = slice_def( \%params );

$params{conf_file} ||= "$params{root_dir}/tidyall.ini" if ( $params{root_dir} );

my $result;
if ($all) {
    $params{conf_file} ||= $class->find_conf_file( cwd() );
    my $ct = $class->new(%params);
    $result = $ct->process_all();
}
else {
    my @files = @ARGV or die "file(s) or -a required";
    $params{conf_file} ||= $class->find_conf_file( dirname( $files[0] ) );
    my $ct = $class->new(%params);
    $result = $ct->process_files(@files);
}

exit( $result->error_count ? 1 : 0 );

1;



=pod

=head1 NAME

tidyall - Your all-in-one code tidier and validator

=head1 VERSION

version 0.01

=head1 SYNOPSIS

    # Create a tidyall.ini at the top of your project
    #
    [PerlTidy]
    argv = -noll -it=2
    select = **/*.{pl,pm,t}

    [PerlCritic]
    argv = -severity 3
    select = lib/**/*.pm
    ignore = lib/UtterHack.pm

    # Process all files in the current project, look upwards from cwd for tidyall.ini
    #
    % tidyall -a

    # Process all files in a particular project
    #
    % tidyall -a --root-dir /home/joe/project

    # Process one or more specific files, look upwards from the first file for tidyall.ini
    #
    % tidyall file [file...]

=head1 DESCRIPTION

There are a lot of great code tidiers and validators out there. C<tidyall>
makes them available from a single unified interface.

You can run C<tidyall> on a single file or on an entire project hierarchy, and
configure which tidiers/validators are applied to which files. C<tidyall> will
back up files beforehand, and for efficiency will only consider files that have
changed since they were last processed.

=head2 What's a tidier? What's a validator?

A I<tidier> transforms a file so as to improve its appearance without changing
its semantics. Examples include L<perltidy>, L<podtidy> and
L<htmltidy|HTML::Tidy>.

A I<validator> analyzes a file for some definition of correctness. Examples
include L<perlcritic>, L<podchecker> and
L<xmllint|http://xmlsoft.org/xmllint.html>.

Many tidiers are also validators, e.g. C<perltidy> will throw an error on badly
formed Perl.

To use a tidier or validator with C<tidyall> it must have a corresponding
plugin, usually under the prefix C<Code::TidyAll::Plugin::>.  This distribution
comes with plugins for L<perltidy|Code::TidyAll::Plugin::PerlTidy>,
L<perlcritic|Code::TidyAll::Plugin::PerlCritic> and
L<podtidy|Code::TidyAll::Plugin::PodTidy>.

=head1 OPTIONS

 -a, --all        Process all files in the project
 -h, --help       Print help message
 --backup-ttl     When backup files can be purged. Defaults to "1h"
 --class          Code::TidyAll subclass to use. Defaults to "Code::TidyAll"
 --conf-file      Specify conf file explicitly; usually inferred from specified files or cwd
 --data-dir       Contains data like backups and cache. Defaults to root_dir/.tidyall.d
 --no-backup      Don't backup files
 --no-cache       Don't cache last processed times; process all files every time
 --root-dir       Specify root dir explicitly; usually inferred from specified files or cwd

=head1 USING TIDYALL

C<tidyall> works on a project basis, where a project is just a directory
hierarchy of files. svn or git working directories are typical examples of
projects.

The top of the project is called the I<root directory>. In the root directory
you'll need a C<tidyall.ini> config file; it defines how various tidiers and
validators will be applied to the files in your project.

C<tidyall> will find your root directory and config file automatically
depending on how you call it:

=over

=item tidyall file [file...]

C<tidyall> will search upwards from the first I<file> for C<tidyall.ini>.

=item tidyall -a

C<tidyall> will search upwards from the current working directory for
C<tidyall.ini>.

=item tidyall -a --root-dir dir

C<tidyall> will expect to find C<tidyall.ini> in the specified root directory.

=back

=head2 Configuration format

The config file is in L<Config::INI|Config::INI> format. Here's a sample:

    [PerlTidy]
    argv = -noll -it=2
    select = **/*.{pl,pm,t}

    [PerlCritic]
    argv = -severity 3
    select = lib/**/*.pm
    ignore = lib/UtterHack.pm

    [PodTidy]
    select = lib/**/*.{pm,pod}

In order, the three sections declare:

=over

=item *

Apply C<PerlTidy> with settings "-noll -it=2" to all *.pl, *.pm, and *.t files.

=item *

Apply C<PerlCritic> with severity 3 to all Perl modules somewhere underneath
"lib/", except for C<lib/UtterHack.pm>.

=item *

Apply C<PodTidy> with default settings to all .pm and .pod files underneath
"lib/".

=back

=head2 Standard configuration elements

=over

=item [class]

The header of each configuration section refers to a tidyall I<plugin>. The
name is automatically prefixed with C<Code::TidyAll::Plugin::> unless it begins
with a '+', e.g.

    # Uses plugin Code::TidyAll::Plugin::PerlTidy
    [PerlTidy]

    # Uses plugin My::TidyAll::Plugin
    [+My::TidyAll::Plugin]

=item select

A L<File::Zglob|File::Zglob> pattern indicating which files to select, e.g.

    # All .pl and .pm files somewhere under bin, lib and t
    select = {bin,lib,t}/**/*.p[lm]

    # All .txt files anywhere in the project
    select = **/*.txt

The pattern is relative to the root directory and should have no leading slash.
 All standard glob characters (C<*>, C<?>, C<[]>, C<{}>) will work; in
addition, C<**> can be used to represent zero or more directories. See
C<File::Zglob> documentation for more details.

=item ignore

A C<File::Zglob> pattern, of the same format described above, indicating which
files to ignore.  This overrides C<select>. e.g.

    select = bin/**/*.pl
    ignore = bin/tmp/*.pl

=item argv

Many plugins (such as L<perltidy|Code::TidyAll::Plugin::PerlTidy>,
L<perlcritic|Code::TidyAll::Plugin::PerlCritic> and
L<podtidy|Code::TidyAll::Plugin::PodTidy>) take this option, which specifies
arguments to pass to the underlying command-line utility.

=back

=head1 LAST-PROCESSED CACHE

C<tidyall> keeps track of each file's signature after it was last processed. On
subsequent runs, it will only process a file if its signature has changed. The
cache is kept in files under the data dir.

You can turn off this behavior with C<--no-cache>.

=head1 BACKUPS

C<tidyall> will backup each file before modifying it. The timestamped backups
are kept in a separate directory hierarchy under the data dir.

Old backup files will be purged automatically as part of occasional C<tidyall>
runs. The duration specified in C<--backup-ttl> indicates both the minimum
amount of time backups should be kept, and the frequency that purges should be
run. It may be specified as "30m" or "4 hours" or any string acceptable to
L<Time::Duration::Parse|Time::Duration::Parse>. It defaults to "1h" (1 hour).

You can turn off backups with C<--no-backups>.

=head1 EXIT STATUS

C<tidyall> will exit with status 1 if any errors occurred while processing
files, and 0 otherwise.

=head1 ACKNOWLEDGEMENTS

Thanks to Jeff Thalhammer for helping me refine this API. Thanks to Jeff for
perlcritic, Steve Hancock for perltidy, and all the other authors of great open
source tidiers and validators.

=head1 SEE ALSO

L<Code::TidyAll|Code::TidyAll>

=head1 AUTHOR

Jonathan Swartz <swartz@pobox.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2011 by Jonathan Swartz.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut


__END__

