#============================================================================= # # Module: Term::CLI::CommandSet # # Description: Class for sets of (sub-)commands in Term::CLI # # Author: Steven Bakker (SBAKKER), # Created: 05/02/18 # # Copyright (c) 2018 Steven Bakker # # This module is free software; you can redistribute it and/or modify # it under the same terms as Perl itself. See "perldoc perlartistic." # # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # #============================================================================= use 5.014_001; package Term::CLI::Role::CommandSet 0.051007 { use Modern::Perl 1.20140107; use Term::CLI::L10N; use Types::Standard 1.000005 qw( ArrayRef CodeRef InstanceOf ConsumerOf Maybe ); use Moo::Role; use namespace::clean 0.25; has parent => ( is => 'rwp', weak_ref => 1, isa => ConsumerOf['Term::CLI::Role::CommandSet'], ); has _commands => ( is => 'rw', writer => '_set_commands', init_arg => 'commands', isa => Maybe[ArrayRef[InstanceOf['Term::CLI::Command']]], trigger => 1, coerce => sub { # Copy the array, so the reference we store becomes # "internal", preventing accidental modification # from the outside. return [@{$_[0]}] }, ); has callback => ( is => 'rw', isa => Maybe[CodeRef], predicate => 1 ); # $self->_set_commands($ref) => $self->_trigger__commands($ref); # # Trigger to run whenever the object's _commands array ref is set. # sub _trigger__commands { my ($self, $arg) = @_; # No need to check for defined-ness of $arg. # The writer method already checks & croaks. for my $cmd (@$arg) { $cmd->_set_parent($self); } }; sub commands { my $self = shift; my @l = sort { $a->name cmp $b->name } @{$self->_commands // []}; return @l; } sub has_commands { my $self = shift; return ($self->_commands and scalar @{$self->_commands} > 0); } sub add_command { my ($self, @commands) = @_; if (!$self->_commands) { $self->_set_commands([]); } for my $cmd (@commands) { push @{$self->_commands}, $cmd; $cmd->_set_parent($self); } return $self; } sub command_names { my $self = shift; return map { $_->name } $self->commands; } sub find_matches { my ($self, $partial) = @_; return () if !$self->has_commands; my @found = grep { rindex($_->name, $partial, 0) == 0 } $self->commands; return @found; } sub root_node { my $curr_node = shift; while (my $parent = $curr_node->parent) { $curr_node = $parent; } return $curr_node; } sub find_command { my ($self, $partial) = @_; my @matches = $self->find_matches($partial); if (@matches == 1) { return $matches[0]; } elsif (@matches == 0) { return $self->set_error(loc("unknown command '[_1]'", $partial)); } else { return $self->set_error( loc("ambiguous command '[_1]' (matches: [_2])", $partial, join(', ', sort map {$_->name} @matches) ) ); } } sub try_callback { my ($self, %args) = @_; if ($self->has_callback && defined $self->callback) { return $self->callback->($self, %args); } else { return %args; } } } 1; __END__ =pod =head1 NAME Term::CLI::Role::CommandSet - Role for (sub-)commands in Term::CLI =head1 VERSION version 0.051007 =head1 SYNOPSIS package Term::CLI::Command { use Moo; with('Term::CLI::Role::CommandSet'); ... }; my $cmd = Term::CLI::Command->new( ... ); $cmd->callback->( %args ) if $cmd->has_callback; if ( $cmd->has_commands ) { my $cmd_ref = $cmd->find_command( $cmd_name ); die $cmd->error unless $cmd_ref; } say "command names:", join(', ', $cmd->command_names); $cmd->callback->( $cmd, %args ) if $cmd->has_callback; %args = $cmd->try_callback( %args ); =head1 DESCRIPTION Role for L(3p) elements that contain a set of L(3p) objects. This role is used by L(3p) and L(3p). =head1 ATTRIBUTES This role defines two additional attributes: =over =item B =E I Reference to an array containing C object instances that describe the sub-commands that the command takes, or C. Note that the elements of the array are copied over to an internal array, so modifications to the I will not be seen. =item B =E I Reference to a subroutine that should be called when the command is executed, or C. =back =head1 ACCESSORS AND PREDICATES =over =item B X =item B X Predicate functions that return whether or not any (sub-)commands have been added to this object. =item B ( [ I ] ) X I to be called when the command is executed. The callback is called as: OBJ->callback->(OBJ, status => Int, error => Str, options => HashRef, arguments => ArrayRef[Value], command_line => Str, command_path => ArrayRef[InstanceOf['Term::CLI::Command']], ); Where: =over =item I Reference to the current C object. =item C Indicates the status of parsing/execution so far. It has the following meanings: =over =item I 0> Negative status values indicate a parse error. This is a sign that no action should be taken, but some error handling should be performed. The actual parse error can be found under the C key. A typical thing to do in this case is for one of the callbacks in the chain (e.g. the one on the C object to print the error to F). =item I<0> The command line parses as valid and execution so far has been successful. =item I 0> Some error occurred in the execution of the action. Callback functions need to set this by themselves. =back =item C In case of a negative C, this will contain the parse error. In all other cases, it may or may not contain useful information. =item C Reference to a hash containing all command line options. Compatible with the options hash as set by L(3p). =item C Reference to an array containing all the arguments to the command. Each value is a scalar value, possibly converted by its corresponding L's L method (e.g. C<3e-1> may have been converted to C<0.3>). =item C Reference to an array containing all the words on the command line that have not been parsed as arguments or sub-commands yet. In case of parse errors, this often contains elements, and otherwise should be empty. =item C The complete command line as given to the L method. =item C Reference to an array containing the "parse tree", i.e. a list of object references: [ InstanceOf['Term::CLI'], InstanceOf['Term::CLI::Command'], ... ] The first item in the C list is always the top-level L object, while the last is always the same as the I parameter. =back The callback is expected to return a hash (list) containing at least the same keys. The C, C, and C should be considered read-only. Note that a callback can be called even in the case of errors, so you should always check the C before doing anything. =item B X Return the list of subordinate C objects (i.e. "sub-commands") sorted on C. =item B X Return a reference to the object that "owns" this object. This is typically another object class that consumes this C role, such as C(3p) or C(3p), or C. =back =head1 METHODS =over =item B ( I, ... ) X Add the given I command(s) to the list of (sub-)commands, setting each I's L in the process. =item B X Return the list of (sub-)command names, sorted alphabetically. =item B ( I ) X Return a list of all commands in this object that match the I prefix. =item B ( I ) X Check whether I uniquely matches a command in this C object. Returns a reference to the appropriate L object if successful; otherwise, it sets the objects C field and returns C. Example: my $sub_cmd = $cmd->find_command($prefix); die $cmd->error unless $sub_cmd; =item B X Walks L chain until it can go no further. Returns a reference to the object at the top. In a functional setup, this is expected to be a L(3p) object. =item B ( I ) X Wrapper function that will call the object's C function if it has been set, otherwise simply returns its arguments. =back =head1 SEE ALSO L(3p), L(3p). =head1 AUTHOR Steven Bakker Esbakker@cpan.orgE, 2018. =head1 COPYRIGHT AND LICENSE Copyright (c) 2018 Steven Bakker This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See "perldoc perlartistic." This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. =begin __PODCOVERAGE =head1 THIS SECTION SHOULD BE HIDDEN This section is meant for methods that should not be considered for coverage. This typically includes things like BUILD and DEMOLISH from Moo/Moose. It is possible to skip these when using the Pod::Coverage class (using C), but this is not an option when running C from the command line. The simplest trick is to add a hidden section with an item list containing these methods. =over =item BUILD =item DEMOLISH =back =end __PODCOVERAGE =cut