source: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli/Base.pm @ 2231

Last change on this file since 2231 was 2231, checked in by nanardon, 5 years ago

Move help from code to POD

File size: 15.4 KB
Line 
1package LATMOS::Accounts::Cli::Base;
2
3# $Id: Cli.pm 2145 2018-08-29 18:15:46Z nanardon $
4
5use strict;
6use warnings;
7use Moose;
8use LATMOS::Accounts::Cli::Context;
9use LATMOS::Accounts::Log;
10use LATMOS::Accounts::Utils;
11use Term::ReadLine;
12use Text::ParseWords;
13use Getopt::Long;
14use Pod::Select;
15use Pod::Text::Termcap;
16use File::Temp;
17
18=head1 NAME
19
20LATMOS::Accounts::Cli - Command line interface functions
21
22=head1 DESCRIPTION
23
24This module handle envirronment and functons for L<la-cli> tools.
25
26=cut
27
28has Context => ( is => 'ro', isa => 'LATMOS::Accounts::Cli::Context' );
29has Parent  => ( is => 'ro' );
30
31=head1 FUNCTIONS
32
33=cut
34
35=head1 CLI FUNCTIONS
36
37=head2 GLOBAL FUNCTIONS
38
39=cut
40
41sub BUILD {
42    my $self = shift;
43
44    my $OUT = $self->Context->Out;
45
46=head3 help
47
48    help [command] - print help about command
49
50=cut
51
52    $self->add_func('help', {
53        completion => sub {
54            if (!$_[2]) { return sort keys %{ $_[0]->{funcs} || {}} }
55        },
56        code => sub {
57            my $env = shift;
58
59            $env->Help(@_);
60        },
61    });
62
63=head3 unexported
64
65    unexported yes|no|show
66
67switch or show base mode regarding' unexported objects behavior
68
69=cut
70
71    $self->add_func('unexported', {
72        completion => sub {
73            if (!$_[2]) {
74                return qw(yes no show);
75            }
76        },
77        code => sub {
78            my ($self, $arg) = @_;
79            if (!$arg) {
80                print $OUT "Unexported objects is" . ($self->base->unexported ?
81                "enable" : "disable") . "\n";
82            } elsif ($arg eq 'yes') {
83                $self->base->unexported(1);
84                print $OUT "Unexported are now show\n";
85            } elsif ($arg eq 'no') {
86                $self->base->unexported(0);
87                print $OUT "Unexported are no longer show\n";
88            } elsif ($arg eq 'show') {
89                print $OUT "Unexported objects is" . ($self->base->unexported ?
90                "enable" : "disable") . "\n";
91            } else {
92                print $OUT "wrong argument\n";
93            }
94        },
95    });
96
97=head3 quit
98
99Exit from C<CLI> tools
100
101=cut
102
103    $self->add_func('quit', {
104            code => sub { print "\n"; exit(0) }, });
105
106=head3 exit
107
108Exit from current selection context
109
110=cut
111
112    $self->add_func('exit', {
113            code => sub { return "EXIT" }, });
114
115=head3 !
116
117    ! [command [arg]]
118
119Open a shell command or run command under shell
120
121=cut
122
123    $self->add_func('!', {
124            code => sub {
125                my ($env, $name, @args) = @_;
126                if ($name) {
127                    system('/bin/bash', '-ic', $env->Context->{_line});
128                } else {
129                    system('/bin/bash', '-i');
130                }
131            },
132    } );
133
134    if ($self->base->is_transactionnal) {
135
136=head2 TRANSACTIONS FUNCTIONS
137
138    transaction [on|off]
139
140Enable or disable the transaction mode: ie automatic commit
141
142=cut
143
144        $self->add_func(
145            'transaction', {
146                code => sub {
147                    $self->Context->TransMode($_[1] eq 'on' ? 1 : 0);
148                },
149                completion => sub {
150                    $self->Context->TransMode == 0 ? 'on' : 'off';
151                },
152            }
153        );
154
155=head3 begin
156
157Start a transaction, meaning changes will be saved only by C<commit> and canceled by C<rollback>
158
159=cut
160
161        $self->add_func(
162            'begin', {
163                code => sub {
164                    $self->Context->TransStarted(1);
165                },
166            }
167        );
168
169=head3 commit
170
171Save pending changes in transaction mode or following C<begin>
172
173=cut
174
175        $self->add_func(
176            'commit', {
177                code => sub {
178                    $_[0]->_commit;
179                },
180            }
181        );
182
183=head3 rollback
184
185Cancel pending changes following a C<begin> or in transaction mode
186
187=cut
188
189        $self->add_func(
190            'rollback', {
191                code => sub {
192                    $_[0]->_rollback;
193                },
194            }
195        );
196    }
197
198=head2 GLOBAL and OBJECTS FUNCTION
199
200=head3 query
201
202    query objectname [attribute]
203    query [attribute]
204
205Show attribute
206
207options:
208
209=over 4
210
211=item -o|--otype objecttype
212
213In global context specify the object type (default: user)
214
215=item -e|--empty
216
217Show empty/unset attributes
218
219=item --ro
220
221Show readonly attributes
222
223=item --fmt format
224
225Instead displaying attribute list use C<format> as formating string
226
227=back
228
229=cut
230
231    $self->add_func( 'query' => {
232            proxy => '*',
233            completion => sub { },
234            code => sub {
235                my $env = shift;
236                my @args = $self->getoption(
237                    {
238                        'o|object=s' => \my $otype,
239                        'e|empty'    => \my $empty_attr,
240                        'ro'         => \my $with_ro,
241                        'fmt=s'      => \my $fmt,
242                        'filefmt=s'  => \my $filefmt,
243                    }, @_
244                );
245                $otype ||= 'user';
246
247                my $objs = $self->Context->{objs};
248
249                if (! $objs ) {
250                    foreach my $name (@args) {
251                        my $obj = $self->base->get_object( $otype, $name) or do {
252                            $self->print("Cannot get object $otype/$name\n");
253                            next;
254                        };
255                        push(@{ $objs }, $obj);
256                    }
257                }
258
259                if ($filefmt){
260                    open(my $hfmt, '<', $filefmt) or die "Cannot open $filefmt\n";
261                    $fmt ||= ''; # avoid undef warning
262                    while (<$hfmt>) {
263                        chomp($fmt .= $_);
264                    }
265                    close $hfmt;
266                }
267
268                foreach (@{ $objs }) {
269                    if ($fmt) {
270                        $self->print($_->queryformat($fmt));
271                    } else {
272                        $_->text_dump( $self->Context->Out, {
273                            empty_attr => $empty_attr,
274                            only_rw => !$with_ro,
275                        } );
276                    }
277                }
278            },
279    } );
280
281=head3 log
282
283    log [[-o otype ] object [object [...]]]
284
285Show global log or log for the object given in arguments
286
287=cut
288
289    $self->add_func('log' => {
290        proxy => '*',
291        completion => sub { },
292        code => sub {
293            my $env = shift;
294            my @args = $self->getoption({
295                'o|object=s' => \my $otype,
296            }, @_);
297            $otype ||= 'user';
298
299            if (!@args && $env->Context->{objs}) {
300                @args = map { $_->id } @{ $env->Context->{objs} };
301                $otype = $env->Context->{objs}[0]->type;
302            }
303
304            my @logs = @args
305                ? $self->base->getobjectlogs($otype, @args)
306                : $self->base->getlogs();
307
308            foreach (@logs) {
309                $self->print(
310                    "%s (%d), %s: %s/%s (%d) %s\n",
311                    $_->{logdate},
312                    $_->{irev} || -1,
313                    $_->{username},
314                    $_->{otype},
315                    $_->{name},
316                    $_->{ikey},
317                    $_->{message}
318                );
319            }
320        },
321    } ) if ($self->base->can('getobjectlogs'));
322
323    if ($self->base->can('CreateAlias')) {
324
325=head2 OBJECT ALIASES FUNCTION
326
327=head3 newalias
328
329    newalias objectType Name Object
330
331Create an object alias named C<Name> for object C<Object>.
332
333=cut
334
335        $self->add_func(
336            'newalias', {
337                code => sub {
338                    my ($self, $otype, $name, $for) = @_;
339                    if ($self->base->CreateAlias($otype, $name, $for)) {
340                        print $OUT "Alias $otype/$name Created\n";
341                        $self->commit;
342                    }
343                },
344                completion => sub {
345                    if ($_[3]) {
346                        return $_[0]->base->list_objects($_[2]);
347                    } elsif (!$_[2]) {
348                        return $_[0]->base->list_supported_objects;
349                    } else {
350                        return;
351                    }
352                }
353            },
354        );
355
356=head3 rmalias
357
358    rmalias objectType Name
359
360Delete alias named C<Name>.
361
362=cut
363
364        $self->add_func(
365            'rmalias', {
366                code => sub {
367                    my ($self, $otype, $name) = @_;
368                    if ($self->base->RemoveAlias($otype, $name)) {
369                        print $OUT "Alias $otype/$name Removed\n";
370                        $self->commit;
371                    }
372                },
373                completion => sub {
374                    if (!$_[2]) {
375                        return $_[0]->base->list_supported_objects;
376                    } else {
377                        return $_[0]->base->search_objects($_[2], 'oalias=*');
378                    }
379                }
380            },
381        );
382
383=head3 updalias
384
385    updalias objectType Name Object
386
387Change the destination of an existing object alias
388
389=cut
390
391        $self->add_func(
392            'updalias', {
393                code => sub {
394                    my ($self, $otype, $name, $for) = @_;
395                    my $obj = $self->base->GetAlias($otype, $name) or do {
396                        print $OUT "No alias $otype/$name found";
397                        return;
398                    };
399                    if ($obj->set_c_fields(oalias => $for)) {
400                        print $OUT "Alias $otype/$name Updated\n";
401                        $self->commit;
402                    }
403                },
404                completion => sub {
405                    if ($_[3]) {
406                        return $_[0]->base->list_objects($_[2]);
407                    } elsif($_[2]) {
408                        return $_[0]->base->search_objects($_[2], 'oalias=*');
409                    } else {
410                        return $_[0]->base->list_supported_objects;
411                    }
412                }
413            },
414        );
415    }
416}
417
418=head2 base
419
420Return the attached base object.
421
422=cut
423
424sub base { $_[0]->Context->base }
425sub term { $_[0]->Context->Term }
426sub print { shift->Context->print(@_) }
427
428sub Top {
429    my ( $self ) = @_;
430
431    if ($self->Parent) {
432        return $self->Parent->Top;
433    } else {
434        return $self;
435    }
436}
437
438=head2 cli
439
440Start the main loop
441
442=cut
443
444sub cli {
445    my ($self) = @_;
446
447    my $term = $_[0]->Context->Term;
448
449    while (1) {
450        $term->Attribs->{completion_function} = sub {
451            $self->complete($_[0], shellwords(substr($_[1], 0, $_[2])));
452        };
453        defined (my $line = $term->readline($self->prompt)) or do {
454            $self->print("\n");
455            return;
456        };
457        $_[0]->Context->{_line} = $line;
458        $term->addhistory($line) if ($line =~ /\S/);
459        my $res = $self->run(shellwords($line));
460        $self->rollback if (!$self->Context->TransMode);
461        if ($res && $res eq 'EXIT') { $self->print("\n"); return }
462    }
463}
464
465=head2 prompt
466
467Wait user to input command
468
469=cut
470
471sub promptPrefix { 'LA cli' }
472
473sub prompt {
474    my ($self) = @_;
475    my $pr = $self->promptPrefix;
476    return sprintf(
477        "%s%s%s ",
478        $pr,
479        $self->Context->TransStarted ? '-' : '=',
480        $self->Context->TransMode  ? '#' : '>',
481    );
482}
483
484=head2 add_func ($name, $param)
485
486Add new function in the envirronment
487
488=cut
489
490# TODO: hide this
491
492sub add_func {
493    my ($self, $name, $param) = @_;
494    my (undef, $file) = caller(0);
495    $param->{podfile} = $file;
496    $self->{funcs}{$name} = $param;
497}
498
499=head2 Help
500
501Display help of given function
502
503=cut
504
505sub Help {
506    my ($self, $name) = @_;
507    if (!$name) {
508        $self->print(join(', ', sort keys %{ $self->{funcs} || {}}) . "\n");
509    } elsif ($self->{funcs}{$name}{alias}) {
510        $self->print("$name is an alias for " . join(' ', @{$self->{funcs}{$name}{alias}}) . "\n");
511    } elsif ($self->{funcs}{$name}{help}) {
512        $self->print($self->{funcs}{$name}{help} . "\n");
513    } else {
514        my $fh = File::Temp->new();
515        my $parser = Pod::Text::Termcap->new( sentence => 0, width => 78 );
516
517        podselect(
518            {-output => $fh, -sections => ["CLI FUNCTIONS/.*/\Q$name"]},
519            $self->{funcs}{$name}{podfile}
520        );
521        seek($fh, 0, 0);
522        $parser->parse_from_filehandle($fh, $self->Context->Out);
523    }
524}
525
526=head2 getoption ($opt, @args)
527
528Parse commmand line
529
530=cut
531
532sub getoption {
533    my ($self, $opt, @args) = @_;
534    local @ARGV = @args;
535    Getopt::Long::Configure("pass_through");
536    GetOptions(%{ $opt });
537
538    return @ARGV;
539}
540
541=head2 complete
542
543Return possible words according current entered words
544
545=cut
546
547sub complete {
548    my ($self, $lastw, $name, @args) = @_;
549    if (!$name) {
550        $lastw ||= ''; # avoid undef warning
551        if ($lastw =~ m!^(\.\./*)(.*)$!) {
552            if ($self->Parent) {
553                my $dot = $1;
554                $dot .= '/' unless($dot =~ m!/$!);
555                return map { "$dot$_" } $self->Parent->complete($2, $name, @args);
556            } else {
557                return ();
558            }
559        } elsif ($lastw =~ m!(^/+)(.*)$!) {
560            return map { "$1$_" } $self->Top->complete($2, $name, @args);
561        } else {
562            return grep { /^\Q$lastw\E/ } sort
563                (keys %{ $self->{funcs} || {}});
564        }
565    } elsif ($name =~ m!^\.\./(.*)$!) {
566        if ($self->Parent) {
567            return $self->Parent->complete($lastw, $1, @args);
568        } else {
569            return ();
570        }
571    } elsif ($name =~ m!^/+(.*)$!) {
572        return $self->Top->complete($lastw, $1, @args);
573    } elsif ($self->{funcs}{$name}{alias}) {
574        $self->complete($lastw, @{$self->{funcs}{$name}{alias}}, @args);
575    } elsif ($self->{funcs}{$name}{completion}) {
576        return map { my $t = $_; $t =~ s/\s/\\ /g; $t } grep { $_ && /^\Q$lastw\E/ } $self->{funcs}{$name}{completion}->($self, $lastw, @args);
577    } else {
578        return ();
579    }
580}
581
582=head2 run ($name, @args)
583
584Run functions
585
586=cut
587
588sub run {
589    my ($self, $name, @args) = @_;
590    return if (!$name);
591
592    if ($name =~ m!^\.\./+(.*)$!) {
593        if ($self->Parent) {
594            $self->Parent->run($1, @args);
595        } else {
596            $self->print("No parent envirronment to call function\n");
597        }
598    } elsif ($name =~ m!^/+(.*)$!) {
599        $self->Top->run($1, @args);
600    } elsif (grep { m/^(-h|--help)$/ } @args) {
601        $self->Help($name);
602    } elsif (!exists($self->{funcs}{$name})) {
603        $self->print("No command $name found\n");
604    } elsif ($self->{funcs}{$name}{alias}) {
605        $self->run(@{$self->{funcs}{$name}{alias}}, @args);
606    } elsif ($self->{funcs}{$name}{code}) {
607        $self->{funcs}{$name}{code}->($self, @args);
608    } else {
609        $self->print("No command $name found\n");
610    }
611}
612
613=head2 commit
614
615Call commit to base unelss in transaction mode
616
617=cut
618
619sub commit {
620    my ($self) = @_;
621    $self->Context->commit;
622}
623
624sub _commit {
625    my ($self) = @_;
626    $self->Context->_commit;
627}
628
629=head2 rollback
630
631Perform rollback unless in transaction mode
632
633=cut
634
635sub rollback {
636    my ($self) = @_;
637    $self->Context->rollback;
638}
639
640sub _rollback {
641    my ($self) = @_;
642    $self->Context->_rollback;
643}
644
6451;
646
647__END__
648
649=head1 SEE ALSO
650
651L<LATMOS::Accounts>
652
653=head1 AUTHOR
654
655Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
656
657=head1 COPYRIGHT AND LICENSE
658
659Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
660
661This library is free software; you can redistribute it and/or modify
662it under the same terms as Perl itself, either Perl version 5.10.0 or,
663at your option, any later version of Perl 5 you may have available.
664
665=cut
Note: See TracBrowser for help on using the repository browser.