Skip to content

Commit

Permalink
Merge pull request #4 from okxchg/master
Browse files Browse the repository at this point in the history
Argument naming and argument count validation in Plugin::BotCommand
  • Loading branch information
bingos committed Sep 19, 2012
2 parents d05ef95 + 7aa2512 commit 9cc9f65
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 13 deletions.
105 changes: 100 additions & 5 deletions lib/POE/Component/IRC/Plugin/BotCommand.pm
Expand Up @@ -14,6 +14,13 @@ sub new {
$args{Method} = 'notice' if !defined $args{Method};

for my $cmd (keys %{ $args{Commands} }) {
if (ref $args{Commands}->{$cmd} eq 'HASH') {
croak "$cmd: no info provided"
if !exists $args{Commands}->{$cmd}->{info} ;
croak "$cmd: no arguments provided"
if !@{ $args{Commands}->{$cmd}->{args} };

}
$args{Commands}->{lc $cmd} = delete $args{Commands}->{$cmd};
}
return bless \%args, $package;
Expand Down Expand Up @@ -99,6 +106,47 @@ sub _handle_cmd {
my $public = $where =~ /^[$chantypes]/ ? 1 : 0;
$cmd = lc $cmd;

if (defined $self->{Commands}->{$cmd}) {
if (ref $self->{Commands}->{$cmd} eq 'HASH') {
my @args_array = defined $args ? split /\s+/, $args : ();
if (@args_array < @{ $self->{Commands}->{$cmd}->{args} } ||
(!defined $self->{Commands}->{$cmd}->{variable} &&
@args_array > @{ $self->{Commands}->{$cmd}->{args} })
) {
$irc->yield($self->{Method}, $where,
"Not enough or too many arguments. See help for $cmd");
return;
}

$args = {};
for (@{ $self->{Commands}->{$cmd}->{args} }) {
my $in_arg = shift @args_array;
if (ref $self->{Commands}->{$cmd}->{$_} eq 'ARRAY') {
my @values = @{ $self->{Commands}->{$cmd}->{$_} };
shift @values;

use List::MoreUtils qw(none);
# Check if argument has one of possible values
if (none { $_ eq $in_arg} @values) {
$irc->yield($self->{Method}, $where,
"$_ can be one of ".join '|', @values);
return;
}

}
$args->{$_} = $in_arg;
}

# Process remaining arguments if variable is set
my $arg_cnt = 0;
if (defined $self->{Commands}->{$cmd}->{variable}) {
for (@args_array) {
$args->{"opt".$arg_cnt++} = $_;
}
}
}
}

if (ref $self->{Auth_sub} eq 'CODE') {
my ($authed, $errors) = $self->{Auth_sub}->($self->{irc}, $who, $where, $cmd, $args);

Expand Down Expand Up @@ -139,7 +187,31 @@ sub _get_help {
if (defined $args) {
my $cmd = (split /\s+/, $args, 2)[0];
if (exists $self->{Commands}->{$cmd}) {
@help = split /\015?\012/, $self->{Commands}->{$cmd};
if (ref $self->{Commands}->{$cmd} eq 'HASH') {
push @help, "Syntax: $cmd ".
(join ' ', @{ $self->{Commands}->{$cmd}->{args} }).
(defined $self->{Commands}->{$cmd}->{variable} ?
" ..." : "");
push @help, split /\015?\012/,
"Description: ".$self->{Commands}->{$cmd}->{info};
push @help, "Arguments:";

for my $arg (@{ $self->{Commands}->{$cmd}->{args} }) {
next if not defined $self->{Commands}->{$cmd}->{$arg};
if (ref $self->{Commands}->{$cmd}->{$arg} eq 'ARRAY') {
my @arg_usage = @{$self->{Commands}->{$cmd}->{$arg}};
push @help, " $arg: ".$arg_usage[0].
" (".(join '|', @arg_usage[1..$#arg_usage]).")"
}
else {
push @help, " $arg: ".
$self->{Commands}->{$cmd}->{$arg};
}
}
}
else {
@help = split /\015?\012/, $self->{Commands}->{$cmd};
}
}
else {
push @help, "Unknown command: $cmd";
Expand All @@ -163,6 +235,11 @@ sub add {
my ($self, $cmd, $usage) = @_;
$cmd = lc $cmd;
return if exists $self->{Commands}->{$cmd};

if (ref $usage eq 'HASH') {
return if !exists $usage->{info} || !@{ $usage->{args} };
}

$self->{Commands}->{$cmd} = $usage;
return 1;
}
Expand Down Expand Up @@ -289,6 +366,22 @@ B<'Commands'>, a hash reference, with your commands as keys, and usage
information as values. If the usage string contains newlines, the plugin
will send one message for each line.
If a command's value is a HASH ref like this:
$irc->plugin_add('BotCommand', POE::Component::IRC::Plugin::BotCommand->new(
Commands => {
slap => {
info => 'Slap someone',
args => [qw(nickname)],
nickname => 'nickname to slap'
}
}
));
The args array reference is than used to validate number of arguments required
and to name arguments passed to event handler. Help is than generated from
C<info> and other hash keys which represent arguments (they are optional).
=head3 Accepting commands
B<'In_channels'>, a boolean value indicating whether to accept commands in
Expand Down Expand Up @@ -359,8 +452,9 @@ L<POE::Component::IRC|POE::Component::IRC>'s C<plugin_add> method.
=head2 C<add>
Adds a new command. Takes two arguments, the name of the command, and a string
containing its usage information. Returns false if the command has already been
defined, true otherwise.
or hash reference containing its usage information (see C<new>). Returns false
if the command has already been defined or no info or arguments are provided,
true otherwise.
=head2 C<remove>
Expand All @@ -370,7 +464,7 @@ if the command wasn't defined to begin with, true otherwise.
=head2 C<list>
Takes no arguments. Returns a list of key/value pairs, the keys being the
command names and the values being the usage strings.
command names and the values being the usage strings or hash references.
=head1 OUTPUT EVENTS
Expand All @@ -387,7 +481,8 @@ every time someone issued that command. It receives the following arguments:
=item * C<ARG1> is the name of the channel in which the command was issued,
or the sender's nickname if this was a private message.
=item * C<ARG2>: a string of arguments to the command, or undef if there
=item * C<ARG2>: a string of arguments to the command, or hash reference with
arguments in case you defined command along with arguments, or undef if there
were no arguments
=back
Expand Down
34 changes: 29 additions & 5 deletions t/04_plugins/13_botcommand/02_commands.t
Expand Up @@ -5,7 +5,7 @@ use POE;
use POE::Component::IRC;
use POE::Component::IRC::Plugin::BotCommand;
use POE::Component::Server::IRC;
use Test::More tests => 18;
use Test::More tests => 22;

my $bot1 = POE::Component::IRC->spawn(
Flood => 1,
Expand All @@ -31,6 +31,7 @@ POE::Session->create(
irc_join
irc_botcmd_cmd1
irc_botcmd_cmd2
irc_botcmd_cmd3
irc_disconnected
)],
],
Expand Down Expand Up @@ -89,18 +90,27 @@ sub irc_001 {
my $plugin = POE::Component::IRC::Plugin::BotCommand->new(
Commands => {
cmd1 => 'First test command',
cmd2 => {
info => 'First test command with argument count checking',
args => [qw(test_arg test_arg2)],
variable => 1,
test_arg => ['Description of first arg', qw(value1 value2)],
test_arg2 => 'Description of second arg',
optional_arg => 'Description of optional arg',
},
foo => 'This will get removed',
},
);

ok($irc->plugin_add(BotCommand => $plugin), 'Add plugin with two commands');
ok($plugin->add(cmd2 => 'Second test command'), 'Add another command');
ok($irc->plugin_add(BotCommand => $plugin), 'Add plugin with three commands');
ok($plugin->add(cmd3 => 'Third test command'), 'Add another command');
ok($plugin->remove('foo'), 'Remove command');

my %cmds = $plugin->list();
is(keys %cmds, 2, 'Correct number of commands');
is(keys %cmds, 3, 'Correct number of commands');
ok($cmds{cmd1}, 'First command is present');
ok($cmds{cmd2}, 'Second command is present');
ok($cmds{cmd3}, 'Third command is present');
}

sub irc_join {
Expand All @@ -116,8 +126,11 @@ sub irc_join {
# try command
$bot2->yield(privmsg => $where, "TestBot1: cmd1 foo bar");

# try command with predefined arguments
$bot2->yield(privmsg => $where, "TestBot1: cmd2 value1 bar opt_arg");

# and one with color
$bot2->yield(privmsg => $where, "\x0302TestBot1\x0f: \x02cmd2\x0f");
$bot2->yield(privmsg => $where, "\x0302TestBot1\x0f: \x02cmd3\x0f");
}

sub irc_botcmd_cmd1 {
Expand All @@ -135,6 +148,17 @@ sub irc_botcmd_cmd2 {
my $nick = (split /!/, $user)[0];
my $irc = $sender->get_heap();

is($nick, $bot2->nick_name(), 'Command with args (user)');
is($where, '#testchannel', 'Command with args (channel)');
is_deeply($args, { test_arg => 'value1', test_arg2 => 'bar', opt0 => 'opt_arg'},
'Command with args (arguments)');
}

sub irc_botcmd_cmd3 {
my ($sender, $user, $where, $args) = @_[SENDER, ARG0..ARG2];
my $nick = (split /!/, $user)[0];
my $irc = $sender->get_heap();

is($nick, $bot2->nick_name(), 'Colored command (user)');
is($where, '#testchannel', 'Colored command (channel)');
ok(!defined $args, 'Colored command (arguments)');
Expand Down
25 changes: 22 additions & 3 deletions t/04_plugins/13_botcommand/04_help.t
Expand Up @@ -5,7 +5,7 @@ use POE;
use POE::Component::IRC;
use POE::Component::IRC::Plugin::BotCommand;
use POE::Component::Server::IRC;
use Test::More tests => 14;
use Test::More tests => 25;

my $bot1 = POE::Component::IRC->spawn(
Flood => 1,
Expand Down Expand Up @@ -35,6 +35,14 @@ POE::Session->create(
],
);

my @bar_help = (
"Syntax: bar arg1 arg2 ...",
"Description: Test command2",
"Arguments:",
" arg1: What to bar (table|chair)",
" arg2: Where to bar"
);

$poe_kernel->run();

sub _start {
Expand Down Expand Up @@ -121,13 +129,24 @@ sub irc_notice {
like($what, qr/^Unknown command:/, 'Bot reply');
my ($p) = grep { $_->isa('POE::Component::IRC::Plugin::BotCommand') } values %{ $bot1->plugin_list() };
ok($p->add(foo => 'Test command'), 'Add command foo');
ok($p->add(bar => {
info => 'Test command2',
args => [qw(arg1 arg2)],
arg1 => ['What to bar', qw(table chair)],
arg2 => 'Where to bar',
variable => 1,
}), 'Add command bar');
$irc->yield(privmsg => $where, "TestBot1: help");
$irc->yield(privmsg => $where, "TestBot1: help bar");
}
elsif ($heap->{replies} == 4) {
is($nick, $bot1->nick_name(), 'Bot nickname');
like($what, qr/^Commands: foo/, 'Bot reply');
like($what, qr/^Commands: bar, foo/, 'Bot reply');
}
elsif ($heap->{replies} == 5) {
elsif ($heap->{replies} >= 6 && $heap->{replies} <= 11) {
is($nick, $bot1->nick_name(), 'Bot nickname');
is($what, shift @bar_help, 'Command with args help');

$bot1->yield('quit');
$bot2->yield('quit');
}
Expand Down

0 comments on commit 9cc9f65

Please sign in to comment.