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