Code-TidyAll

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN

- cache\_model\_class

    The cache model class. Defaults to `Code::TidyAll::CacheModel`

- cache

    The cache instance (e.g. an instance of `Code::TidyAll::Cache` or a `CHI`
    instance.) An instance of `Code::TidyAll::Cache` is automatically instantiated
    by default.

- backup\_ttl
- check\_only

    If this is true, then we simply check that files pass validation steps and that
    tidying them does not change the file. Any changes from tidying are not
    actually written back to the file.

- no\_cleanup

    A boolean indicating if we should skip cleaning temporary files or not.
    Defaults to false.

README.md  view on Meta::CPAN

- inc

    An arrayref of directories to prepend to `@INC`. This can be set via the
    command-line as `-I`, but you can also set it in a config file.

    This affects both loading and running plugins.

- data\_dir
- iterations
- mode
- no\_backups
- no\_cache
- output\_suffix
- quiet
- root\_dir
- ignore
- verbose

    These options are the same as the equivalent `tidyall` command-line options,
    replacing dashes with underscore (e.g. the `backup-ttl` option becomes
    `backup_ttl` here).

- msg\_outputter

    This is a subroutine reference that is called whenever a message needs to be
    printed in some way. The sub receives a `sprintf()` format string followed by
    one or more parameters. The default sub used simply calls `printf "$format\n",
    @_` but [Test::Code::TidyAll](https://metacpan.org/pod/Test%3A%3ACode%3A%3ATidyAll) overrides this to use the `Test::Builder->diag` method.

## $tidyall->process\_paths( $path, ... )

bin/tidyall  view on Meta::CPAN

   -g, --git                Process all added/modified files according to git
   -l, --list               List each file along with the plugins it matches
   -m, --mode               Mode (e.g. "editor", "commit") - affects which plugins run
   -p <path>, --pipe <path> Read from STDIN, output to STDOUT/STDERR
   -r, --recursive          Descend recursively into directories listed on command line
   -j, --jobs               Number of parallel jobs to run - default is 1
   -s, --svn                Process all added/modified files according to svn
   -q, --quiet              Suppress output except for errors
   -v, --verbose            Show extra output
   -I I<path1,path2,...>    Add one or more paths to @INC
   --backup-ttl <duration>  Amount of time before backup files can be purged
   --check-only             Just check each file, don't modify
   --plugins <name>         Explicitly run only the given plugins
   --conf-file <path>       Relative or absolute path to conf file
   --conf-name <name>       Conf file name to search for
   --data-dir <path>        Contains metadata, defaults to root/.tidyall.d
   --iterations <count>     Number of times to repeat each transform - default is 1
   --no-backups             Don't back up files before processing
   --no-cache               Don't cache last processed times
   --no-cleanup             Don't clean up the temporary files
   --output-suffix <suffix> Suffix to add to tidied file
   --refresh-cache          Erase any existing cache info before processing each file
   --root-dir               Specify root directory explicitly
   --tidyall-class <class>  Subclass to use instead of Code::TidyAll
   --version                Show version
   -h, --help               Print help message
EOF

bin/tidyall  view on Meta::CPAN

    'g|git'           => \$git_files,
    'l|list'          => \$params{list_only},
    'm|mode=s'        => \$params{mode},
    'p|pipe=s'        => \$pipe,
    'r|recursive'     => \$params{recursive},
    'j|jobs=i'        => \$params{jobs},
    's|svn'           => \$svn_files,
    'q|quiet'         => \$params{quiet},
    'v|verbose'       => \$params{verbose},
    'I=s'             => \$inc_dirs,
    'backup-ttl=i'    => \$params{backup_ttl},
    'check-only'      => \$params{check_only},
    'plugins=s@'      => \$params{selected_plugins},
    'conf-file=s'     => \$conf_file,
    'conf-name=s'     => \$conf_name,
    'data-dir=s'      => \$params{data_dir},
    'iterations=i'    => \$iterations,
    'no-backups'      => \$params{no_backups},
    'no-cache'        => \$params{no_cache},
    'no-cleanup'      => \$params{no_cleanup},
    'output-suffix=s' => \$params{output_suffix},
    'refresh-cache'   => \$params{refresh_cache},
    'root-dir=s'      => \$params{root_dir},
    'tidyall-class=s' => \$params{tidyall_class},
    'version'         => \$version,
    'h|help'          => \$help,
) or usage();
version()                  if $version;

bin/tidyall  view on Meta::CPAN

    usage();
}

my @results = $ct->process_paths(@paths);
my $status  = ( grep { $_->error } @results ) ? 1 : 0;
exit($status);

sub handle_pipe {
    my ($pipe_filename) = @_;

    $params{$_} = 1 for ( 'no_backups', 'no_cache', 'quiet' );
    $params{$_} = 0 for ('verbose');

    $conf_file ||= $tidyall_class->find_conf_file( \@conf_names, $pipe_filename->parent );
    my $ct     = $tidyall_class->new_from_conf_file( $conf_file, %params );
    my $source = do { local $/; <STDIN> };

    # Merge stdout and stderr and output all to stderr, so that stdout is
    # dedicated to the tidied content
    #
    my $result;

bin/tidyall  view on Meta::CPAN

successful, tidyall exits with status 0. If an error occurs, tidyall outputs
the error message to STDERR, I<mirrors the input content> to STDOUT with no
changes, and exits with status 1. The mirroring means that you can safely pipe
to your destination regardless of whether an error occurs.

When specifying this option you must specify exactly one filename, relative or
absolute, which will be used to determine which plugins to apply and also where
the root directory and configuration file are. The file will not actually be
read and does need even need to exist.

This option implies --no-backups and --no-cache (since there's no actual file)
and --quiet (since we don't want to mix extraneous output with the tidied
result).

    # Read from STDIN and write to STDOUT, with appropriate plugins
    # for some/path.pl (which need not exist)
    #
    % tidyall --pipe some/path.pl

=item -r, --recursive

bin/tidyall  view on Meta::CPAN


=item -v, --verbose

Show extra output.

=item -I I<path1,path2,...>

Add one or more library paths to @INC, like Perl's -I. Useful if
--tidyall-class or plugins are in an alternate lib directory.

=item --backup-ttl I<duration>

Amount of time before backup files can be purged. Can be a number of seconds or
any string recognized by L<Time::Duration::Parse>, e.g. "4h" or "1day".
Defaults to "1h".

=item --check-only

Instead of actually tidying files, check if each file is tidied (i.e. if its
tidied version is equal to its current version) and consider it an error if
not. This is used by L<Test::Code::TidyAll> and the
L<svn|Code::TidyAll::SVN::Precommit> and L<git|Code::TidyAll::Git::Precommit>
pre-commit hooks, for example, to enforce that you've tidied your files.

bin/tidyall  view on Meta::CPAN

Specify relative or absolute path to conf file, instead of searching for it in
the usual way.

=item --conf-name I<name>

Specify a conf file name to search for instead of the defaults (C<tidyall.ini>
/ C<.tidyallrc>).

=item --data-dir I<path>

Contains data like backups and cache. Defaults to root_dir/.tidyall.d

=item --iterations I<count>

Run each tidier transform I<count> times. Default is 1.

In some cases (hopefully rare) the output from a tidier can be different if it
is applied multiple times. You may want to perform multiple iterations to make
sure the content "settles" into its final tidied form -- especially if the
tidiness is being enforced with a version-control hook or a test. Of course,
performance will suffer a little. You should rarely need to set this higher
than 2.

This only affects tidiers, not validators; e.g.
L<perlcritic|Code::TidyAll::Plugin::PerlCritic> and
L<jshint|Code::TidyAll::Plugin::JSHint> would still only be run once.

=item --no-backups

Don't backup files before processing.

=item --no-cache

Don't cache last processed times; process all files every time. See also
C<--refresh-cache>.

=item --no-cleanup

Don't clean up temporary files.

bin/tidyall  view on Meta::CPAN


Print help message

=back

=head2 Specifying options in configuration

Almost any command-line option can be specified at the top of the config file,
above the plugin sections. Replace dashes with underscores. e.g.

    backup_ttl = 4h
    iterations = 2
    tidyall_class = My::Code::TidyAll

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

    ...

If an option is passed in both places, the command-line takes precedence.

bin/tidyall  view on Meta::CPAN


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 force a refresh of the cache with C<--refresh-cache>, or turn off the
behavior entirely 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>. It defaults to "1h" (1 hour).

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

=head1 "MISSING" PREREQS

The C<Code::TidyAll> distribution intentionally does not depend on the prereqs
needed for each plugin. This means that if you want to use the
L<perltidy|Code::TidyAll::Plugin::PerlTidy>, you must install the L<Perl::Tidy>
module manually.

=head1 RELATED TOOLS

lib/Code/TidyAll.pm  view on Meta::CPAN

use Time::Duration::Parse qw(parse_duration);
use Try::Tiny;

use Moo 2.000000;

our $VERSION = '0.85';

sub default_conf_names { ( 'tidyall.ini', '.tidyallrc' ) }

# External
has backup_ttl => (
    is      => 'ro',
    isa     => t('NonEmptyStr'),
    default => '1 hour',
);

has cache => (
    is  => 'lazy',
    isa => object_can_type( methods => [qw( get set )] ),
);

lib/Code/TidyAll.pm  view on Meta::CPAN

    isa     => t('NonEmptyStr'),
    default => 'cli',
);

has msg_outputter => (
    is      => 'ro',
    isa     => t('CodeRef'),
    builder => '_build_msg_outputter',
);

has no_backups => (
    is  => 'ro',
    isa => t('Bool'),
);

has no_cache => (
    is  => 'ro',
    isa => t('Bool'),
);

has output_suffix => (

lib/Code/TidyAll.pm  view on Meta::CPAN

    isa     => t('Bool'),
    default => 0,
);

has inc => (
    is      => 'ro',
    isa     => t( 'ArrayRef', of => t('NonEmptyStr') ),
    default => sub { [] },
);

has _backup_dir => (
    is       => 'ro',
    isa      => t('Path'),
    init_arg => undef,
    lazy     => 1,
    builder  => '_build_backup_dir',
);

has _backup_ttl_secs => (
    is       => 'ro',
    isa      => t('Int'),
    init_arg => undef,
    lazy     => 1,
    builder  => '_build_backup_ttl_secs',
);

has _base_sig => (
    is       => 'ro',
    isa      => t('NonEmptyStr'),
    init_arg => undef,
    lazy     => 1,
    builder  => '_build_base_sig',
);

lib/Code/TidyAll.pm  view on Meta::CPAN

has _plugins_for_path => (
    is       => 'ro',
    isa      => t( 'HashRef', of => t('HashRef') ),
    init_arg => undef,
    lazy     => 1,
    default  => sub { {} },
);

with qw( Code::TidyAll::Role::HasIgnore Code::TidyAll::Role::Tempdir );

sub _build_backup_dir {
    my $self = shift;
    return $self->data_dir->child('backups');
}

sub _build_backup_ttl_secs {
    my $self = shift;
    return parse_duration( $self->backup_ttl );
}

sub _build_base_sig {
    my $self           = shift;
    my $active_plugins = join( q{|}, map { $_->name } @{ $self->_plugin_objects } );
    return $self->_sig( [ $Code::TidyAll::VERSION || 0, $active_plugins ] );
}

sub _sig {
    my ( $self, $data ) = @_;

lib/Code/TidyAll.pm  view on Meta::CPAN

    #
    if ( my @bad_params = grep { !$self->can($_) } keys(%$params) ) {
        die sprintf(
            'unknown constructor param%s %s for %s',
            @bad_params > 1 ? 's' : q{},
            join( ', ', sort map {qq['$_']} @bad_params ),
            ref($self)
        );
    }

    unless ( $self->no_backups ) {
        $self->_backup_dir->mkpath( { mode => 0775 } );
        $self->_purge_backups_periodically();
    }

    @INC = ( @{ $self->inc }, @INC );
}

sub _purge_backups_periodically {
    my ($self)             = @_;
    my $cache              = $self->cache;
    my $last_purge_backups = $cache->get('last_purge_backups') || 0;
    if ( time > $last_purge_backups + $self->_backup_ttl_secs ) {
        $self->_purge_backups();
        $cache->set( 'last_purge_backups', time() );
    }
}

sub _purge_backups {
    my ($self) = @_;
    $self->msg('purging old backups') if $self->verbose;
    find(
        {
            follow => 0,
            wanted => sub {
                unlink $_ if -f && /\.bak$/ && time > ( stat($_) )[9] + $self->_backup_ttl_secs;
            },
            no_chdir => 1
        },
        $self->_backup_dir,
    );
}

sub new_from_conf_file {
    my ( $class, $conf_file, %params ) = @_;

    $conf_file = path($conf_file);

    die qq{no such file '$conf_file'} unless $conf_file->is_file;
    my $conf_params = $class->_read_conf_file($conf_file);

lib/Code/TidyAll.pm  view on Meta::CPAN

    elsif ( $cache_model->is_cached ) {
        $self->msg( '[cached] %s', $path ) if $self->verbose;
        return Code::TidyAll::Result->new( path => $path, state => 'cached' );
    }

    my $contents = $cache_model->file_contents || $full_path->slurp_raw;
    my $result   = $self->process_source( $contents, $path );

    if ( $result->state eq 'tidied' ) {

        # backup original contents
        $self->_backup_file( $path, $contents );

        # write new contents out to disk
        $contents = $result->new_contents;

        # We don't use ->spew because that creates a new file and renames it,
        # losing the existing mode setting in the process.
        path( $full_path . $self->output_suffix )->append_raw( { truncate => 1 }, $contents );

        # change the in memory contents of the cache (but don't update yet)
        $cache_model->file_contents($contents) unless $self->output_suffix;

lib/Code/TidyAll.pm  view on Meta::CPAN

sub _cache_model_for {
    my ( $self, $path, $full_path ) = @_;
    return $self->cache_model_class->new(
        path      => $path,
        full_path => $full_path,
        ( $self->no_cache ? () : ( cache_engine => $self->cache ) ),
        base_sig => $self->_base_sig,
    );
}

sub _backup_file {
    my ( $self, $path, $contents ) = @_;
    unless ( $self->no_backups ) {
        my $backup_file = $self->_backup_dir->child( $self->_backup_filename($path) );
        $backup_file->parent->mkpath( { mode => 0775 } );
        $backup_file->spew_raw($contents);
    }
}

sub _backup_filename {
    my ( $self, $path ) = @_;

    return join( q{}, $path, '-', time2str( '%Y%m%d-%H%M%S', time ), '.bak' );
}

sub process_source {
    my ( $self, $contents, $path ) = @_;

    $path = path($path);

lib/Code/TidyAll.pm  view on Meta::CPAN

=item * cache_model_class

The cache model class. Defaults to C<Code::TidyAll::CacheModel>

=item * cache

The cache instance (e.g. an instance of C<Code::TidyAll::Cache> or a C<CHI>
instance.) An instance of C<Code::TidyAll::Cache> is automatically instantiated
by default.

=item * backup_ttl

=item * check_only

If this is true, then we simply check that files pass validation steps and that
tidying them does not change the file. Any changes from tidying are not
actually written back to the file.

=item * no_cleanup

A boolean indicating if we should skip cleaning temporary files or not.

lib/Code/TidyAll.pm  view on Meta::CPAN

command-line as C<-I>, but you can also set it in a config file.

This affects both loading and running plugins.

=item * data_dir

=item * iterations

=item * mode

=item * no_backups

=item * no_cache

=item * output_suffix

=item * quiet

=item * root_dir

=item * ignore

=item * verbose

These options are the same as the equivalent C<tidyall> command-line options,
replacing dashes with underscore (e.g. the C<backup-ttl> option becomes
C<backup_ttl> here).

=item * msg_outputter

This is a subroutine reference that is called whenever a message needs to be
printed in some way. The sub receives a C<sprintf()> format string followed by
one or more parameters. The default sub used simply calls C<printf "$format\n",
@_> but L<Test::Code::TidyAll> overrides this to use the C<<
Test::Builder->diag >> method.

=back

lib/Code/TidyAll/Git/Prereceive.pm  view on Meta::CPAN

        my $contents = $self->get_file_contents( $rel_file, $commit )
            or die sprintf( q{could not find file '%s' in repo root}, $rel_file );
        $temp_dir->child($rel_file)->spew($contents);
    }
    my $tidyall = $self->tidyall_class->new_from_conf_file(
        "$temp_dir/" . $conf_file,
        mode  => 'commit',
        quiet => 1,
        %{ $self->tidyall_options },
        no_cache   => 1,
        no_backups => 1,
        check_only => 1,
    );
    return $tidyall;
}

sub get_changed_files {
    my ( $self, $base, $commit ) = @_;
    my $output = capturex( $self->git_path, 'diff', '--numstat', '--name-only', "$base..$commit" );
    my @files  = grep {/\S/} split( "\n", $output );
    return @files;

lib/Code/TidyAll/Plugin/PodTidy.pm  view on Meta::CPAN

    isa => t('PositiveInt'),
);

sub transform_file {
    my ( $self, $file ) = @_;

    my $output = capture_merged {
        Pod::Tidy::tidy_files(
            files    => [ $file->stringify ],
            inplace  => 1,
            nobackup => 1,
            verbose  => 1,
            ( $self->columns ? ( columns => $self->columns ) : () ),
        );
    };
    die $output if $output =~ /\S/ && $output !~ /does not contain Pod/;
}

1;

# ABSTRACT: Use podtidy with tidyall

php/PHP_CodeSniffer/scripts/ValidatePEAR/FileList.php  view on Meta::CPAN

     */
    protected $fileIterator;

    /**
     * Base regex to use if no filter regex is provided.
     *
     * Matches based on:
     * - File path starts with the project root (replacement done in constructor).
     * - Don't match .git/ files.
     * - Don't match dot files, i.e. "." or "..".
     * - Don't match backup files.
     * - Match everything else in a case-insensitive manner.
     *
     * @var string
     */
    private $baseRegex = '`^%s(?!\.git/)(?!(.*/)?\.+$)(?!.*\.(bak|orig)).*$`Dix';


    /**
     * Constructor.
     *

t/lib/TestFor/Code/TidyAll/Basic.pm  view on Meta::CPAN


sub test_plugin {"+TestHelper::Plugin::$_[0]"}
my %UpperText
    = ( test_plugin('UpperText') => { select => '**/*.txt', ignore => 'plugin_ignore/*' } );
my %ReverseFoo = ( test_plugin('ReverseFoo') => { select => '**/foo*' } );
my %RepeatFoo  = ( test_plugin('RepeatFoo')  => { select => '**/foo*' } );
my %CheckUpper = ( test_plugin('CheckUpper') => { select => '**/*.txt' } );
my %AToZ       = ( test_plugin('AToZ')       => { select => '**/*.txt' } );

my $cli_conf = <<'EOF';
backup_ttl = 15m
verbose = 1
ignore  = global_ignore/*

[+TestHelper::Plugin::UpperText]
select = **/*.txt
ignore = plugin_ignore/*

[+TestHelper::Plugin::RepeatFoo]
select = **/foo*
times = 3

t/lib/TestFor/Code/TidyAll/Basic.pm  view on Meta::CPAN

                    ( $state eq 'normal' ? () : ( $state => 1 ) )
                );
                $ct->process_paths("$root_dir/foo.txt");
            };
            if ($error) {
                like( $output, qr/non-alpha content found/, "non-alpha content found ($state)" );
            }
            else {
                is( $output, "[tidied]  foo.txt\n" ) if $state eq 'normal';
                is( $output, q{} )                   if $state eq 'quiet';
                like( $output, qr/purging old backups/, "purging old backups ($state)" )
                    if $state eq 'verbose';
                like(
                    $output,
                    qr/\[tidied\]  foo\.txt \(\+TestHelper::Plugin::UpperText\)/s,
                    "foo.txt ($state)"
                ) if $state eq 'verbose';
            }
        }
    }
}

t/lib/TestFor/Code/TidyAll/Basic.pm  view on Meta::CPAN

    my $ct       = Code::TidyAll->new(
        plugins    => { test_plugin('RepeatFoo') => { select => '**/foo*', times => 3 } },
        root_dir   => $root_dir,
        iterations => 2
    );
    my $file = $root_dir->child('foo.txt');
    $ct->process_paths($file);
    is( $file->slurp, scalar( 'abc' x 9 ), '3^2 = 9' );
}

sub test_caching_and_backups : Tests {
    my $self = shift;

    my @chi_or_no_chi = (q{});
    if ( eval 'use CHI; 1' ) {
        push @chi_or_no_chi, 'chi';
    }

    foreach my $chi (@chi_or_no_chi) {
        foreach my $cache_model_class (
            qw(
            Code::TidyAll::CacheModel
            Code::TidyAll::CacheModel::Shared
            )
        ) {
            foreach my $no_cache ( 0 .. 1 ) {
                foreach my $no_backups ( 0 .. 1 ) {
                    my $desc
                        = "(no_cache=$no_cache, no_backups=$no_backups, model=$cache_model_class, cache_class=$chi)";
                    my $root_dir = $self->create_dir( { 'foo.txt' => 'abc' } );
                    my $ct       = Code::TidyAll->new(
                        plugins           => {%UpperText},
                        root_dir          => $root_dir,
                        cache_model_class => $cache_model_class,
                        ( $no_cache   ? ( no_cache   => 1 )      : () ),
                        ( $no_backups ? ( no_backups => 1 )      : () ),
                        ( $chi        ? ( cache      => _chi() ) : () ),
                    );
                    my $output;
                    my $file = path( $root_dir, 'foo.txt' );
                    my $go   = sub {
                        $output = capture_stdout { $ct->process_paths($file) };
                    };

                    $go->();
                    is( $file->slurp, "ABC",                 "first file change $desc" );

t/lib/TestFor/Code/TidyAll/Basic.pm  view on Meta::CPAN


                    $file->spew('ABCD');
                    $go->();
                    is( $output, "[checked] foo.txt\n", "third output $desc" );

                    $file->spew('def');
                    $go->();
                    is( $file->slurp, 'DEF',                 "fourth file change $desc" );
                    is( $output,      "[tidied]  foo.txt\n", "fourth output $desc" );

                    my $backup_dir = $ct->data_dir->child('backups');
                    $backup_dir->mkpath( { mode => 0775 } );
                    my @files;
                    find(
                        {
                            follow   => 0,
                            wanted   => sub { push @files, $_ if -f },
                            no_chdir => 1
                        },
                        $backup_dir
                    );

                    if ($no_backups) {
                        ok( @files == 0, "no backup files $desc" );
                    }
                    else {
                        ok(
                            scalar(@files) == 1 || scalar(@files) == 2,
                            "1 or 2 backup files $desc"
                        );
                        foreach my $file (@files) {
                            like(
                                $file,
                                qr|\.tidyall\.d/backups/foo\.txt-\d+-\d+\.bak|,
                                "backup filename $desc"
                            );
                        }
                    }
                }
            }
        }
    }
}

sub _chi {

t/lib/TestFor/Code/TidyAll/Basic.pm  view on Meta::CPAN

                $conf_file->spew($cli_conf);

                $root_dir->child('foo.txt')->spew('hello');
                my $output = capture_stdout {
                    $run->( $root_dir->child('foo.txt'), '-v' );
                };

                my ($params_msg)
                    = ( $output =~ /constructing Code::TidyAll with these params:(.*)/ );
                ok( defined($params_msg), 'params msg' );
                like( $params_msg, qr/backup_ttl => '15m'/, 'backup_ttl' );
                like( $params_msg, qr/verbose => '?1'?/,    'verbose' );
                like(
                    $params_msg, qr/\Qroot_dir => '$root_dir'\E/,
                    'root_dir'
                );
                like(
                    $output,
                    qr/\[tidied\]  foo.txt \(.*RepeatFoo, .*UpperText\)/,
                    'foo.txt'
                );

t/lib/TestFor/Code/TidyAll/Conf.pm  view on Meta::CPAN

package TestFor::Code::TidyAll::Conf;

use Code::TidyAll;
use Code::TidyAll::Util qw(tempdir_simple);
use Test::Class::Most parent => 'TestHelper::Test::Class';

my @tests = (
    {
        name   => 'valid config',
        config => <<'EOF',
backup_ttl = 5m
no_cache = 1
inc = /foo
inc = /bar

[+TestHelper::Plugin::UpperText]
select = **/*.txt

[+TestHelper::Plugin::RepeatFoo]
select = **/foo*
select = **/bar*
times = 3
EOF
        methods => {
            backup_ttl       => '5m',
            _backup_ttl_secs => '300',
            inc              => [ '/foo', '/bar' ],
            no_backups       => undef,
            no_cache         => 1,
            plugins          => {
                '+TestHelper::Plugin::UpperText' => {
                    select => ['**/*.txt'],
                },
                '+TestHelper::Plugin::RepeatFoo' => {
                    select => [ '**/foo*', '**/bar*' ],
                    times  => 3,
                },
            },



( run in 1.897 second using v1.01-cache-2.11-cpan-49f99fa48dc )