Agent-TCLI

 view release on metacpan or  search on metacpan

Build.PL  view on Meta::CPAN

    license             => 'perl',
    dist_author         => 'Eric Hacker <hacker@cpan.org>',
    dist_abstract		=> 'Transactional Contextual Line Interface Agent',
    dist_version_from   => 'lib/Agent/TCLI.pm',
    requires => {
		'Data::Dump' 	            => '0',
#		'TimeDate'					=> '1.16',
		'Date::Parse'				=> '1.16',
		'File::ShareDir'			=> '0.05',
		'FormValidator::Simple'		=> '0',
		'Getopt::Lucid'             => '0.16',
#		'Scalar::List::Utils'        => '1.18',
		'Scalar::Util' 		    	=> '1.18',
		'Module::Build'				=> '0',
		'Net::Jabber'		        => 'undef',
		'Net::XMPP'		   	     	=> '1.02',
		'NetAddr::IP'		        => '3',
		'Object::InsideOut'			=> '3.07',
		'POE'						=> '0.9',
		'POE::Component::Child'		=> '0',
		'Test::Simple'              => '0.62',

MANIFEST  view on Meta::CPAN

lib/Agent/TCLI/User.pm
lib/auto/Agent/TCLI/Control/config.xml
lib/auto/Agent/TCLI/Package/Base/config.xml
Makefile.PL
MANIFEST			This list of files
META.yml
README
t/00.load.t
t/pod.t
t/TCLI.Command.BuildCommandLine.t
t/TCLI.Command.GetoptLucid.t
t/TCLI.Command.t
t/TCLI.Control.Interactive.t
t/TCLI.Control.t
t/TCLI.Package.Base.t
t/TCLI.Package.Base.xml
t/TCLI.Package.Tail.t
t/TCLI.Package.XMPP.t
t/TCLI.Parameter.t
t/TCLI.Request.t
t/TCLI.Transport.Test.t

META.yml  view on Meta::CPAN

  - Eric Hacker <hacker@cpan.org>
abstract: Transactional Contextual Line Interface Agent
license: perl
resources:
  license: http://dev.perl.org/licenses/
requires:
  Data::Dump: 0
  Date::Parse: 1.16
  File::ShareDir: 0.05
  FormValidator::Simple: 0
  Getopt::Lucid: 0.16
  Module::Build: 0
  Net::Jabber: undef
  Net::XMPP: 1.02
  NetAddr::IP: 3
  Object::InsideOut: 3.07
  POE: 0.9
  POE::Component::Child: 0
  Scalar::Util: 1.18
  Test::Simple: 0.62
  XML::Simple: 0

README  view on Meta::CPAN

    ./Build install

Alternatively, one may load from CPAN, or hopefully a PPM repository on Windows
machines.

DEPENDENCIES

		'Data::Dump' 	            => '0',
		'TimeDate'					=> '1.16',
		'FormValidator::Simple'		=> '0',
		'Getopt::Lucid'             => '0.16',
		'Scalar::List::Utils'        => '1.18',
		'Module::Build'				=> '0',
		'Net::Jabber'		        => 'undef',
		'Net::XMPP'		   	     	=> '1.02',
		'NetAddr::IP'		        => '3',
		'Object::InsideOut'			=> '3.07',
		'Test::Simple'              => '0.62',
		'YAML::Syck'				=> '0'

USING

bin/agent_tail.pl  view on Meta::CPAN


This script is free software; you may redistribute it
and/or modify it under the same terms as Perl itself.

=cut

# Useful for debugging or just seeing what the Agent is doing.
sub VERBOSE () { 0 }

# Process optional parameters from the command line and assign defaults.
use Getopt::Lucid qw(:all);

my ($opt, $verbose,$domain,$username,$password,$resource,$host);

eval {$opt = Getopt::Lucid->getopt([
		Param("domain|d"),
		Param("username|u"),
		Param("password|p"),
		Param("resource|r"),
		Param("host"),
		Counter("verbose|v"),
		Switch("help"),
		Switch("man"),
	])};

lib/Agent/TCLI/Command.pm  view on Meta::CPAN

to load or define Parameters before Commands that use them.

=cut

use warnings;
use strict;

our $VERSION = '0.030.'.sprintf "%04d", (qw($Id: Command.pm 59 2007-04-30 11:24:24Z hacker $))[2];

use Object::InsideOut qw(Agent::TCLI::Base);
use Getopt::Lucid qw(:all);
use FormValidator::Simple;

=head2 ATTRIBUTES

The following attributes are accessible through standard named accessor/mutator
methods unless otherwise noted

=over

=item name

lib/Agent/TCLI/Command.pm  view on Meta::CPAN

#	$cmdhash{'topic'} 	= $topic[$$self] 	if (defined($topic[$$self]));
#	$cmdhash{'contexts'}	= $contexts[$$self] if (defined($contexts[$$self]));
#	$cmdhash{'call_style'}	= $call_style[$$self] if (defined($call_style[$$self]));
#	$cmdhash{'handler'}	= $handler[$$self] 	if (defined($handler[$$self]));
#	$cmdhash{'start'}	= $start[$$self] 	if (defined($start[$$self]));
#	$cmdhash{'stop'}	= $stop[$$self] 	if (defined($stop[$$self]));
#
#  	return ( \%cmdhash );
#}

=item GetoptLucid( $kernel, $request)

Returns an option hash keyed on parameter after the arguments have bee parsed
by Getopt::Lucid. Will respond itself if there is an error and return nothing.

Takes the POE Kernel and the request as args.

=cut

sub GetoptLucid {
	my ($self, $kernel, $request, $package) = @_;

	my (@options, $func);

	# Creat an array for Getopt::Lucid
	foreach my $param ( values %{ $self->parameters }  )
	{
#		my $name = defined($param->aliases)
#			? $param->name.'|'.$param->aliases
#			: $param->name;
		my $name = $param->name;

		# don't put as required if default is set.
		if ( exists $self->required->{$name} &&
			( defined ($package) &&

lib/Agent/TCLI/Command.pm  view on Meta::CPAN

			no strict 'refs';
			push(@options, $param->type->($param->Alias)->required() );
		}
		else
		{
			no strict 'refs';
			push(@options, $param->type->($param->Alias) );
		}
	}

	$self->Verbose("GetoptLucid: options ",2,\@options);

	my $opt;

#	$self->Verbose("GetoptLucid: request args",1,$request->args );

	# Parse the args using parameters.
	eval {$opt = Getopt::Lucid->getopt(
		\@options,
		$request->args,
		);
	};

	# If it went bad, error and return nothing.
	if( $@ )
	{
		$self->Verbose('GetoptLucid: got ('.$@.') ');
		$request->Respond($kernel,  "Invalid Args: $@ !", 400);
		return (0);
	}

	return( $opt );
}

=item Validate( <kernel>, <request>, <package> )

Returns a hash keyed on parameter after the arguments have been parsed
by Getopt::Lucid and validated by FormValidator::Simple as per the constraints
specified in the Parameter or Command definitions.
Will respond itself if there is an error and return nothing.

Takes the POE Kernel, the Request, and the Package as args.

=cut

sub Validate {
	my ($self, $kernel, $request, $package) = @_;

	# Getopt will send error if problem.
	return unless (my $opt  = $self->GetoptLucid($kernel, $request, $package) );

#	my %args = $opt->options;
	my %args = $self->ApplyDefaults($opt, $package, $request->input );

#	$self->Verbose("Validate: param",1,\%args);
#	$self->Verbose('Validate: $request->input ',1,$request->input);

	# are there any left?
	if (keys %args == 0 )
	{

lib/Agent/TCLI/Command.pm  view on Meta::CPAN

Returns a hash keyed on parameter after the defaults from the Package
attributes have been applied. This is used during the Validate method.

=cut

sub ApplyDefaults {
	my ($self, $opt, $package, $input ) = @_;

	my %defaults;

	# Creat defaults hash for Getopt::Lucid
	foreach my $param ( values %{ $self->parameters }  )
	{
		# add to the default hash if an attribute exists in the package
		my $acc = $param->name;
		if (defined( $package ) &&
			$package->can( $acc ) &&
			defined( $package->$acc )
			)
		{
			$defaults{$acc} = $package->$acc;

lib/Agent/TCLI/Command.pm  view on Meta::CPAN

			defined( $package->$key ) ) )
			);
	}

	return( %opt );
}

=item BuildCommandLine( <param_hash>, <with_cmd> )

Returns a hash keyed on parameter after the arguments have been parsed
by Getopt::Lucid and validated by FormValidator::Simple as per the constraints
specified in the Parameter or Command definitions.
Will respond itself if there is an error and return nothing.

Takes the POE Kernel, the Request, and the Package as args.

=cut

sub BuildCommandLine {
	my ($self, $param_hash, $with_cmd ) = @_;

lib/Agent/TCLI/Command.pm  view on Meta::CPAN

inherits methods from both. Please refer to their documentation for more
details.

=head1 AUTHOR

Eric Hacker	 E<lt>hacker at cpan.orgE<gt>

=head1 BUGS

When naming commands in the preinit commands hash or loading from loadyaml()
it is easy to accidentally
duplicate names and cause commands not to load. The author expects that when he
makes this a habit, he'll try to fix it by doing something better than a loading
a hash with no validation.

Most command packages process args in an eval statement which will sometimes
return rather gnarly detailed traces back to the user. This is not a security issue
because open source software is not a black box where such obscurity might
be relied upon (albeit ineffectively), but it is a bug.

SHOULDS and MUSTS are currently not always enforced.

lib/Agent/TCLI/Package/Base.pm  view on Meta::CPAN


=cut

use warnings;
use strict;
use Carp;
use Object::InsideOut qw(Agent::TCLI::Base);

use POE;
use Scalar::Util  qw(blessed looks_like_number);
use Getopt::Lucid;
use YAML::Syck;
use XML::Simple;
use File::ShareDir;
#use FormValidator::Simple;

$YAML::Syck::Headless = 1;
$YAML::Syck::SortKeys = 1;

our $VERSION = '0.030.'.sprintf "%04d", (qw($Id: Base.pm 62 2007-05-03 15:55:17Z hacker $))[2];

lib/Agent/TCLI/Package/Tail.pm  view on Meta::CPAN


use Object::InsideOut qw( Agent::TCLI::Package::Base );

use POE qw(Wheel::FollowTail);

use Agent::TCLI::Command;
use Agent::TCLI::Parameter;
use Agent::TCLI::Package::Tail::Line;
use Agent::TCLI::Package::Tail::Test;

use Getopt::Lucid qw(:all);

our $VERSION = '0.030.'.sprintf "%04d", (qw($Id: Tail.pm 59 2007-04-30 11:24:24Z hacker $))[2];

=head2 ATTRIBUTES

These attrbiutes are generally internal and are probably only useful to
someone trying to enhance the functionality of this Package module.
It would be unusual to set any of these attributes on creation of the
package for an Agent. That doesn't mean you can't.

lib/Agent/TCLI/Package/Tail.pm  view on Meta::CPAN

    my ($kernel,  $self, $sender, $request, ) =
      @_[KERNEL, OBJECT,  SENDER,     ARG0, ];
	$self->Verbose("test: request ".$request->id." input(".$request->input.") ",1);

	my $txt = '';
	my $opt;
	my $sub_command = $request->command->[0];
	my $command = $request->command->[1];

	# break down args
	eval { $opt = Getopt::Lucid->getopt( [
		Param("like"),
		Param("unlike"),
		Param("ok"),
		Param("name"),
		Param("max_lines|l"),
		Param("match_times|t"),
		Param("ttl"),
		Switch('ordered'),
		Switch('cache')->default(1),
		Switch("verbose|v"),
		Switch("feedback|f"),
	], $request->args )};

	if( $@ )
	{
		$self->Verbose('set: getopt lucid got ('.$@.') ');
		$request->Respond($kernel,  "Invalid Args: $@ !", 400);
		return;
	}

   	# Validate args
   	# Need to evolve this into being more automated code but not sure how yet.
   	# Probably should check that like and unlike and ok are not all set at once.
   	# someday....
	$txt .= $self->NotRegex(qr($opt->get_like), "like" );
	$txt .= $self->NotRegex(qr($opt->get_unlike), "unlike");

lib/Agent/TCLI/Package/Tail.pm  view on Meta::CPAN


sub file {
    my ($kernel,  $self, $sender, $request, ) =
      @_[KERNEL, OBJECT,  SENDER,     ARG0, ];

	my $txt = '';
	my $opt;
	my $command = $request->command->[0];

	# break down args
	eval { $opt = Getopt::Lucid->getopt( [
		Param("file")->required(),
		Param("filter"),
		Param("interval"),
		Param("seek"),
		Param("seekback"),
	], $request->args )};

	if( $@ )
	{
		$self->Verbose('file: getopt lucid got ('.$@.') ');
		$request->Respond($kernel,  "Invalid Args: $@ !", 400);
		return;
	}

   	# Validate args
   	# Need to evolve this into being more automated code but not sure how yet.
	$txt .= $self->NotScalar($opt->get_file, "file" );
	$txt .= $self->NotType($opt->get_filter, "filter", qr(POE::Filter));
	$txt .= $self->NotPosInt($opt->get_interval, "interval" );
	$txt .= $self->NotPosInt($opt->get_seek, "seek");

lib/Agent/TCLI/Package/Tail.pm  view on Meta::CPAN

    my ($kernel,  $self, $sender, $request, ) =
      @_[KERNEL, OBJECT,  SENDER,     ARG0, ];

	my $txt = '';
	my $opt;
	my $command = $request->command->[0];

	# TODO a way to unset/restore defaults....

	# break down args
	eval { $opt = Getopt::Lucid->getopt( [
		Counter("test_verbose"),
		Counter("test_feedback"),
		Param("ordered"),
		Param("interval"),
		Param("line_max_cache"),
		Param("line_hold_time"),
		Param("test_max_lines"),
		Param("test_match_times"),
		Param("test_ttl"),
	], $request->args )};

	if( $@ )
	{
		$self->Verbose('set: getopt lucid got ('.$@.') ');
		$request->Respond($kernel,  "Invalid Args: $@ !", 400);
		return;
	}

   	# Validate args
   	# Need to evolve this into being more automated code but not sure how yet.
	$txt .= $self->NotPosInt($opt->get_test_verbose, "test_verbose", 'set');
	$txt .= $self->NotPosInt($opt->get_test_feedback, "test_feedback", 'set');
	$txt .= $self->NotPosInt($opt->get_ordered, "ordered", 'set');
	$txt .= $self->NotPosInt($opt->get_interval, "interval", 'set');

lib/Agent/TCLI/Package/XMPP.pm  view on Meta::CPAN


=cut

use warnings;
use strict;

use POE;
use Agent::TCLI::Command;
use Agent::TCLI::Parameter;
use Agent::TCLI::User;
use Getopt::Lucid qw(:all);

use Object::InsideOut qw(Agent::TCLI::Package::Base);

our $VERSION = '0.030.'.sprintf "%04d", (qw($Id: XMPP.pm 59 2007-04-30 11:24:24Z hacker $))[2];

=head2 ATTRIBUTES

The following attributes are accessible through standard <attribute>
methods unless otherwise noted.

lib/Agent/TCLI/Parameter.pm  view on Meta::CPAN

	}

=head1 DESCRIPTION

Parameters are the arguements supplied with a command. TCLI defines them as
objects to make it easier to provide several necessary interface features
in a consistent manner. One can use the new function to create Parameters
to load into a package, but the author prefers the YAML syntax as it is
easier to work with.

Arguement parsing may be done with Getopt::Lucid. One should define the type
if using the provided parsing.

Arguement validation may be performed using FormValidator::Simple constraints
as defined in the parameter. Otherwise it should be performed within the
Package subroutine handling the command.

Typically each Package will have a field defined with a standard
accessor/mutator that represents the default value to be used for the
parameter when the command the command is called. This field can be
manually defined in the Package, or it can be autocreated upon parameter
loading within the Package. If necessary, the class filed may be used to
set the Object::InsideOut type to be used for the field.

The reason for the use of Parameter and Command objects is to push a Package
to be as data driven as possible, with only the only code being the actual
command logic. It was decided that it would be best to evolve towards that
goal, rather than try to get it right from the outset. So what you see what
you get.

=cut

use warnings;
use strict;

use Object::InsideOut qw(Agent::TCLI::Base);

lib/Agent/TCLI/Parameter.pm  view on Meta::CPAN


B<name> should only contain scalar values.

=cut
my @name			:Field
#					:Type('scalar')
					:All('name');

=item aliases

The aliases will be used by Getopt::Lucid in addition to the name when
parsing the arguments to a command. This allows one to create
variations on the argument name.
This is useful for verbose and other times when names might clash. One can
name the argument I<command_verbose> and create an alias of I<verbose>.

If B<aliases> are defined, they will be appended to the name in the Getopt::Lucid
specification. B<aliases> should being with an alias with
each subsequent alias separated by the vertical bar character. E.g.:

  name: command_verbose
  aliases: "verbose|v"

  means name "command_verbose", alias "verbose" and alias "v"

B<aliases> should only contain scalar values. When represented in YAML, they
should be quoted to keep YAML from trying to interpret the bars.

=cut
my @aliases			:Field
#					:Type('scalar')
					:All('aliases');

=item type

The type will be used by Getopt::Lucid to parse the arguments into the
parameters. It will also be used in a future HTTP inerface to determine
what type of form field to present to the user. Refer to Getopt::Lucid
for the complete details on how it works. A summary of the Getopt::Lucid
supported types:

=over 8

=item Switch -- a true/fals value

=item Counter -- a numerical counter

=item Param -- a variable taking an argument

lib/Agent/TCLI/Parameter.pm  view on Meta::CPAN


See the attributes above for a description of the available attributes.

The preferred method of creating a Parameter object for a Package module
is to use the LoadYaml command in the module. This will create the object,
and insert it correctly into the Package parameter store.

=item Alias ()

Alias simply returns the name and aliases joined togetehr with a
bar for use in Getopt::Lucid or a regular expression. If the name of the
parameter is foo, and the aliases is bar, then $param->alias returns foo|bar.

=cut

sub Alias {
  my $self = shift;
  if ( $self->aliases )
  {
  	return ( $self->name."|".$self->aliases )
  }

lib/Agent/TCLI/Parameter.pm  view on Meta::CPAN

This module is an Object::InsideOut object that inherits from Agent::TCLI::Base. It
inherits methods from both. Please refer to their documentation for more
details.

=head1 AUTHOR

Eric Hacker	 E<lt>hacker at cpan.orgE<gt>

=head2 BUGS

When naming parametersit is easy to accidentally
duplicate names and cause problems. The author expects that when he
makes this a habit, he'll try to fix it by doing something better than a loading
a hash with no validation.

SHOULDS and MUSTS are currently not always enforced.

Test scripts not thorough enough.

Probably many others.

lib/Agent/TCLI/Transport/Test.pm  view on Meta::CPAN

=cut

sub PostResponse {
	my ($kernel,  $self, $sender, $response) =
  	  @_[KERNEL, OBJECT,  SENDER,      ARG0];
	$self->Verbose($self->alias.":PostResponse: sender(".$sender->ID.") Code(".$response->code.") \n");

	# Test always terminates a response transmission. The buck stops here,
	# unlike other transports

	# TODO Need to figure out how to decide it is time to start checking the tests!

	# Hmm. I donn't want to optimize this better with another object right now.
	# Push response into a responses array in a hash keyed on id.
	push( @{ $responses[$$self]->{$response->id} }, $response  );

	$self->Verbose($self->alias.":PostResponse: responses(".@{ $responses[$$self]->{$response->id} }.
		") ",3,$responses[$$self]->{$response->id} );

	# Work off of the first response for tracking.

t/TCLI.Command.BuildCommandLine.t  view on Meta::CPAN

#!/usr/bin/env perl
# $Id: TCLI.Command.BuildCommandLine.t 48 2007-04-11 12:43:07Z hacker $

use warnings;
use strict;

use Test::More tests => 13;
use Agent::TCLI::Parameter;
#use Agent::TCLI::Request;
#use Getopt::Lucid;
#use POE;
#
#use Data::Dump qw(pp);

# TASK Test suite is not complete. Need more testing for catching errors.
BEGIN {
    use_ok('Agent::TCLI::Command');
}


t/TCLI.Command.GetoptLucid.t  view on Meta::CPAN

#!/usr/bin/env perl
# $Id: TCLI.Command.GetoptLucid.t 57 2007-04-30 11:07:22Z hacker $

use Test::More tests => 36;
use Agent::TCLI::Parameter;
use Agent::TCLI::Request;
use Getopt::Lucid;
use POE;

use Data::Dump qw(pp);

# TASK Test suite is not complete. Need testing for catching errors.
BEGIN {
    use_ok('Agent::TCLI::Command');
}

my $request = Agent::TCLI::Request->new({

t/TCLI.Command.GetoptLucid.t  view on Meta::CPAN

# Test command get-set methods
is($test1->command,'test1', '$test1->command get from init args');
ok($test2->command('test2'),'$test2->command set ');
is($test2->command,'test2', '$test2->command get from set');

# Test handler get-set methods
is($test1->handler,'cmd1', '$test1->handler get from init args');
ok($test2->handler('cmd2'),'$test2->handler set ');
is($test2->handler,'cmd2', '$test2->handler get from set');

# Test GetoptLucid
my $testee = "GetoptLucid";

$request->args([qw(paramint 7 verbose)]);

my $opt1 = $test1->GetoptLucid($poe_kernel, $request );

is($opt1->get_paramint,7,"$testee paramint ok");
is($opt1->get_test_verbose,1,"$testee verbose ok");

$request->args([qw(paramA AAAAA verbose)]);

my $opt2 = $test2->GetoptLucid($poe_kernel, $request );

is($opt2->get_paramA,'AAAAA',"$testee paramA ok");
is($opt2->get_test_verbose,1,"$testee verbose ok");

# Test Validator
my $testee = "Validator";

$request->args([qw(paramint 7 verbose)]);

$opt1 = $test1->Validate($poe_kernel, $request);

t/TCLI.Control.Interactive.t  view on Meta::CPAN

# $Id: TCLI.Control.Interactive.t 62 2007-05-03 15:55:17Z hacker $

use warnings;
use strict;
use Test::More tests => 49;
#use Test::More qw(no_plan);


# TASK Test suite is not complete. Need more testing for catching errors.

use Getopt::Lucid qw(:all);

sub VERBOSE () { 0 }

my ($opt, $verbose, $poe_td, $poe_te);

eval {$opt = Getopt::Lucid->getopt([
		Counter("poe_debug|d"),
		Counter("poe_event|e"),
		Counter("verbose|v"),
		Switch("blib|b"),
	])};
if($@) {die "ERROR: $@";}

if ($opt->get_blib)
{
	use lib 'blib/lib';

t/TCLI.Control.t  view on Meta::CPAN

#!/usr/bin/env perl
# $Id: TCLI.Control.t 62 2007-05-03 15:55:17Z hacker $

use warnings;
use strict;
use Test::More tests => 402;

# TASK Test suite is not complete. Need more testing for catching errors.

use Getopt::Lucid qw(:all);

sub VERBOSE () { 0 }

my ($opt, $verbose, $poe_td, $poe_te);

eval {$opt = Getopt::Lucid->getopt([
		Counter("poe_debug|d"),
		Counter("poe_event|e"),
		Counter("verbose|v"),
		Switch("blib|b"),
	])};
if($@) {die "ERROR: $@";}

if ($opt->get_blib)
{
	use lib 'blib/lib';

t/TCLI.Package.XMPP.t  view on Meta::CPAN

#!/usr/bin/env perl
# $Id: TCLI.Package.XMPP.t 49 2007-04-25 10:32:36Z hacker $

use Test::More tests => 32;
use lib 'blib/lib';
use warnings;
use strict;

use Getopt::Lucid qw(:all);

sub VERBOSE () { 0 }

my ($opt, $verbose,$domain,$username,$password,$host, $poe_td, $poe_te);

eval {$opt = Getopt::Lucid->getopt([
		Param("domain"),
		Param("username|u"),
		Param("password|p"),
		Param("host"),
		Counter("poe_debug|d"),
		Counter("poe_event|e"),
		Counter("xmpp_debug|x"),
		Counter("verbose|v"),
	])};
if($@) {die "ERROR: $@";}

t/TCLI.Transport.Test.t  view on Meta::CPAN

#!/usr/bin/env perl
# $Id: TCLI.Package.Tail.t 49 2007-04-25 10:32:36Z hacker $

use Test::More qw(no_plan);
use warnings;
use strict;

use Getopt::Lucid qw(:all);

my ($opt, $verbose, $poe_td, $poe_te);

eval {$opt = Getopt::Lucid->getopt([
		Counter("poe_debug|d"),
		Counter("poe_event|e"),
		Counter("verbose|v"),
		Switch("blib|b"),
	])};
if($@) {die "ERROR: $@";}

if ($opt->get_blib)
{
	use lib 'blib/lib';



( run in 2.093 seconds using v1.01-cache-2.11-cpan-de7293f3b23 )