Index: trunk/LATMOS-Accounts/MANIFEST
===================================================================
--- trunk/LATMOS-Accounts/MANIFEST	(revision 2208)
+++ trunk/LATMOS-Accounts/MANIFEST	(revision 2209)
@@ -108,4 +108,7 @@
 lib/LATMOS/Accounts/Bases/Zimbra/User.pm
 lib/LATMOS/Accounts/Cli.pm
+lib/LATMOS/Accounts/Cli/Base.pm
+lib/LATMOS/Accounts/Cli/Context.pm
+lib/LATMOS/Accounts/Cli/Object.pm
 lib/LATMOS/Accounts/I18N.pm
 lib/LATMOS/Accounts/Log.pm
@@ -161,4 +164,5 @@
 sample/latmos-accounts.ini
 sample/list.cfg
+scripts/build_orig_schema
 scripts/ochelper
 scripts/pod2xhtml.pl
Index: trunk/LATMOS-Accounts/bin/la-cli
===================================================================
--- trunk/LATMOS-Accounts/bin/la-cli	(revision 2208)
+++ trunk/LATMOS-Accounts/bin/la-cli	(revision 2209)
@@ -6,4 +6,5 @@
 use Getopt::Long;
 use Pod::Usage;
+use LATMOS::Accounts::Cli::Context;
 use LATMOS::Accounts::Cli;
 
@@ -21,5 +22,5 @@
     'c|config=s'        => \my $config,
     'b|base=s'          => \my $base,
-    't|transaction'     => sub { $LATMOS::Accounts::Cli::trans_mode = 1 },
+    't|transaction'     => \my $transmode,
     'no-unexp|wo-unexp' => \my $nounexp,
     'with-unexp'        => \my $unexp,
@@ -61,15 +62,22 @@
 $labase->unexported($nounexp ? 0 : 1);
 
-my $globalenv = LATMOS::Accounts::Cli::globalenv($labase);
+my $Context = LATMOS::Accounts::Cli::Context->new(
+    base => $labase,
+    TransMode => $transmode ? 1 : 0,
+);
+
+my $Env = LATMOS::Accounts::Cli->new(
+    Context => $Context,
+);
 
 if (@ARGV) {
-    $globalenv->run(@ARGV);
+    $Env->run(@ARGV);
 } else {
-    my $history = "$ENV{HOME}/.lacli_history";
-    $LATMOS::Accounts::Cli::term->ReadHistory($history);
-    $globalenv->cli;
-    if (! $LATMOS::Accounts::Cli::term->WriteHistory($history)) {
-        warn "Cannot write history:  $!\n";
-    }
+     my $history = "$ENV{HOME}/.lacli_history";
+     $Env->Context->Term->ReadHistory($history);
+     $Env->cli;
+     if (! $Env->Context->Term->WriteHistory($history)) {
+         warn "Cannot write history:  $!\n";
+     }
 }
 print "\n";
Index: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli.pm
===================================================================
--- trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli.pm	(revision 2208)
+++ trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli.pm	(revision 2209)
@@ -5,4 +5,5 @@
 use strict;
 use warnings;
+use Moose;
 use LATMOS::Accounts::Log;
 use LATMOS::Accounts::Utils;
@@ -10,4 +11,7 @@
 use Text::ParseWords;
 use Getopt::Long;
+use LATMOS::Accounts::Cli::Object;
+
+extends 'LATMOS::Accounts::Cli::Base';
 
 =head1 NAME
@@ -21,18 +25,4 @@
 =cut
 
-{
-    open (my $fh, "/dev/tty" )
-        or eval 'sub Term::ReadLine::findConsole { ("&STDIN", "&STDERR") }';
-    die $@ if $@;
-    close ($fh);
-}
-
-our $term = Term::ReadLine->new('LA CLI');
-$term->MinLine(99999);
-my $OUT = $term->OUT || \*STDOUT;
-
-our $trans_mode = 0;
-my $trans_start = 0;
-
 =head1 FUNCTIONS
 
@@ -45,9 +35,11 @@
 =cut
 
-sub globalenv {
-    my ($labase) = @_;
-    my $env = LATMOS::Accounts::Cli->new({ prompt => sub { $_[0]->base->label . " cli" }, },
-        $labase);
-    $env->add_func('unexported', {
+sub BUILD {
+    my ( $self ) = @_;
+
+    my $labase = $self->base;
+    my $OUT = $self->Context->Out;
+
+    $self->add_func('unexported', {
         help => 'unexported yes|no|show - switch or show base mode regarding' .
             ' unexported objects',
@@ -58,13 +50,13 @@
         },
         code => sub {
-            my ($env, $arg) = @_;
+            my ($self, $arg) = @_;
             if ($arg eq 'yes') {
-                $env->base->unexported(1);
+                $self->base->unexported(1);
                 print $OUT "Unexported are now show\n";
             } elsif ($arg eq 'no') {
-                $env->base->unexported(0);
+                $self->base->unexported(0);
                 print $OUT "Unexported are no longer show\n";
             } elsif ($arg eq 'show') {
-                print $OUT "Unexported objects " . ($env->base->unexported ?
+                print $OUT "Unexported objects " . ($self->base->unexported ?
                 "enable" : "disable") . "\n";
             } else {
@@ -73,5 +65,5 @@
         },
     });
-    $env->add_func('ls', {
+    $self->add_func('ls', {
             help => 'ls object_type - list object of type object_type', 
             completion => sub {
@@ -88,5 +80,5 @@
             },
         });
-    $env->add_func('search', {
+    $self->add_func('search', {
             help => 'search objecttype filter1 [filter2...] - search object according filter',
             completion => sub {
@@ -96,10 +88,10 @@
             },
             code => sub {
-                my ($env, @args) = @_;
+                my ($self, @args) = @_;
                 if ($_[1]) {
-                    my @res = $env->base->search_objects(@args);
+                    my @res = $self->base->search_objects(@args);
                     print $OUT map { "$_\n" } @res;
-                    $env->{_lastsearch} = \@res;
-                    $env->{_lastsearchtype} = $args[0];
+                    $self->{_lastsearch} = \@res;
+                    $self->{_lastsearchtype} = $args[0];
                 } else {
                     print $OUT "Object type missing\n";
@@ -107,25 +99,25 @@
             },
         });
-    $env->add_func('expired', {
+    $self->add_func('expired', {
         help => 'expired [delay] - list expired account more than delay (default is now)',
         code => sub {
-            my ($env, $expire) = @_;
-            my @users = $env->base->find_expired_users($expire);
+            my ($self, $expire) = @_;
+            my @users = $self->base->find_expired_users($expire);
             print $OUT map { "$_\n" } @users;
-            $env->{_lastsearchtype} = 'user';
-            $env->{_lastsearch} = \@users;
-        },
-    }) if ($env->base->can('find_expired_users'));
-    $env->add_func('expires', {
+            $self->{_lastsearchtype} = 'user';
+            $self->{_lastsearch} = \@users;
+        },
+    }) if ($self->base->can('find_expired_users'));
+    $self->add_func('expires', {
         help => 'expires [delay] - list account expiring before delay (default is 1 month)',
         code => sub {
-            my ($env, $expire) = @_;
-            my @users = $env->base->find_next_expire_users($expire);
+            my ($self, $expire) = @_;
+            my @users = $self->base->find_next_expire_users($expire);
             print $OUT map { "$_\n" } @users;
-            $env->{_lastsearchtype} = 'user';
-            $env->{_lastsearch} = \@users;
-        },
-    }) if ($env->base->can('find_next_expire_users'));
-    $env->add_func('select', {
+            $self->{_lastsearchtype} = 'user';
+            $self->{_lastsearch} = \@users;
+        },
+    }) if ($self->base->can('find_next_expire_users'));
+    $self->add_func('select', {
             help => 'select object_type - select objects to perform action on it',
             completion => sub {
@@ -137,10 +129,10 @@
             },
             code => sub {
-                my ($env, $otype, @ids) = @_;
+                my ($self, $otype, @ids) = @_;
                 my @objs;
                 if ($otype eq '@') {
-                    if (@{$env->{_lastsearch} || []}) {
-                        $otype = $env->{_lastsearchtype};
-                        @ids = @{$env->{_lastsearch}};
+                    if (@{$self->{_lastsearch} || []}) {
+                        $otype = $self->{_lastsearchtype};
+                        @ids = @{$self->{_lastsearch}};
                     } else {
                         print $OUT "No results store from previous search\n";
@@ -153,5 +145,5 @@
                 }
                 foreach (@ids) {
-                    my $obj = $env->base->get_object($otype, $_) or do {
+                    my $obj = $self->base->get_object($otype, $_) or do {
                         print $OUT "Cannot get $otype $_\n";
                         return;
@@ -160,11 +152,15 @@
                 }
                 print $OUT "Selecting $otype " . join(', ', @ids) . "\n";
-                objenv($_[0]->base, $otype, @objs)->cli();
+                LATMOS::Accounts::Cli::Object->new(
+                    Context => $self->Context,
+                    otype   => $otype,
+                    objs    => \@objs,
+                )->cli();
             },
         });
-    $env->add_func('create', {
-            code => sub {
-                my ($env, $otype) = @_;
-                my $helper = $env->base->ochelper($otype);
+    $self->add_func('create', {
+            code => sub {
+                my ($self, $otype) = @_;
+                my $helper = $self->base->ochelper($otype);
                 my $info = undef;
                 while (1) {
@@ -175,8 +171,8 @@
                         if ($status eq 'CREATED') {
                             print $OUT "Object created\n";
-                            $env->commit;
+                            $self->commit;
                         } else {
                             print $OUT "Nothing done\n";
-                            $env->rollback;
+                            $self->rollback;
                         }
                         return;
@@ -184,12 +180,12 @@
 
                     if ($info->{name}{ask}) {
-                        my $line = $term->readline("Name of the object ?");
+                        my $line = $self->Context->Term->readline("Name of the object ?");
                         $info->{name}{content} = $line;
                     }
                     foreach my $attr (@{$info->{ask} || []}) {
-                        $term->Attribs->{completion_function} = sub {
+                        $self->Context->Term->Attribs->{completion_function} = sub {
                             $info->{contents}{$attr}
                         };
-                        my $line = $term->readline(sprintf('  %s %s? ',
+                        my $line = $self->Context->Term->readline(sprintf('  %s %s? ',
                                 $attr,
                                 $info->{contents}{$attr}
@@ -203,9 +199,9 @@
         }
     );
-    $env->add_func('exchangeip', 
+    $self->add_func('exchangeip', 
         {
             help => 'Exchange two IP on host',
             code => sub {
-                my ($env, @args) = @_;
+                my ($self, @args) = @_;
                 my ($ip1, $ip2) =
                     grep { $_ && $_ =~ /\d+\.\d+\.\d+\.\d+/ } @args;
@@ -214,21 +210,21 @@
                     return;
                 }
-                if ($env->base->nethost_exchange_ip($ip1, $ip2)) {
+                if ($self->base->nethost_exchange_ip($ip1, $ip2)) {
                     print $OUT "$ip1 and $ip2 get exchange\n";
-                    $env->commit;
-                } else {
-                    $env->rollback;
+                    $self->commit;
+                } else {
+                    $self->rollback;
                 }   
             },
             completion => sub {
-                my ($env, $carg, @args) = @_;
+                my ($self, $carg, @args) = @_;
                 if ($args[-1] && $args[-1] !~ m/\d+\.\d+\.\d+\.\d+/) {
-                    if (my $obj = $env->base->get_object('nethost', $args[-1])) {
+                    if (my $obj = $self->base->get_object('nethost', $args[-1])) {
                         return $obj->get_attributes('ip');
                     }
                 } else {
                     my @list = 
-                    ($env->base->attributes_summary('nethost', 'ip'),
-                        $env->base->list_objects('nethost'));
+                    ($self->base->attributes_summary('nethost', 'ip'),
+                        $self->base->list_objects('nethost'));
                     return @list;
                 }
@@ -236,768 +232,7 @@
         }
     );
-    $env->add_func('user',  { alias => [qw'select user' ] });
-    $env->add_func('group', { alias => [qw'select group'] });
-    return $env
-}
-
-=head2 objenv ($labase, $otype, @objs)
-
-Return a C<cli> envirronment over object.
-
-=cut
-
-sub objenv {
-    my ($labase, $otype, @objs) = @_;
-    my $objenv = LATMOS::Accounts::Cli->new(
-        {
-            prompt => sub {
-                sprintf("%s %s/%s",
-                    $_[0]->base->label,
-                    $_[0]->{_otype},
-                    @{$_[0]->{_objects}} > 1 ? '(' .
-                    scalar(@{$_[0]->{_objects}}) . ' obj.)' : $_[0]->{_objects}[0]->id,
-                );
-            },
-        },
-        $labase
-    );
-    $objenv->{_otype} = $otype;
-    $objenv->{_objects} = [ @objs ];
-    $objenv->add_func('+', {
-        help => 'add item to selection',
-        code => sub {
-            my ($env, @ids) = @_;
-            my %ids = map { $_->id => 1 } @{$env->{_objects}};
-            foreach (@ids) {
-                $ids{$_} and next;
-                my $o = $env->base->get_object($env->{_otype}, $_) or next;
-                push(@{$env->{_objects}}, $o);
-            }
-            printf $OUT "select is now %s: %s\n", $env->{_otype}, join(', ', map {
-                $_->id } @{$env->{_objects}});
-        },
-        completion => sub {
-            my ($env, undef, @ids) = @_;
-            my %ids = map { $_->id => 1 } @{$env->{_objects}};
-            return ( grep { ! $ids{$_} } $env->base->list_objects($env->{_otype}));
-        },
-        }
-    );
-    $objenv->add_func('-', {
-        help => 'add item to selection',
-        code => sub {
-            my ($env, @ids) = @_;
-            my %ids = map { $_ => 1 } @ids;
-            my @newobjs = grep { !$ids{$_->id} } @{$env->{_objects}};
-
-            if (!@newobjs) {
-                print $OUT "This would remove all objects from the list...\n";
-                return;
-            } else {
-                @{$env->{_objects}} = @newobjs;
-            }
-            printf $OUT "select is now %s: %s\n", $env->{_otype}, join(', ', map {
-                $_->id } @{$env->{_objects}});
-        },
-        completion => sub {
-            my ($env, undef, @ids) = @_;
-            my %ids = map { $_ => 1 } @ids;
-            grep { !$ids{$_} } map { $_->id } @{$env->{_objects}};
-        },
-        }
-    );
-    $objenv->add_func('show', {
-        help => 'show attributes - show an attributes of object',
-        code => sub {
-            my ($env, $attr) = @_;
-            if (!$attr) {
-                foreach (@{$env->{_objects}}) {
-                    print $OUT $_->dump;
-                }
-            } else {
-                foreach my $u (@{$env->{_objects}}) {
-                    print $OUT sort map { $u->id . ': ' .($_ || '') . "\n" } $u->get_attributes($attr);
-                }
-            }
-        },
-        completion => sub {
-            if (!$_[2]) {
-                return $_[0]->base->list_canonical_fields($_[0]->{_otype}, 'r')
-            }
-        },
-    });
-    $objenv->add_func('print', {
-        help => 'print fmt - show attributes using template',
-        code => sub {
-            my ($env, $fmt) = @_;
-            if (!defined($fmt)) {
-                print $OUT "no format given";
-                return;
-            }
-            foreach (@{$env->{_objects}}) {
-                print $OUT $_->queryformat($fmt) . "\n";
-            }
-        },
-    });
-    $objenv->add_func('unset', {
-        help => 'unset attribute - unset specified attribute',
-        code => sub {
-            my ($env, $attr) = @_;
-            $attr or do {
-                print $OUT "Attributes must be specified";
-                return;
-            };
-            foreach (@{$env->{_objects}}) {
-                defined $_->set_c_fields($attr => undef) or do {
-                    print $OUT "cannot unset attributes $attr for " . $_->id .
-                    "\n";
-                    return;
-                };
-            }
-            $env->commit;
-            print $OUT "Changes applied\n";
-        },
-        completion => sub {
-            my ($env, $lastw, @args) = @_;
-            if (!$args[0]) {
-                return $env->base->list_canonical_fields($env->{_otype}, 'w')
-            }
-        },
-    });
-    $objenv->add_func('set', {
-        help => 'set attribute value - set an attributes to single value "value"',
-        code => sub {
-            my ($env, $attr, @value) = @_;
-            @value or do {
-                print $OUT "attribute and value must be specified\n";
-                return;
-            };
-            foreach (@{$env->{_objects}}) {
-                defined $_->set_c_fields($attr => @value <= 1 ? $value[0] :
-                    \@value) or do {
-                    $_->base->rollback;
-                    printf $OUT "Cannot set $attr to %s for %s\n", join(', ',
-                        @value), $_->id;
-                    return;
-                };
-            }
-            $env->commit;
-            print $OUT "Done.\n";
-        },
-        completion => sub {
-            my ($env, $lastw, @args) = @_;
-            if (!$args[0]) {
-                return $env->base->list_canonical_fields($env->{_otype}, 'w')
-            } else {
-                my $attr = $env->base->attribute($env->{_otype}, $args[0]);
-                if ($attr->has_values_list) {
-                    $attr->can_values;
-                } elsif (@{$env->{_objects}} == 1) {
-                    return
-                    $env->{_objects}[0]->get_attributes($args[0]);
-                }
-            }
-        },
-    });
-    $objenv->add_func('add', {
-        help => 'add a value to an attribute',
-        code => sub {
-            my ($env, $attr, @value) = @_;
-            @value or do {
-                print $OUT "attribute and value must be specified\n";
-                return;
-            };
-            foreach (@{$env->{_objects}}) {
-                my @attrv = grep { $_ } $_->get_attributes($attr);
-                defined $_->set_c_fields($attr => [ @attrv, @value ]) or do {
-                    $_->base->rollback;
-                    printf $OUT "Cannot set $attr to %s for %s\n", join(', ',
-                        @value), $_->id;
-                    return;
-                };
-            }
-            $env->commit;
-            print $OUT "done\n";
-        },
-        completion => sub {
-            my ($env, $lastw, @args) = @_;
-            if (!$args[0]) {
-                return grep {
-                    $env->base->attribute($env->{_otype}, $_)->{multiple}
-                } $env->base->list_canonical_fields($env->{_otype}, 'w')
-            } else {
-                my $attr = $env->base->attribute($env->{_otype}, $args[0]);
-                if ($attr->has_values_list) {
-                    $attr->can_values;
-                } elsif (@{$env->{_objects}} == 1) {
-                    return
-                    $env->{_objects}[0]->get_attributes($args[0]);
-                }
-            }
-        },
-    });
-    $objenv->add_func('remove', {
-        help => 'remove a value from an attribute',
-        code => sub {
-            my ($env, $attr, @value) = @_;
-            @value or do {
-                print $OUT "attribute and value must be specified\n";
-                return;
-            };
-            foreach (@{$env->{_objects}}) {
-                my @attrv = grep { $_ } $_->get_attributes($attr);
-                foreach my $r (@value) {
-                    @attrv = grep { $_ ne $r } @attrv;
-                }
-                defined $_->set_c_fields($attr => @attrv ? [ @attrv ] : undef) or do {
-                    $_->rollback;
-                    printf $OUT "Cannot set $attr to %s for %s\n", join(', ',
-                        @value), $_->id;
-                    return;
-                };
-            }
-            $env->commit;
-            print $OUT "done\n";
-        },
-        completion => sub {
-            my ($env, $lastw, @args) = @_;
-            if (!$args[0]) {
-                return grep {
-                    $env->base->attribute($env->{_otype}, $_)->{multiple}
-                } $env->base->list_canonical_fields($env->{_otype}, 'w')
-            } else {
-                my $attr = $env->base->attribute($env->{_otype}, $args[0]);
-                if (@{$env->{_objects}} == 1) {
-                    return
-                    $env->{_objects}[0]->get_attributes($args[0]);
-                }
-            }
-        },
-    });
-    $objenv->add_func('list', {
-        help => 'list current selected objects',
-        code => sub {
-            printf $OUT "%s: %s\n", $_[0]->{_otype}, join(', ', map { $_->id }
-            @{$_[0]->{_objects}});
-        }
-    });
-    $objenv->add_func('edit', {
-            help => 'edit [object] - edit selected object using vi',
-            completion => sub {
-                return map { $_->id } @{$_[0]->{_objects}}
-            },
-            code => sub {
-                my ($env, $id) = @_;
-                my $obj;
-                if ($id) {
-                    $obj = grep { $_->id = $id } @{$env->{_objects}} or do {
-                        print $OUT "$id is not part of selected objects\n";
-                        return;
-                    };
-                } elsif (@{$env->{_objects}} == 1) {
-                    $obj = $env->{_objects}[0]
-                } else {
-                    print $OUT "multiple objects selected but can edit only one,"
-                    . "please specify which one\n";
-                    return;
-                }
-                my $res = LATMOS::Accounts::Utils::dump_read_temp_file(
-                    sub {
-                        my ($fh) = @_;
-                        $obj->text_dump($fh,
-                            {
-                                empty_attr => 1,
-                                only_rw => 1,
-                            }
-                        );
-                    },
-                    sub {
-                        my ($fh) = @_;
-                        my %attr = LATMOS::Accounts::Utils::parse_obj_file($fh);
-                        my $res = $obj->set_c_fields(%attr);
-                        if ($res) {
-                            print $OUT "Changes applied\n";
-                            $env->commit;
-                        }
-                        else { print $OUT "Error applying changes\n" }
-                        return $res ? 1 : 0;
-                    }
-                );
-            },
-        });
-    $objenv->add_func('delete', {
-        help => 'delete - delete selected object',
-        code => sub {
-            my ($env) = @_;
-            printf $OUT "%s: %s\ndelete selected objects ? (yes/NO)\n",
-            $env->{_otype}, join(', ', map { $_->id } @{$env->{_objects}});
-            my $reply = <STDIN> || ''; chomp($reply);
-            if ($reply eq 'yes') {
-                foreach (@{$env->{_objects}}) {
-                    $env->base->delete_object($env->{_otype}, $_->id) or do {
-                        print $OUT "Cannot delete " . $_->id . "\n";
-                        return;
-                    };
-                }
-                $env->commit;
-                return "EXIT";
-            } else {
-                print $OUT "cancel !\n"
-            }
-        },
-    });
-    if (grep { $objenv->base->attribute($otype, $_)->reference }
-        $objenv->base->list_canonical_fields($otype, 'r')) {
-        $objenv->add_func('select', {
-            help => 'select attribute [object]',
-            code => sub {
-                my ($env, $attrname, @objects) = @_;
-
-                my $attr = $env->base->attribute(
-                    $env->{_otype},
-                    $attrname
-                ) or do {
-                    print $OUT "No attribute $attrname";
-                    return;
-                };
-                my $totype = $attr->reference or return;
-
-                if (! @objects) {
-                    @objects = grep { $_ } 
-                      map { $_->get_attributes($attrname) } @{$env->{_objects}};
-                }
-                {
-                    my %uniq = map { $_ => 1 } @objects;
-                    @objects = keys %uniq;
-                }
-                my @objs = (grep { $_ } map { $env->base->get_object($totype, $_) }
-                        @objects);
-                return if (!@objs);
-                print $OUT "Selecting $otype " . join(', ', map { $_->id } @objs) . "\n";
-                objenv($_[0]->base, $totype, @objs)->cli();
-            },
-            completion => sub {
-                if ($_[2]) {
-                    my $totype = $_[0]->base->attribute($_[0]->{_otype},
-                        $_[2])->reference or return;
-                    return grep { $_ }
-                           map { $_->get_attributes($_[2]) }
-                           @{$_[0]->{_objects}};
-                } else {
-                    return grep { $_[0]->base->attribute($otype, $_)->reference }
-                    $_[0]->base->list_canonical_fields($otype, 'r');
-                }
-            },
-            }
-        );
-    }
-
-    if (lc($otype) eq 'user') {
-        $objenv->add_func('group', {
-            help => 'group add|remove|primary goupname',
-            code => sub {
-                my ($env, $action, @groups) = @_;
-                foreach my $obj (@{$env->{_objects}}) {
-                    if ($action eq 'primary') {
-                        my $gid = $groups[0];
-                        if ($gid !~ /^\d/) {
-                            my $gobj = $env->base->get_object('group', $gid) or
-                            do {
-                                print $OUT "Cannot find group $gid\n";
-                                return;
-                            };
-                            $gid = $gobj->get_attributes('gidNumber');
-                        }
-                        $obj->set_c_fields('gidNumber', $gid);
-                    } else {
-                        my %gr;
-                        foreach ($obj->get_attributes('memberOf')) {
-                            $gr{$_} = 1;
-                        }
-                        if ($action eq 'add') {
-                            $gr{$_} = 1 foreach(@groups);
-                        } elsif ($action eq 'remove') {
-                            delete($gr{$_}) foreach(@groups);
-                        } else {
-                            print $OUT 'invalid action' . "\n";
-                            return;
-                        }
-                        defined $obj->set_c_fields('memberOf' => [ keys %gr ]) or do {
-                            print $OUT "cannot set memberOf attributes for " .
-                            $obj->id . "\n";
-                            return;
-                        };
-                    }
-                }
-                $env->commit;
-            },
-            completion => sub {
-                if (!$_[2]) {
-                    return (qw(add remove primary));
-                } else {
-                    if ($_[2] eq 'remove') {
-                        my %uniq = map { $_ => 1 }
-                            grep { $_ }
-                            map { $_->get_attributes('memberOf') }
-                            @{$_[0]->{_objects}};
-                        return sort keys %uniq;
-                    } else {
-                        return $_[0]->base->search_objects('group');
-                    }
-                }
-            },
-        });
-    } elsif ($otype eq 'group') {
-        $objenv->add_func('member', {
-            help => 'member add|remove user',
-            code => sub {
-                my ($env, $action, @groups) = @_;
-                foreach my $obj (@{$env->{_objects}}) {
-                    my %gr;
-                    foreach ($obj->get_attributes('memberUID')) {
-                        $gr{$_} = 1;
-                    }
-                    if ($action eq 'add') {
-                        $gr{$_} = 1 foreach(@groups);
-                    } elsif ($action eq 'remove') {
-                        delete($gr{$_}) foreach(@groups);
-                    } else {
-                        print $OUT 'invalid action' . "\n";
-                        return;
-                    }
-                    defined $obj->set_c_fields('memberUID' => [ keys %gr ]) or do {
-                        print $OUT "cannot set memberUID attributes for " .
-                        $obj->id . "\n";
-                        return;
-                    };
-                }
-                $env->commit;
-            },
-            completion => sub {
-                if (!$_[2]) {
-                    return (qw(add remove));
-                } else {
-                    if ($_[2] eq 'remove') {
-                        my %uniq = map { $_ => 1 }
-                            grep { $_ }
-                            map { $_->get_attributes('member') }
-                            @{$_[0]->{_objects}};
-                        return sort keys %uniq;
-                    } else {
-                        return $_[0]->base->search_objects('user');
-                    }
-                }
-            },
-        });
-    }
-
-    return $objenv;
-}
-
-=head1 OBJECT FUNCTIONS
-
-=head2 new ($env, $labase)
-
-Create an envirronment object.
-
-C<$env> is functions descriptions.
-
-=cut
-
-sub new {
-    my ($class, $env, $labase) = @_;
-    bless($env, $class);
-    $env->{_labase} = $labase;
-
-    if ($labase->is_transactionnal) {
-        $env->add_func(
-            'transaction', {
-                help => 'change transaction mode',
-                code => sub {
-                    $trans_mode = $_[1] eq 'on' ? 1 : 0;
-                },
-                completion => sub {
-                    $trans_mode == 0 ? 'on' : 'off';
-                },
-            }
-        );
-        $env->add_func(
-            'begin', {
-                help => 'Start transaction',
-                code => sub {
-                    $trans_start = 1;
-                },
-            }
-        );
-        $env->add_func(
-            'commit', {
-                help => 'commit pending change',
-                code => sub {
-                    $_[0]->_commit;
-                },
-            }
-        );
-        $env->add_func(
-            'rollback', {
-                help => 'commit pending change',
-                code => sub {
-                    $_[0]->_rollback;
-                },
-            }
-        );
-    }
-    if ($labase->can('CreateAlias')) {
-        $env->add_func(
-            'newalias', {
-                help => 'Create an alias object',
-                code => sub {
-                    my ($self, $otype, $name, $for) = @_;
-                    if ($self->base->CreateAlias($otype, $name, $for)) {
-                        print $OUT "Alias $otype/$name Created\n";
-                        $self->commit;
-                    }
-                },
-                completion => sub {
-                    if ($_[3]) {
-                        return $_[0]->base->list_objects($_[2]);
-                    } elsif (!$_[2]) {
-                        return $_[0]->base->list_supported_objects;
-                    } else {
-                        return;
-                    }
-                }
-            },
-        );
-        $env->add_func(
-            'rmalias', {
-                help => 'Remove an alias object',
-                code => sub {
-                    my ($self, $otype, $name) = @_;
-                    if ($self->base->RemoveAlias($otype, $name)) {
-                        print $OUT "Alias $otype/$name Removed\n";
-                        $self->commit;
-                    }
-                },
-                completion => sub {
-                    if (!$_[2]) {
-                        return $_[0]->base->list_supported_objects;
-                    } else {
-                        return $_[0]->base->search_objects($_[2], 'oalias=*');
-                    }
-                }
-            },
-        );
-        $env->add_func(
-            'updalias', {
-                help => 'Update an alias object',
-                code => sub {
-                    my ($self, $otype, $name, $for) = @_;
-                    my $obj = $self->base->GetAlias($otype, $name) or do {
-                        print $OUT "No alias $otype/$name found";
-                        return;
-                    };
-                    if ($obj->set_c_fields(oalias => $for)) {
-                        print $OUT "Alias $otype/$name Updated\n";
-                        $self->commit;
-                    }
-                },
-                completion => sub {
-                    if ($_[3]) {
-                        return $_[0]->base->list_objects($_[2]);
-                    } elsif($_[2]) {
-                        return $_[0]->base->search_objects($_[2], 'oalias=*');
-                    } else {
-                        return $_[0]->base->list_supported_objects;
-                    }
-                }
-            },
-        );
-    }
-    $env->add_func('quit', { help => 'quit - exit the tool',
-            code => sub { print "\n"; exit(0) }, });
-    $env->add_func('exit', { help => "exit current mode",
-            code => sub { return "EXIT" }, });
-    $env->add_func('help', {
-        help => 'help [command] - print help about command',
-        completion => sub {
-            if (!$_[2]) { return sort keys %{ $_[0]->{funcs} || {}} }
-        },
-        code => sub {
-            my ($self, $name) = @_;
-            if (!$name) {
-                print $OUT join(', ', sort keys %{ $self->{funcs} || {}}) . "\n";
-            } elsif ($self->{funcs}{$name}{alias}) {
-                print $OUT "$name is an alias for " . join(' ',
-                    @{$self->{funcs}{$name}{alias}}) . "\n";
-            } elsif ($self->{funcs}{$name}{help}) {
-                print $OUT $self->{funcs}{$name}{help} . "\n";
-            } else {
-                print $OUT "No help availlable\n";
-            }
-        },
-    });
-
-    $env;
-}
-
-=head2 base
-
-Return the attached base object.
-
-=cut
-
-sub base { $_[0]->{_labase} }
-
-=head2 cli
-
-Start the main loop
-
-=cut
-
-sub cli {
-    my ($self) = @_;
-    while (1) {
-        $term->Attribs->{completion_function} = sub {
-            $self->complete($_[0], shellwords(substr($_[1], 0, $_[2])));
-        };
-        defined (my $line = $term->readline($self->prompt)) or do {
-            print $OUT "\n";
-            return;
-        };
-        $term->addhistory($line);
-        my $res = $self->run(shellwords($line));
-        $self->rollback if (!$trans_mode);
-        if ($res && $res eq 'EXIT') { print $OUT "\n"; return }
-    }
-}
-
-=head2 prompt
-
-Wait user to input command
-
-=cut
-
-sub prompt {
-    my ($self) = @_;
-    my $pr = $self->{prompt}
-        ? $self->{prompt}->($self)
-        : "LA cli";
-    return sprintf(
-        "%s%s%s ",
-        $pr,
-        $trans_start ? '-' : '=',
-        $trans_mode  ? '#' : '>',
-    );
-}
-
-=head2 add_func ($name, $param)
-
-Add new function in the envirronment
-
-=cut
-
-# TODO: hide this
-
-sub add_func {
-    my ($self, $name, $param) = @_;
-    $self->{funcs}{$name} = $param;
-}
-
-=head2 getoption ($opt, @args)
-
-Parse commmand line
-
-=cut
-
-sub getoption {
-    my ($self, $opt, @args) = @_;
-    local @ARGV = @args;
-    Getopt::Long::Configure("pass_through");
-    GetOptions(%{ $opt });
-
-    return @ARGV;
-}
-
-=head2 complete
-
-Return possible words according current entered words
-
-=cut
-
-sub complete {
-    my ($self, $lastw, $name, @args) = @_;
-    if (!$name) {
-        return grep { /^\Q$lastw\E/ } sort
-            (keys %{ $self->{funcs} || {}});
-    } elsif ($self->{funcs}{$name}{alias}) {
-        $self->complete($lastw, @{$self->{funcs}{$name}{alias}}, @args);
-    } elsif ($self->{funcs}{$name}{completion}) {
-        return map { my $t = $_; $t =~ s/\s/\\ /g; $t } grep { $_ && /^\Q$lastw\E/ } $self->{funcs}{$name}{completion}->($self, $lastw, @args);
-    } else {
-        return ();
-    }
-}
-
-=head2 run ($name, @args)
-
-Run functions
-
-=cut
-
-sub run {
-    my ($self, $name, @args) = @_;
-    return if (!$name);
-    if (!exists($self->{funcs}{$name})) {
-        print $OUT "No command $name found\n";
-    } elsif ($self->{funcs}{$name}{alias}) {
-        $self->run(@{$self->{funcs}{$name}{alias}}, @args);
-    } elsif ($self->{funcs}{$name}{code}) {
-        $self->{funcs}{$name}{code}->($self, @args);
-    } else {
-        print $OUT "No command $name found\n";
-    }
-}
-
-=head2 commit
-
-Call commit to base unelss in transaction mode
-
-=cut
-
-sub commit {
-    my ($self) = @_;
-    if ($trans_mode || $trans_start) {
-        $trans_start = 1;
-    } else {
-        $self->_commit;
-    }
-}
-
-sub _commit {
-    my ($self) = @_;
-    $self->base->commit;
-    $trans_start = 0;
-}
-
-=head2 rollback
-
-Perform rollback unless in transaction mode
-
-=cut
-
-sub rollback {
-    my ($self) = @_;
-    if ($trans_mode) {
-        print $OUT "All pending changes get rollback\n";
-    }
-    if (!$trans_start) {
-        $self->_rollback;
-    }
-}
-
-sub _rollback {
-    my ($self) = @_;
-    $self->base->rollback;
-    $trans_start = 0;
+    $self->add_func('user',  { alias => [qw'select user' ] });
+    $self->add_func('group', { alias => [qw'select group'] });
+    return $self
 }
 
Index: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli/Base.pm
===================================================================
--- trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli/Base.pm	(revision 2209)
+++ trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli/Base.pm	(revision 2209)
@@ -0,0 +1,340 @@
+package LATMOS::Accounts::Cli::Base;
+
+# $Id: Cli.pm 2145 2018-08-29 18:15:46Z nanardon $
+
+use strict;
+use warnings;
+use Moose;
+use LATMOS::Accounts::Cli::Context;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+use Term::ReadLine;
+use Text::ParseWords;
+use Getopt::Long;
+
+=head1 NAME
+
+LATMOS::Accounts::Cli - Command line interface functions
+
+=head1 DESCRIPTION
+
+This module handle envirronment and functons for L<la-cli> tools.
+
+=cut
+
+has Context => ( is => 'ro', isa => 'LATMOS::Accounts::Cli::Context' );
+
+=head1 FUNCTIONS
+
+=cut
+
+sub BUILD {
+    my $self = shift;
+
+    my $OUT = $self->Context->Out;
+
+    if ($self->base->is_transactionnal) {
+        $self->add_func(
+            'transaction', {
+                help => 'change transaction mode',
+                code => sub {
+                    $self->Context->TransMode($_[1] eq 'on' ? 1 : 0);
+                },
+                completion => sub {
+                    $self->Context->TransMode == 0 ? 'on' : 'off';
+                },
+            }
+        );
+        $self->add_func(
+            'begin', {
+                help => 'Start transaction',
+                code => sub {
+                    $self->Context->TransStarted(1);
+                },
+            }
+        );
+        $self->add_func(
+            'commit', {
+                help => 'commit pending change',
+                code => sub {
+                    $_[0]->_commit;
+                },
+            }
+        );
+        $self->add_func(
+            'rollback', {
+                help => 'commit pending change',
+                code => sub {
+                    $_[0]->_rollback;
+                },
+            }
+        );
+    }
+    if ($self->base->can('CreateAlias')) {
+        $self->add_func(
+            'newalias', {
+                help => 'Create an alias object',
+                code => sub {
+                    my ($self, $otype, $name, $for) = @_;
+                    if ($self->base->CreateAlias($otype, $name, $for)) {
+                        print $OUT "Alias $otype/$name Created\n";
+                        $self->commit;
+                    }
+                },
+                completion => sub {
+                    if ($_[3]) {
+                        return $_[0]->base->list_objects($_[2]);
+                    } elsif (!$_[2]) {
+                        return $_[0]->base->list_supported_objects;
+                    } else {
+                        return;
+                    }
+                }
+            },
+        );
+        $self->add_func(
+            'rmalias', {
+                help => 'Remove an alias object',
+                code => sub {
+                    my ($self, $otype, $name) = @_;
+                    if ($self->base->RemoveAlias($otype, $name)) {
+                        print $OUT "Alias $otype/$name Removed\n";
+                        $self->commit;
+                    }
+                },
+                completion => sub {
+                    if (!$_[2]) {
+                        return $_[0]->base->list_supported_objects;
+                    } else {
+                        return $_[0]->base->search_objects($_[2], 'oalias=*');
+                    }
+                }
+            },
+        );
+        $self->add_func(
+            'updalias', {
+                help => 'Update an alias object',
+                code => sub {
+                    my ($self, $otype, $name, $for) = @_;
+                    my $obj = $self->base->GetAlias($otype, $name) or do {
+                        print $OUT "No alias $otype/$name found";
+                        return;
+                    };
+                    if ($obj->set_c_fields(oalias => $for)) {
+                        print $OUT "Alias $otype/$name Updated\n";
+                        $self->commit;
+                    }
+                },
+                completion => sub {
+                    if ($_[3]) {
+                        return $_[0]->base->list_objects($_[2]);
+                    } elsif($_[2]) {
+                        return $_[0]->base->search_objects($_[2], 'oalias=*');
+                    } else {
+                        return $_[0]->base->list_supported_objects;
+                    }
+                }
+            },
+        );
+    }
+    $self->add_func('quit', { help => 'quit - exit the tool',
+            code => sub { print "\n"; exit(0) }, });
+    $self->add_func('exit', { help => "exit current mode",
+            code => sub { return "EXIT" }, });
+    $self->add_func('help', {
+        help => 'help [command] - print help about command',
+        completion => sub {
+            if (!$_[2]) { return sort keys %{ $_[0]->{funcs} || {}} }
+        },
+        code => sub {
+            my ($self, $name) = @_;
+            if (!$name) {
+                print $OUT join(', ', sort keys %{ $self->{funcs} || {}}) . "\n";
+            } elsif ($self->{funcs}{$name}{alias}) {
+                print $OUT "$name is an alias for " . join(' ',
+                    @{$self->{funcs}{$name}{alias}}) . "\n";
+            } elsif ($self->{funcs}{$name}{help}) {
+                print $OUT $self->{funcs}{$name}{help} . "\n";
+            } else {
+                print $OUT "No help availlable\n";
+            }
+        },
+    });
+}
+
+=head2 base
+
+Return the attached base object.
+
+=cut
+
+sub base { $_[0]->Context->base }
+sub term { $_[0]->Context->Term }
+sub print { shift->Context->print(@_) }
+
+=head2 cli
+
+Start the main loop
+
+=cut
+
+sub cli {
+    my ($self) = @_;
+
+    my $term = $_[0]->Context->Term;
+
+    while (1) {
+        $term->Attribs->{completion_function} = sub {
+            $self->complete($_[0], shellwords(substr($_[1], 0, $_[2])));
+        };
+        defined (my $line = $term->readline($self->prompt)) or do {
+            $self->print("\n");
+            return;
+        };
+        $term->addhistory($line);
+        my $res = $self->run(shellwords($line));
+        $self->rollback if (!$self->Context->TransMode);
+        if ($res && $res eq 'EXIT') { $self->print("\n"); return }
+    }
+}
+
+=head2 prompt
+
+Wait user to input command
+
+=cut
+
+sub promptPrefix { 'LA cli' }
+
+sub prompt {
+    my ($self) = @_;
+    my $pr = $self->promptPrefix;
+    return sprintf(
+        "%s%s%s ",
+        $pr,
+        $self->Context->TransStarted ? '-' : '=',
+        $self->Context->TransMode  ? '#' : '>',
+    );
+}
+
+=head2 add_func ($name, $param)
+
+Add new function in the envirronment
+
+=cut
+
+# TODO: hide this
+
+sub add_func {
+    my ($self, $name, $param) = @_;
+    $self->{funcs}{$name} = $param;
+}
+
+=head2 getoption ($opt, @args)
+
+Parse commmand line
+
+=cut
+
+sub getoption {
+    my ($self, $opt, @args) = @_;
+    local @ARGV = @args;
+    Getopt::Long::Configure("pass_through");
+    GetOptions(%{ $opt });
+
+    return @ARGV;
+}
+
+=head2 complete
+
+Return possible words according current entered words
+
+=cut
+
+sub complete {
+    my ($self, $lastw, $name, @args) = @_;
+    if (!$name) {
+        return grep { /^\Q$lastw\E/ } sort
+            (keys %{ $self->{funcs} || {}});
+    } elsif ($self->{funcs}{$name}{alias}) {
+        $self->complete($lastw, @{$self->{funcs}{$name}{alias}}, @args);
+    } elsif ($self->{funcs}{$name}{completion}) {
+        return map { my $t = $_; $t =~ s/\s/\\ /g; $t } grep { $_ && /^\Q$lastw\E/ } $self->{funcs}{$name}{completion}->($self, $lastw, @args);
+    } else {
+        return ();
+    }
+}
+
+=head2 run ($name, @args)
+
+Run functions
+
+=cut
+
+sub run {
+    my ($self, $name, @args) = @_;
+    return if (!$name);
+    if (!exists($self->{funcs}{$name})) {
+        $self->print("No command $name found\n");
+    } elsif ($self->{funcs}{$name}{alias}) {
+        $self->run(@{$self->{funcs}{$name}{alias}}, @args);
+    } elsif ($self->{funcs}{$name}{code}) {
+        $self->{funcs}{$name}{code}->($self, @args);
+    } else {
+        $self->print("No command $name found\n");
+    }
+}
+
+=head2 commit
+
+Call commit to base unelss in transaction mode
+
+=cut
+
+sub commit {
+    my ($self) = @_;
+    $self->Context->commit;
+}
+
+sub _commit {
+    my ($self) = @_;
+    $self->Context->_commit;
+}
+
+=head2 rollback
+
+Perform rollback unless in transaction mode
+
+=cut
+
+sub rollback {
+    my ($self) = @_;
+    $self->Context->rollback;
+}
+
+sub _rollback {
+    my ($self) = @_;
+    $self->Context->_rollback;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli/Context.pm
===================================================================
--- trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli/Context.pm	(revision 2209)
+++ trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli/Context.pm	(revision 2209)
@@ -0,0 +1,134 @@
+package LATMOS::Accounts::Cli::Context;
+
+# $Id: Cli.pm 2145 2018-08-29 18:15:46Z nanardon $
+
+use strict;
+use warnings;
+use Moose;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+use Term::ReadLine;
+use Text::ParseWords;
+use Getopt::Long;
+
+=head1 NAME
+
+LATMOS::Accounts::Cli - Command line interface functions
+
+=head1 DESCRIPTION
+
+This module handle envirronment and functons for L<la-cli> tools.
+
+=cut
+
+{
+    open (my $fh, "/dev/tty" )
+        or eval 'sub Term::ReadLine::findConsole { ("&STDIN", "&STDERR") }';
+    die $@ if $@;
+    close ($fh);
+}
+
+has Term => ( is => 'ro', isa => 'Term::ReadLine' );
+has TransMode => ( is => 'rw', isa => 'Bool', default => 0 );
+has TransStarted => ( is => 'rw', isa => 'Bool', default => 0 );
+has base => ( is => 'ro' );
+has Out => ( is => 'ro' );
+
+=head1 FUNCTIONS
+
+=head2 new ($env, $labase)
+
+Create an envirronment object.
+
+C<$env> is functions descriptions.
+
+=cut
+
+around BUILDARGS => sub {
+    my $orig = shift;
+    my $class = shift;
+
+    my $term = Term::ReadLine->new('LA CLI');
+    $term->MinLine(99999);
+    my $OUT = $term->OUT || \*STDOUT;
+
+
+    return $class->$orig( Out => $OUT, Term => $term, @_ );
+
+};
+
+=head2 print
+
+=cut
+
+sub print {
+    my ( $self, $str, @args ) = @_;
+    my $out = $self->Out;
+
+    printf $out $str, @args;
+}
+
+=head2 commit
+
+Call commit to base unelss in transaction mode
+
+=cut
+
+sub commit {
+    my ($self) = @_;
+    if ($self->TransMode || $self->TransStarted) {
+        $self->TransStarted(1);
+    } else {
+        $self->_commit;
+    }
+}
+
+sub _commit {
+    my ($self) = @_;
+    $self->base->commit;
+    $self->TransStarted(0);
+}
+
+=head2 rollback
+
+Perform rollback unless in transaction mode
+
+=cut
+
+sub rollback {
+    my ($self) = @_;
+    if ($self->TransMode) {
+        $self->print("All pending changes get rollback\n");
+    }
+    if (!$self->TransStarted) {
+        $self->_rollback;
+    }
+}
+
+sub _rollback {
+    my ($self) = @_;
+    $self->base->rollback;
+    $self->TransStarted(0);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli/Object.pm
===================================================================
--- trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli/Object.pm	(revision 2209)
+++ trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Cli/Object.pm	(revision 2209)
@@ -0,0 +1,510 @@
+package LATMOS::Accounts::Cli::Object;
+
+# $Id: Cli.pm 2145 2018-08-29 18:15:46Z nanardon $
+
+use strict;
+use warnings;
+use Moose;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+use Term::ReadLine;
+use Text::ParseWords;
+use Getopt::Long;
+
+extends 'LATMOS::Accounts::Cli::Base';
+
+=head1 NAME
+
+LATMOS::Accounts::Cli - Command line interface functions
+
+=head1 DESCRIPTION
+
+This module handle envirronment and functons for L<la-cli> tools.
+
+=cut
+
+=head1 FUNCTIONS
+
+=cut
+
+has otype => ( is => 'ro' );
+has objs  => ( is => 'rw' );
+
+sub BUILD {
+    my ( $self ) = @_;
+
+    my $labase = $self->base;
+    my $OUT = $self->Context->Out;
+
+    $self->{_otype} = $self->otype;
+    $self->{_objects} = $self->objs;
+
+    $self->add_func('+', {
+        help => 'add item to selection',
+        code => sub {
+            my ($env, @ids) = @_;
+            my %ids = map { $_->id => 1 } @{$env->{_objects}};
+            foreach (@ids) {
+                $ids{$_} and next;
+                my $o = $env->base->get_object($env->{_otype}, $_) or next;
+                push(@{$env->{_objects}}, $o);
+            }
+            printf $OUT "select is now %s: %s\n", $env->{_otype}, join(', ', map {
+                $_->id } @{$env->{_objects}});
+        },
+        completion => sub {
+            my ($env, undef, @ids) = @_;
+            my %ids = map { $_->id => 1 } @{$env->{_objects}};
+            return ( grep { ! $ids{$_} } $env->base->list_objects($env->{_otype}));
+        },
+        }
+    );
+    $self->add_func('-', {
+        help => 'add item to selection',
+        code => sub {
+            my ($env, @ids) = @_;
+            my %ids = map { $_ => 1 } @ids;
+            my @newobjs = grep { !$ids{$_->id} } @{$env->{_objects}};
+
+            if (!@newobjs) {
+                print $OUT "This would remove all objects from the list...\n";
+                return;
+            } else {
+                @{$env->{_objects}} = @newobjs;
+            }
+            printf $OUT "select is now %s: %s\n", $env->{_otype}, join(', ', map {
+                $_->id } @{$env->{_objects}});
+        },
+        completion => sub {
+            my ($env, undef, @ids) = @_;
+            my %ids = map { $_ => 1 } @ids;
+            grep { !$ids{$_} } map { $_->id } @{$env->{_objects}};
+        },
+        }
+    );
+    $self->add_func('show', {
+        help => 'show attributes - show an attributes of object',
+        code => sub {
+            my ($env, $attr) = @_;
+            if (!$attr) {
+                foreach (@{$env->{_objects}}) {
+                    print $OUT $_->dump;
+                }
+            } else {
+                foreach my $u (@{$env->{_objects}}) {
+                    print $OUT sort map { $u->id . ': ' .($_ || '') . "\n" } $u->get_attributes($attr);
+                }
+            }
+        },
+        completion => sub {
+            if (!$_[2]) {
+                return $_[0]->base->list_canonical_fields($_[0]->{_otype}, 'r')
+            }
+        },
+    });
+    $self->add_func('print', {
+        help => 'print fmt - show attributes using template',
+        code => sub {
+            my ($env, $fmt) = @_;
+            if (!defined($fmt)) {
+                print $OUT "no format given";
+                return;
+            }
+            foreach (@{$env->{_objects}}) {
+                print $OUT $_->queryformat($fmt) . "\n";
+            }
+        },
+    });
+    $self->add_func('unset', {
+        help => 'unset attribute - unset specified attribute',
+        code => sub {
+            my ($env, $attr) = @_;
+            $attr or do {
+                print $OUT "Attributes must be specified";
+                return;
+            };
+            foreach (@{$env->{_objects}}) {
+                defined $_->set_c_fields($attr => undef) or do {
+                    print $OUT "cannot unset attributes $attr for " . $_->id .
+                    "\n";
+                    return;
+                };
+            }
+            $env->commit;
+            print $OUT "Changes applied\n";
+        },
+        completion => sub {
+            my ($env, $lastw, @args) = @_;
+            if (!$args[0]) {
+                return $env->base->list_canonical_fields($env->{_otype}, 'w')
+            }
+        },
+    });
+    $self->add_func('set', {
+        help => 'set attribute value - set an attributes to single value "value"',
+        code => sub {
+            my ($env, $attr, @value) = @_;
+            @value or do {
+                print $OUT "attribute and value must be specified\n";
+                return;
+            };
+            foreach (@{$env->{_objects}}) {
+                defined $_->set_c_fields($attr => @value <= 1 ? $value[0] :
+                    \@value) or do {
+                    $_->base->rollback;
+                    printf $OUT "Cannot set $attr to %s for %s\n", join(', ',
+                        @value), $_->id;
+                    return;
+                };
+            }
+            $env->commit;
+            print $OUT "Done.\n";
+        },
+        completion => sub {
+            my ($env, $lastw, @args) = @_;
+            if (!$args[0]) {
+                return $env->base->list_canonical_fields($env->{_otype}, 'w')
+            } else {
+                my $attr = $env->base->attribute($env->{_otype}, $args[0]);
+                if ($attr->has_values_list) {
+                    $attr->can_values;
+                } elsif (@{$env->{_objects}} == 1) {
+                    return
+                    $env->{_objects}[0]->get_attributes($args[0]);
+                }
+            }
+        },
+    });
+    $self->add_func('add', {
+        help => 'add a value to an attribute',
+        code => sub {
+            my ($env, $attr, @value) = @_;
+            @value or do {
+                print $OUT "attribute and value must be specified\n";
+                return;
+            };
+            foreach (@{$env->{_objects}}) {
+                my @attrv = grep { $_ } $_->get_attributes($attr);
+                defined $_->set_c_fields($attr => [ @attrv, @value ]) or do {
+                    $_->base->rollback;
+                    printf $OUT "Cannot set $attr to %s for %s\n", join(', ',
+                        @value), $_->id;
+                    return;
+                };
+            }
+            $env->commit;
+            print $OUT "done\n";
+        },
+        completion => sub {
+            my ($env, $lastw, @args) = @_;
+            if (!$args[0]) {
+                return grep {
+                    $env->base->attribute($env->{_otype}, $_)->{multiple}
+                } $env->base->list_canonical_fields($env->{_otype}, 'w')
+            } else {
+                my $attr = $env->base->attribute($env->{_otype}, $args[0]);
+                if ($attr->has_values_list) {
+                    $attr->can_values;
+                } elsif (@{$env->{_objects}} == 1) {
+                    return
+                    $env->{_objects}[0]->get_attributes($args[0]);
+                }
+            }
+        },
+    });
+    $self->add_func('remove', {
+        help => 'remove a value from an attribute',
+        code => sub {
+            my ($env, $attr, @value) = @_;
+            @value or do {
+                print $OUT "attribute and value must be specified\n";
+                return;
+            };
+            foreach (@{$env->{_objects}}) {
+                my @attrv = grep { $_ } $_->get_attributes($attr);
+                foreach my $r (@value) {
+                    @attrv = grep { $_ ne $r } @attrv;
+                }
+                defined $_->set_c_fields($attr => @attrv ? [ @attrv ] : undef) or do {
+                    $_->rollback;
+                    printf $OUT "Cannot set $attr to %s for %s\n", join(', ',
+                        @value), $_->id;
+                    return;
+                };
+            }
+            $env->commit;
+            print $OUT "done\n";
+        },
+        completion => sub {
+            my ($env, $lastw, @args) = @_;
+            if (!$args[0]) {
+                return grep {
+                    $env->base->attribute($env->{_otype}, $_)->{multiple}
+                } $env->base->list_canonical_fields($env->{_otype}, 'w')
+            } else {
+                my $attr = $env->base->attribute($env->{_otype}, $args[0]);
+                if (@{$env->{_objects}} == 1) {
+                    return
+                    $env->{_objects}[0]->get_attributes($args[0]);
+                }
+            }
+        },
+    });
+    $self->add_func('list', {
+        help => 'list current selected objects',
+        code => sub {
+            printf $OUT "%s: %s\n", $_[0]->{_otype}, join(', ', map { $_->id }
+            @{$_[0]->{_objects}});
+        }
+    });
+    $self->add_func('edit', {
+            help => 'edit [object] - edit selected object using vi',
+            completion => sub {
+                return map { $_->id } @{$_[0]->{_objects}}
+            },
+            code => sub {
+                my ($env, $id) = @_;
+                my $obj;
+                if ($id) {
+                    $obj = grep { $_->id = $id } @{$env->{_objects}} or do {
+                        print $OUT "$id is not part of selected objects\n";
+                        return;
+                    };
+                } elsif (@{$env->{_objects}} == 1) {
+                    $obj = $env->{_objects}[0]
+                } else {
+                    print $OUT "multiple objects selected but can edit only one,"
+                    . "please specify which one\n";
+                    return;
+                }
+                my $res = LATMOS::Accounts::Utils::dump_read_temp_file(
+                    sub {
+                        my ($fh) = @_;
+                        $obj->text_dump($fh,
+                            {
+                                empty_attr => 1,
+                                only_rw => 1,
+                            }
+                        );
+                    },
+                    sub {
+                        my ($fh) = @_;
+                        my %attr = LATMOS::Accounts::Utils::parse_obj_file($fh);
+                        my $res = $obj->set_c_fields(%attr);
+                        if ($res) {
+                            print $OUT "Changes applied\n";
+                            $env->commit;
+                        }
+                        else { print $OUT "Error applying changes\n" }
+                        return $res ? 1 : 0;
+                    }
+                );
+            },
+        });
+    $self->add_func('delete', {
+        help => 'delete - delete selected object',
+        code => sub {
+            my ($env) = @_;
+            printf $OUT "%s: %s\ndelete selected objects ? (yes/NO)\n",
+            $env->{_otype}, join(', ', map { $_->id } @{$env->{_objects}});
+            my $reply = <STDIN> || ''; chomp($reply);
+            if ($reply eq 'yes') {
+                foreach (@{$env->{_objects}}) {
+                    $env->base->delete_object($env->{_otype}, $_->id) or do {
+                        print $OUT "Cannot delete " . $_->id . "\n";
+                        return;
+                    };
+                }
+                $env->commit;
+                return "EXIT";
+            } else {
+                print $OUT "cancel !\n"
+            }
+        },
+    });
+    if (grep { $self->base->attribute($self->otype, $_)->reference }
+        $self->base->list_canonical_fields($self->otype, 'r')) {
+        $self->add_func('select', {
+            help => 'select attribute [object]',
+            code => sub {
+                my ($env, $attrname, @objects) = @_;
+
+                my $attr = $env->base->attribute(
+                    $env->{_otype},
+                    $attrname
+                ) or do {
+                    print $OUT "No attribute $attrname";
+                    return;
+                };
+                my $totype = $attr->reference or return;
+
+                if (! @objects) {
+                    @objects = grep { $_ } 
+                      map { $_->get_attributes($attrname) } @{$env->{_objects}};
+                }
+                {
+                    my %uniq = map { $_ => 1 } @objects;
+                    @objects = keys %uniq;
+                }
+                my @objs = (grep { $_ } map { $env->base->get_object($totype, $_) }
+                        @objects);
+                return if (!@objs);
+                print $OUT "Selecting $env->otype " . join(', ', map { $_->id } @objs) . "\n";
+                LATMOS::Accounts::Cli::Object->new(
+                    Context => $env->Context,
+                    otype   => $totype,
+                    objs    => \@objs
+                )->cli();
+            },
+            completion => sub {
+                if ($_[2]) {
+                    my $totype = $_[0]->base->attribute($_[0]->{_otype},
+                        $_[2])->reference or return;
+                    return grep { $_ }
+                           map { $_->get_attributes($_[2]) }
+                           @{$_[0]->{_objects}};
+                } else {
+                    return grep { $_[0]->base->attribute($self->otype, $_)->reference }
+                    $_[0]->base->list_canonical_fields($self->otype, 'r');
+                }
+            },
+            }
+        );
+    }
+
+    if (lc($self->otype) eq 'user') {
+        $self->add_func('group', {
+            help => 'group add|remove|primary goupname',
+            code => sub {
+                my ($env, $action, @groups) = @_;
+                foreach my $obj (@{$env->{_objects}}) {
+                    if ($action eq 'primary') {
+                        my $gid = $groups[0];
+                        if ($gid !~ /^\d/) {
+                            my $gobj = $env->base->get_object('group', $gid) or
+                            do {
+                                print $OUT "Cannot find group $gid\n";
+                                return;
+                            };
+                            $gid = $gobj->get_attributes('gidNumber');
+                        }
+                        $obj->set_c_fields('gidNumber', $gid);
+                    } else {
+                        my %gr;
+                        foreach ($obj->get_attributes('memberOf')) {
+                            $gr{$_} = 1;
+                        }
+                        if ($action eq 'add') {
+                            $gr{$_} = 1 foreach(@groups);
+                        } elsif ($action eq 'remove') {
+                            delete($gr{$_}) foreach(@groups);
+                        } else {
+                            print $OUT 'invalid action' . "\n";
+                            return;
+                        }
+                        defined $obj->set_c_fields('memberOf' => [ keys %gr ]) or do {
+                            print $OUT "cannot set memberOf attributes for " .
+                            $obj->id . "\n";
+                            return;
+                        };
+                    }
+                }
+                $env->commit;
+            },
+            completion => sub {
+                if (!$_[2]) {
+                    return (qw(add remove primary));
+                } else {
+                    if ($_[2] eq 'remove') {
+                        my %uniq = map { $_ => 1 }
+                            grep { $_ }
+                            map { $_->get_attributes('memberOf') }
+                            @{$_[0]->{_objects}};
+                        return sort keys %uniq;
+                    } else {
+                        return $_[0]->base->search_objects('group');
+                    }
+                }
+            },
+        });
+    } elsif ($self->type eq 'group') {
+        $self->add_func('member', {
+            help => 'member add|remove user',
+            code => sub {
+                my ($env, $action, @groups) = @_;
+                foreach my $obj (@{$env->{_objects}}) {
+                    my %gr;
+                    foreach ($obj->get_attributes('memberUID')) {
+                        $gr{$_} = 1;
+                    }
+                    if ($action eq 'add') {
+                        $gr{$_} = 1 foreach(@groups);
+                    } elsif ($action eq 'remove') {
+                        delete($gr{$_}) foreach(@groups);
+                    } else {
+                        print $OUT 'invalid action' . "\n";
+                        return;
+                    }
+                    defined $obj->set_c_fields('memberUID' => [ keys %gr ]) or do {
+                        print $OUT "cannot set memberUID attributes for " .
+                        $obj->id . "\n";
+                        return;
+                    };
+                }
+                $env->commit;
+            },
+            completion => sub {
+                if (!$_[2]) {
+                    return (qw(add remove));
+                } else {
+                    if ($_[2] eq 'remove') {
+                        my %uniq = map { $_ => 1 }
+                            grep { $_ }
+                            map { $_->get_attributes('member') }
+                            @{$_[0]->{_objects}};
+                        return sort keys %uniq;
+                    } else {
+                        return $_[0]->base->search_objects('user');
+                    }
+                }
+            },
+        });
+    }
+
+    return $self;
+}
+
+sub promptPrefix {
+    my ($self) = @_;
+
+    sprintf("%s %s/%s",
+        $self->base->label,
+        $self->otype,
+        @{$self->objs} > 1
+            ? '(' . scalar(@{$self->objs}) . ' obj.)'
+            : $self->objs->[0]->id,
+    );
+}
+
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
