1package Net::CLI::Interact::ActionSet; 2{ $Net::CLI::Interact::ActionSet::VERSION = '2.300003' } 3 4use Moo; 5use Sub::Quote; 6use MooX::Types::MooseLike::Base qw(InstanceOf ArrayRef CodeRef RegexpRef); 7use Net::CLI::Interact::Action; 8 9with 'Net::CLI::Interact::Role::Iterator'; 10 11has default_continuation => ( 12 is => 'rw', 13 isa => InstanceOf['Net::CLI::Interact::ActionSet'], 14 predicate => 1, 15); 16 17has current_match => ( 18 is => 'rw', 19 isa => ArrayRef[RegexpRef], 20 predicate => 1, 21 coerce => quote_sub(q{ (ref qr// eq ref $_[0]) ? [$_[0]] : $_[0] }), 22); 23 24sub BUILDARGS { 25 my ($class, @rest) = @_; 26 27 # accept single hash ref or naked hash 28 my $params = (ref {} eq ref $rest[0] ? $rest[0] : {@rest}); 29 30 if (exists $params->{actions} and ref $params->{actions} eq ref []) { 31 foreach my $a (@{$params->{actions}}) { 32 if (ref $a eq 'Net::CLI::Interact::ActionSet') { 33 push @{$params->{_sequence}}, @{ $a->_sequence }; 34 next; 35 } 36 37 if (ref $a eq 'Net::CLI::Interact::Action') { 38 push @{$params->{_sequence}}, $a; 39 next; 40 } 41 42 if (ref $a eq ref {}) { 43 push @{$params->{_sequence}}, 44 Net::CLI::Interact::Action->new($a); 45 next; 46 } 47 48 die "don't know what to do with a: '$a'\n"; 49 } 50 delete $params->{actions}; 51 } 52 53 return $params; 54} 55 56sub clone { 57 my $self = shift; 58 return Net::CLI::Interact::ActionSet->new({ 59 actions => [ map { $_->clone } @{ $self->_sequence } ], 60 ($self->_has_callbacks ? (_callbacks => $self->_callbacks) : ()), 61 ($self->has_default_continuation ? (default_continuation => $self->default_continuation) : ()), 62 ($self->has_current_match ? (current_match => $self->current_match) : ()), 63 }); 64} 65 66# store params to the set, used when send is passed via sprintf 67sub apply_params { 68 my ($self, @params) = @_; 69 70 $self->reset; 71 while ($self->has_next) { 72 my $next = $self->next; 73 $next->params([splice @params, 0, $next->num_params]); 74 } 75} 76 77has _callbacks => ( 78 is => 'rw', 79 isa => ArrayRef[CodeRef], 80 default => sub { [] }, 81 predicate => 1, 82); 83 84sub register_callback { 85 my $self = shift; 86 $self->_callbacks([ @{$self->_callbacks}, shift ]); 87} 88 89sub execute { 90 my $self = shift; 91 92 $self->_pad_send_with_match; 93 $self->_forward_continuation_to_match; 94 $self->_do_exec; 95 $self->_marshall_responses; 96} 97 98sub _do_exec { 99 my $self = shift; 100 101 $self->reset; 102 while ($self->has_next) { 103 $_->($self->next) for @{$self->_callbacks}; 104 } 105} 106 107# pad out the Actions with match Actions if needed between send pairs. 108sub _pad_send_with_match { 109 my $self = shift; 110 my $match = Net::CLI::Interact::Action->new({ 111 type => 'match', value => $self->current_match, 112 }); 113 114 $self->reset; 115 while ($self->has_next) { 116 my $this = $self->next; 117 my $next = $self->peek or last; # careful... 118 next unless $this->type eq 'send' and $next->type eq 'send'; 119 120 $self->insert_at($self->idx + 1, $match->clone); 121 } 122 123 # always finish on a match 124 if ($self->last->type ne 'match') { 125 $self->insert_at($self->count, $match->clone); 126 } 127} 128 129# carry-forward a continuation beacause it's the match which really does the 130# heavy lifting. 131sub _forward_continuation_to_match { 132 my $self = shift; 133 134 $self->reset; 135 while ($self->has_next) { 136 my $this = $self->next; 137 my $next = $self->peek or last; # careful... 138 my $cont = ($this->continuation || $self->default_continuation); 139 next unless $this->type eq 'send' 140 and $next->type eq 'match' 141 and defined $cont; 142 143 $next->continuation($cont); 144 } 145} 146 147# marshall the responses so as to move data from match to send 148sub _marshall_responses { 149 my $self = shift; 150 151 $self->reset; 152 while ($self->has_next) { 153 my $send = $self->next; 154 my $match = $self->peek or last; # careful... 155 next unless $match->type eq 'match'; 156 157 # remove echoed command from the beginning 158 my $cmd = quotemeta( sprintf $send->value, @{ $send->params } ); 159 (my $output = $match->response_stash) =~ s/^${cmd}[\t ]*(?:\r\n|\r|\n)?//s; 160 $send->response($output); 161 } 162} 163 1641; 165 166=pod 167 168=head1 NAME 169 170Net::CLI::Interact::ActionSet - Conversation of Send and Match Actions 171 172=head1 DESCRIPTION 173 174This class is used internally by L<Net::CLI::Interact> and it's unlikely that 175an end-user will need to make use of ActionSet objects directly. The interface 176is documented here as a matter of record. 177 178An ActionSet comprises a sequence (usefully, two or more) of 179L<Actions|Net::CLI::Interact::Action> which describe a conversation with a 180connected network device. Actions will alternate between type C<send> and 181C<match>, perhaps not in their original 182L<Phrasebook|Net::CLI::Interact::Phrasebook> definition, but certainly by the 183time they are used. 184 185If the first Action is of type C<send> then the ActionSet is a normal sequence 186of "send a command" then "match a response", perhaps repeated. If the first 187Action is of type C<match> then the ActionSet represents a C<continuation>, 188which is the method of dealing with paged output. 189 190=head1 INTERFACE 191 192=head2 default_continuation 193 194An ActionSet (C<match> then C<send>) which will be available for use on all 195commands sent from this ActionSet. An alternative to explicitly describing the 196Continuation sequence within the Phrasebook. 197 198=head2 current_match 199 200A stash for the current Prompt (regular expression reference) which 201L<Net::CLI::Interact> expects to see after each command. This is passed into 202the constructor and is used when padding Match Actions into the ActionSet (see 203C<execute>, below). 204 205=head2 clone 206 207Returns a new ActionSet which is a shallow clone of the existing one. All the 208reference based slots will share data, but you can add (for example) a 209C<current_match> without affecting the original ActionSet. Used when preparing 210to execute an ActionSet which has been retrieved from the 211L<Phrasebook|Net::CLI::Interact::Phrasebook>. 212 213=head2 apply_params 214 215Accepts a list of parameters which will be used when C<sprintf> is called on 216each Send Action in the set. You must supply sufficient parameters as a list 217for I<all> Send Actions in the set, and they will be popped off and stashed 218with the Action(s) according to how many are required. 219 220=head2 register_callback 221 222Allows the L<Transport|Net::CLI::Interact::Transport> to be registered 223such that when the ActionSet is executed, commands are sent to the registered 224callback subroutine. May be called more than once, and on execution each of 225the callbacks will be run, in turn and in order. 226 227=head2 execute 228 229The business end of this class, where the sequence of Actions is prepared for 230execution and then control passed to the Transport. This process is split into 231a number of phases: 232 233=over 4 234 235=item Pad C<send> with C<match> 236 237The Phrasebook allows missing out of the Match statements between Send 238statements, when they are expected to be the same as the C<current_match>. 239This phase inserts Match statements to restore a complete ActionSet 240definition. 241 242=item Forward C<continuation> to C<match> 243 244In the Phrasebook a user defines a Continuation (C<match>, then C<send>) 245following a Send statement (because it deals with the response to the sent 246command). However they are actually used by the Match, as it's the Match which 247captures output. 248 249This phase copies Continuation ActionSets from Send statements to following 250Match statements in the ActionSet. It also performs a similar action using the 251C<default_continuation> if one is set and there's no existing Continuation 252configured. 253 254=item Callback(s) 255 256Here the registered callbacks are executed (i.e. data is sent to the 257Transport). 258 259=item Marshall Responses 260 261Finally, responses which are stashed in the Match Actions are copied back to 262the Send actions, as more logically they are responses to commands sent. The 263ActionSet is now ready for access to retrieve the C<last_response> from the 264device. 265 266=back 267 268=head1 COMPOSITION 269 270See the following for further interface details: 271 272=over 4 273 274=item * 275 276L<Net::CLI::Interact::Role::Iterator> 277 278=back 279 280=cut 281 282