Rash - A Raku Shell

Exercise 3: Safe Alias

by Arne Sommer

Rash - Exercise 3: Safe Alias

[67.5.1] Published 13. April 2020.

You have read the Exercise text first?

Preventing an alias with the same name as a built-in command is easy. All we have to do is change «do-alias»:

File: rash-safe (changes only)
multi sub do-alias ($command)
{
  my ($alias, $full) = $command.split(/\s/, 2); 

  if %commands{$alias}
  {
    say "Unable to redefine internal command: $alias.";
  }
  else
  {
    %aliases{$alias} = $full;
  }
}

The complete program:

File: rash-safe
use Linenoise;

constant HIST_FILE = ( $*HOME.add: '.rash-hist' ).Str;
constant HIST_LEN  = 25;
constant CONF_FILE = ( $*HOME.add: '.rashrc' ).Str;

linenoiseHistoryLoad(HIST_FILE);
linenoiseHistorySetMaxLen(HIST_LEN);

my %commands; %commands{$_} = True for <alias cd exit help pwd rehash version which6>;
my %programs;
my %aliases;

read-conf;
do-rehash;

linenoiseSetCompletionCallback(-> $line, $c
{
  my @all = (|%commands.keys, |%programs.keys, |%aliases.keys);

  for @all.grep(/^ $line /).sort -> $cmd
  {
    linenoiseAddCompletion($c, $cmd);
  }
});

say 'rash: Enter «exit» to exit';

signal(SIGINT).tap();

while (my $line = linenoise '> ').defined
{
  linenoiseHistoryAdd($line);

  my $cmd = $line.words[0];
  $line.= subst(/^$cmd/, %aliases{$cmd}) if %aliases{$cmd};

  given $line
  {
    when "alias"           { do-alias; }
    when /^alias\s+(.*)/   { do-alias $0; }
    when /^cd\s+(\S+)/     { do-chdir $0; }
    when "exit"            { last; }
    when "help"            { say "Built-in commands: { %commands.keys.sort }" }
    when "pwd"             { say $*CWD.Str; }
    when "rehash"          { %programs = (); do-rehash; }
    when "version"         { say "Version 0.18"; }
    when /^which6\s+(\S+)/ { do-which $0; }
    default
    {
      my ($cmd, @args) = $line.words;
      %programs{$cmd}
        ?? do-run %programs{$cmd}, @args
        !! say "Unknown command: \"$_\" (use \"help\" for a list of commands)";
    }
  }
}

linenoiseHistorySave(HIST_FILE);

sub do-run ($cmd, @args)
{
  my $res = @args
    ?? run $cmd, @args
    !! run $cmd;

  if ! $res.pid
  {
    say "$cmd: command not found";
  }
  elsif $res.exitcode 
  {
    say "$cmd: exit with code { $res.exitcode }";
  }
}

sub do-chdir ($dir)
{
  say "cd: $dir: No such file or directory" unless chdir $dir;
}

sub do-rehash
{
  for %*ENV.split(":") -> $dir
  {
    next unless $dir.IO.d;
    %programs{.Str} = "$dir/"  ~ .Str for indir($dir, { dir(:x) }).sort
  }
}

sub do-which ($command)
{
  if %commands{$command}
  {
    say "$command is a rash built-in command.";
  }
  else
  {
    say %programs{$command} if %programs{$command};
  }
}

multi sub do-alias ($command)
{
  my ($alias, $full) = $command.split(/\s/, 2); 

  if %commands{$alias}
  {
    say "Unable to redefine internal command: $alias.";
  }
  else
  {
    %aliases{$alias} = $full;
  }
}

multi sub do-alias ()
{
  say "$_ -> %aliases{$_}" for %aliases.keys.sort; 
}

sub read-conf
{
  return unless CONF_FILE.IO.r;
  
  for CONF_FILE.IO.lines
  {
    when /^alias\s+(.*)/ { do-alias $0.Str }
  }
}