package Zoidberg::Fish::Commands; our $VERSION = '0.96'; use strict; use AutoLoader 'AUTOLOAD'; use Cwd; use Env qw/@CDPATH @DIRSTACK/; use base 'Zoidberg::Fish'; use Zoidberg::Utils qw/:default path getopt usage path2hashref/; # FIXME what to do with commands that use block input ? # currently hacked with statements like join(' ', @_) =head1 NAME Zoidberg::Fish::Commands - Zoidberg plugin with builtin commands =head1 SYNOPSIS This module is a Zoidberg plugin, see Zoidberg::Fish for details. =head1 DESCRIPTION This object contains internal/built-in commands for the Zoidberg shell. =head2 EXPORT None by default. =cut sub init { $_[0]{dir_hist} = [$ENV{PWD}]; # FIXME try to read log first $_[0]{_dir_hist_i} = 0; } =head1 COMMANDS =over 4 =item cd [-v|--verbose] [I|-|(+|-)I] =item cd (-l|--list) Changes the current working directory to I. When used with a single dash changes to OLDPWD. This command uses the environment variable 'CDPATH'. It serves as a search path when the directory you want to change to isn't found in the current directory. This command also uses a directory history. The '-number' and '+number' switches are used to change directory to an positive or negative offset in this history. =cut sub cd { # TODO [-L|-P] see man 1 bash my $self = shift; my ($dir, $done, $verbose); if (@_ == 1 and $_[0] eq '-') { # cd - $dir = $ENV{OLDPWD}; $verbose++; } else { my ($opts, $args) = getopt 'list,-l verbose,-v +* -* @', @_; if (@$args) { # 'normal' cd error 'to many arguments' if @$args > 1; $dir = $$args[0]; } if (%$opts) { $verbose++ if $$opts{verbose}; if (my ($opt) = grep /^[+-][^\d+lv]$/, @{$$opts{_opts}}) { error "unrecognized option '$opt'"; } elsif ($$opts{list}) { # list dirhist error 'to many args' if @$args; return $$self{shell}->builtin(qw/history --type pwd +1 -2/); # last pwd is current } elsif (my ($idx) = grep /^[+-]\d+$/, @{$$opts{_opts}}) { # cd back/forward in history error 'to many args' if @$args; $idx -= 1 if $idx < 1; # last pwd is current ($dir) = $$self{shell}->builtin(qw/history --type pwd/, $idx, $idx); $verbose++; } } } if ($dir) { # due to things like autofs we must *try* every possibility # instead of checking '-d' $done = chdir path($dir); if ($done) { message $dir if $verbose } elsif ($dir !~ m#^\.{0,2}/#) { for (@CDPATH) { next unless $done = chdir path("$_/$dir"); message "$_/$dir"; # verbose last; } } } else { message $ENV{HOME} if $verbose; $done = chdir($ENV{HOME}); } unless ($done) { error $dir.': Not a directory' unless -d $dir; error "Could not change to dir: $dir"; } } 1; __END__ =item exec I Execute I. This effectively ends the shell session, process flow will B return to the prompt. =cut sub exec { # FIXME not completely stable I'm afraid my $self = shift; $self->{shell}->{round_up} = 0; $self->{shell}->shell_string({fork_job => 0}, join(" ", @_)); # the process should not make it to this line $self->{shell}->{round_up} = 1; $self->{shell}->exit; } =item eval I Eval I like a shell command. Main use of this is to run code stored in variables. =cut sub eval { my $self = shift; $$self{shell}->shell(@_); } =item export I=I Set the environment variable I to I. TODO explain how export moved varraibles between the perl namespace and the environment =cut sub export { # TODO if arg == 1 and not hash then export var from zoid::eval to env :D my $self = shift; my ($opt, $args, $vals) = getopt 'unexport,n print,p *', @_; my $class = $$self{shell}{settings}{perl}{namespace}; no strict 'refs'; if ($$opt{unexport}) { for (@$args) { s/^([\$\@]?)//; next unless exists $ENV{$_}; if ($1 eq '@') { @{$class.'::'.$_} = split ':', delete $ENV{$_} } else { ${$class.'::'.$_} = delete $ENV{$_} } } } elsif ($$opt{print}) { output [ map { my $val = $ENV{$_}; $val =~ s/'/\\'/g; "export $_='$val'"; } sort keys %ENV ]; } else { # really export for (@$args) { s/^([\$\@]?)//; if ($1 eq '@') { # arrays my @env = defined($$vals{$_}) ? (@{$$vals{$_}}) : defined(*{$class.'::'.$_}{ARRAY}) ? (@{$class.'::'.$_}) : () ; $ENV{$_} = join ':', @env if @env; } else { # scalars $env = defined($$vals{$_}) ? $$vals{$_} : defined(${$class.'::'.$_}) ? ${$class.'::'.$_} : undef ; $ENV{$_} = $env if defined $env; } } } } =item setenv I I Like B, but with a slightly different syntax. =cut sub setenv { shift; my $var = shift; $ENV{$var} = join ' ', @_; } =item unsetenv I Set I to undefined. =cut sub unsetenv { my $self = shift; delete $ENV{$_} for @_; } =item set [+-][abCefnmnuvx] =item set [+o|-o] I