File Coverage

blib/lib/Net/CLI/Interact/ActionSet.pm
Criterion Covered Total %
statement 12 83 14.4
branch 0 30 0.0
condition 0 15 0.0
subroutine 4 13 30.7
pod 4 5 80.0
total 20 146 13.7


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