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

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

Add a way to dump object and all related objects

File size: 15.5 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=item recur
228
229Dump object and all related objects
230
231=back
232
233=cut
234
235    $self->add_func( 'query' => {
236            proxy => '*',
237            completion => sub { },
238            code => sub {
239                my $env = shift;
240                my @args = $self->getoption(
241                    {
242                        'o|object=s' => \my $otype,
243                        'e|empty'    => \my $empty_attr,
244                        'ro'         => \my $with_ro,
245                        'fmt=s'      => \my $fmt,
246                        'filefmt=s'  => \my $filefmt,
247                        'recur'      => \my $recur,
248                    }, @_
249                );
250                $otype ||= 'user';
251
252                my $objs = $self->Context->{objs};
253
254                if (! $objs ) {
255                    foreach my $name (@args) {
256                        my $obj = $self->base->get_object( $otype, $name) or do {
257                            $self->print("Cannot get object $otype/$name\n");
258                            next;
259                        };
260                        push(@{ $objs }, $obj);
261                    }
262                }
263
264                if ($filefmt){
265                    open(my $hfmt, '<', $filefmt) or die "Cannot open $filefmt\n";
266                    $fmt ||= ''; # avoid undef warning
267                    while (<$hfmt>) {
268                        chomp($fmt .= $_);
269                    }
270                    close $hfmt;
271                }
272
273                foreach (@{ $objs }) {
274                    if ($fmt) {
275                        $self->print($_->queryformat($fmt));
276                    } else {
277                        $_->text_dump( $self->Context->Out, {
278                            recur => $recur,
279                            empty_attr => $empty_attr,
280                            only_rw => !$with_ro,
281                        } );
282                    }
283                }
284            },
285    } );
286
287=head3 log
288
289    log [[-o otype ] object [object [...]]]
290
291Show global log or log for the object given in arguments
292
293=cut
294
295    $self->add_func('log' => {
296        proxy => '*',
297        completion => sub { },
298        code => sub {
299            my $env = shift;
300            my @args = $self->getoption({
301                'o|object=s' => \my $otype,
302            }, @_);
303            $otype ||= 'user';
304
305            if (!@args && $env->Context->{objs}) {
306                @args = map { $_->id } @{ $env->Context->{objs} };
307                $otype = $env->Context->{objs}[0]->type;
308            }
309
310            my @logs = @args
311                ? $self->base->getobjectlogs($otype, @args)
312                : $self->base->getlogs();
313
314            foreach (@logs) {
315                $self->print(
316                    "%s (%d), %s: %s/%s (%d) %s\n",
317                    $_->{logdate},
318                    $_->{irev} || -1,
319                    $_->{username},
320                    $_->{otype},
321                    $_->{name},
322                    $_->{ikey},
323                    $_->{message}
324                );
325            }
326        },
327    } ) if ($self->base->can('getobjectlogs'));
328
329    if ($self->base->can('CreateAlias')) {
330
331=head2 OBJECT ALIASES FUNCTION
332
333=head3 newalias
334
335    newalias objectType Name Object
336
337Create an object alias named C<Name> for object C<Object>.
338
339=cut
340
341        $self->add_func(
342            'newalias', {
343                code => sub {
344                    my ($self, $otype, $name, $for) = @_;
345                    if ($self->base->CreateAlias($otype, $name, $for)) {
346                        print $OUT "Alias $otype/$name Created\n";
347                        $self->commit;
348                    }
349                },
350                completion => sub {
351                    if ($_[3]) {
352                        return $_[0]->base->list_objects($_[2]);
353                    } elsif (!$_[2]) {
354                        return $_[0]->base->list_supported_objects;
355                    } else {
356                        return;
357                    }
358                }
359            },
360        );
361
362=head3 rmalias
363
364    rmalias objectType Name
365
366Delete alias named C<Name>.
367
368=cut
369
370        $self->add_func(
371            'rmalias', {
372                code => sub {
373                    my ($self, $otype, $name) = @_;
374                    if ($self->base->RemoveAlias($otype, $name)) {
375                        print $OUT "Alias $otype/$name Removed\n";
376                        $self->commit;
377                    }
378                },
379                completion => sub {
380                    if (!$_[2]) {
381                        return $_[0]->base->list_supported_objects;
382                    } else {
383                        return $_[0]->base->search_objects($_[2], 'oalias=*');
384                    }
385                }
386            },
387        );
388
389=head3 updalias
390
391    updalias objectType Name Object
392
393Change the destination of an existing object alias
394
395=cut
396
397        $self->add_func(
398            'updalias', {
399                code => sub {
400                    my ($self, $otype, $name, $for) = @_;
401                    my $obj = $self->base->GetAlias($otype, $name) or do {
402                        print $OUT "No alias $otype/$name found";
403                        return;
404                    };
405                    if ($obj->set_c_fields(oalias => $for)) {
406                        print $OUT "Alias $otype/$name Updated\n";
407                        $self->commit;
408                    }
409                },
410                completion => sub {
411                    if ($_[3]) {
412                        return $_[0]->base->list_objects($_[2]);
413                    } elsif($_[2]) {
414                        return $_[0]->base->search_objects($_[2], 'oalias=*');
415                    } else {
416                        return $_[0]->base->list_supported_objects;
417                    }
418                }
419            },
420        );
421    }
422}
423
424=head2 base
425
426Return the attached base object.
427
428=cut
429
430sub base { $_[0]->Context->base }
431sub term { $_[0]->Context->Term }
432sub print { shift->Context->print(@_) }
433
434sub Top {
435    my ( $self ) = @_;
436
437    if ($self->Parent) {
438        return $self->Parent->Top;
439    } else {
440        return $self;
441    }
442}
443
444=head2 cli
445
446Start the main loop
447
448=cut
449
450sub cli {
451    my ($self) = @_;
452
453    my $term = $_[0]->Context->Term;
454
455    while (1) {
456        $term->Attribs->{completion_function} = sub {
457            $self->complete($_[0], shellwords(substr($_[1], 0, $_[2])));
458        };
459        defined (my $line = $term->readline($self->prompt)) or do {
460            $self->print("\n");
461            return;
462        };
463        $_[0]->Context->{_line} = $line;
464        $term->addhistory($line) if ($line =~ /\S/);
465        my $res = $self->run(shellwords($line));
466        $self->rollback if (!$self->Context->TransMode);
467        if ($res && $res eq 'EXIT') { $self->print("\n"); return }
468    }
469}
470
471=head2 prompt
472
473Wait user to input command
474
475=cut
476
477sub promptPrefix { 'LA cli' }
478
479sub prompt {
480    my ($self) = @_;
481    my $pr = $self->promptPrefix;
482    return sprintf(
483        "%s%s%s ",
484        $pr,
485        $self->Context->TransStarted ? '-' : '=',
486        $self->Context->TransMode  ? '#' : '>',
487    );
488}
489
490=head2 add_func ($name, $param)
491
492Add new function in the envirronment
493
494=cut
495
496# TODO: hide this
497
498sub add_func {
499    my ($self, $name, $param) = @_;
500    my (undef, $file) = caller(0);
501    $param->{podfile} = $file;
502    $self->{funcs}{$name} = $param;
503}
504
505=head2 Help
506
507Display help of given function
508
509=cut
510
511sub Help {
512    my ($self, $name) = @_;
513    if (!$name) {
514        $self->print(join(', ', sort keys %{ $self->{funcs} || {}}) . "\n");
515    } elsif ($self->{funcs}{$name}{alias}) {
516        $self->print("$name is an alias for " . join(' ', @{$self->{funcs}{$name}{alias}}) . "\n");
517    } elsif ($self->{funcs}{$name}{help}) {
518        $self->print($self->{funcs}{$name}{help} . "\n");
519    } else {
520        my $fh = File::Temp->new();
521        my $parser = Pod::Text::Termcap->new( sentence => 0, width => 78 );
522
523        podselect(
524            {-output => $fh, -sections => ["CLI FUNCTIONS/.*/\Q$name"]},
525            $self->{funcs}{$name}{podfile}
526        );
527        seek($fh, 0, 0);
528        $parser->parse_from_filehandle($fh, $self->Context->Out);
529    }
530}
531
532=head2 getoption ($opt, @args)
533
534Parse commmand line
535
536=cut
537
538sub getoption {
539    my ($self, $opt, @args) = @_;
540    local @ARGV = @args;
541    Getopt::Long::Configure("pass_through");
542    GetOptions(%{ $opt });
543
544    return @ARGV;
545}
546
547=head2 complete
548
549Return possible words according current entered words
550
551=cut
552
553sub complete {
554    my ($self, $lastw, $name, @args) = @_;
555    if (!$name) {
556        $lastw ||= ''; # avoid undef warning
557        if ($lastw =~ m!^(\.\./*)(.*)$!) {
558            if ($self->Parent) {
559                my $dot = $1;
560                $dot .= '/' unless($dot =~ m!/$!);
561                return map { "$dot$_" } $self->Parent->complete($2, $name, @args);
562            } else {
563                return ();
564            }
565        } elsif ($lastw =~ m!(^/+)(.*)$!) {
566            return map { "$1$_" } $self->Top->complete($2, $name, @args);
567        } else {
568            return grep { /^\Q$lastw\E/ } sort
569                (keys %{ $self->{funcs} || {}});
570        }
571    } elsif ($name =~ m!^\.\./(.*)$!) {
572        if ($self->Parent) {
573            return $self->Parent->complete($lastw, $1, @args);
574        } else {
575            return ();
576        }
577    } elsif ($name =~ m!^/+(.*)$!) {
578        return $self->Top->complete($lastw, $1, @args);
579    } elsif ($self->{funcs}{$name}{alias}) {
580        $self->complete($lastw, @{$self->{funcs}{$name}{alias}}, @args);
581    } elsif ($self->{funcs}{$name}{completion}) {
582        return map { my $t = $_; $t =~ s/\s/\\ /g; $t } grep { $_ && /^\Q$lastw\E/ } $self->{funcs}{$name}{completion}->($self, $lastw, @args);
583    } else {
584        return ();
585    }
586}
587
588=head2 run ($name, @args)
589
590Run functions
591
592=cut
593
594sub run {
595    my ($self, $name, @args) = @_;
596    return if (!$name);
597
598    if ($name =~ m!^\.\./+(.*)$!) {
599        if ($self->Parent) {
600            $self->Parent->run($1, @args);
601        } else {
602            $self->print("No parent envirronment to call function\n");
603        }
604    } elsif ($name =~ m!^/+(.*)$!) {
605        $self->Top->run($1, @args);
606    } elsif (grep { m/^(-h|--help)$/ } @args) {
607        $self->Help($name);
608    } elsif (!exists($self->{funcs}{$name})) {
609        $self->print("No command $name found\n");
610    } elsif ($self->{funcs}{$name}{alias}) {
611        $self->run(@{$self->{funcs}{$name}{alias}}, @args);
612    } elsif ($self->{funcs}{$name}{code}) {
613        $self->{funcs}{$name}{code}->($self, @args);
614    } else {
615        $self->print("No command $name found\n");
616    }
617}
618
619=head2 commit
620
621Call commit to base unelss in transaction mode
622
623=cut
624
625sub commit {
626    my ($self) = @_;
627    $self->Context->commit;
628}
629
630sub _commit {
631    my ($self) = @_;
632    $self->Context->_commit;
633}
634
635=head2 rollback
636
637Perform rollback unless in transaction mode
638
639=cut
640
641sub rollback {
642    my ($self) = @_;
643    $self->Context->rollback;
644}
645
646sub _rollback {
647    my ($self) = @_;
648    $self->Context->_rollback;
649}
650
6511;
652
653__END__
654
655=head1 SEE ALSO
656
657L<LATMOS::Accounts>
658
659=head1 AUTHOR
660
661Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
662
663=head1 COPYRIGHT AND LICENSE
664
665Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
666
667This library is free software; you can redistribute it and/or modify
668it under the same terms as Perl itself, either Perl version 5.10.0 or,
669at your option, any later version of Perl 5 you may have available.
670
671=cut
Note: See TracBrowser for help on using the repository browser.