Skip to content

Latest commit

 

History

History
303 lines (231 loc) · 9.57 KB

alias.org

File metadata and controls

303 lines (231 loc) · 9.57 KB

Aliases for Elvish

Implementation of aliases for Elvish.

This file is written in literate programming style, to make it easy to explain. See alias.elv for the generated file.

Table of Contents

Usage

Install the elvish-modules package using epm:

use epm
epm:install github.com/zzamboni/elvish-modules

In your rc.elv, load this module:

use github.com/zzamboni/elvish-modules/alias

To define an alias:

alias:new alias command

Aliases are not automatically stored persistently. If you want to store it, you can use the &save option of the alias:new command:

alias:new &save alias command

Alternatively, once the alias is defined, you can save it using alias:save:

alias:new alias command
# test your alias, once you are happy:
alias:save alias

The alias:save command can receive one or more alias names, or with the &all option saves all the currently defined aliases:

alias:save &verbose &all

When saved, each alias is stored in a separate file under $alias:dir (~/.elvish/aliases/ by default). Saved aliases are automatically loaded when the module initializes.

Note that due to Elvish’s scoping rules, if you want to use a module from your alias, you need to load the module as well by specifying the &use option when defining the alias, which receives a list of module names to load, like this:

alias:new cd &use=[github.com/zzamboni/elvish-modules/dir] dir:cd

Normally, any arguments you specify to the alias are appended at the end of its definition. However, you can specify where they should appear by inserting the string {} (in Elvish you have to quote it like this =’{}’= ), then the arguments are inserted at that place in the command:

alias:new fdirs find '{}' -name foo

You can configure the string to use for the argument placeholder by setting the value of $aliases:arg-replacer. For example:

alias:arg-replacer = '@@@'
alias:new fdirs find @@@ -name foo

You can also use alias:bash-alias to use the bash syntax alias=command for defining aliases:

alias:bash-alias ls=exa

To list existing aliases:

alias:list
alias:ls

To remove an alias:

alias:rm alias

Implementation

Loading libraries

use re
use str

Configuration

The alias:dir variable determines where the alias files will be saved.

var dir = ~/.elvish/aliases

The alias:arg-replacer variable contains the string that will be used to indicate where the arguments will be inserted in the alias expansion.

var arg-replacer = '{}'

Creating aliases

Aliases are defined as functions with the corresponding name, which execute the body provided as the alias definition, always allowing optional parameters to be provided to the alias, and which are added at the end of the alias definition.

The $alias:aliases map is where all the alias definitions are stored, indexed by name. The stored value is a string containing the following two lines:

#alias:new aliasname command
edit:add-var aliasname~ {|@_args| command }

The first line is the alias definition as provided to the alias:new command, and the second is the code that gets evaluated to load the alias into the current session.

var aliases = [&]

The -define-alias function receives the name of the alias and a string containing its definition, which must include the corresponding edit:add-var call to load the alias into the interactive namespace as described above. It evaluates the alias body with eval, and stores its definition in $alias:aliases.

fn -define-alias {|name body|
  eval $body
  set aliases[$name] = $body
}

The -load-alias function receives the name of the alias and the file in which it is stored. It reads the definition and loads it into memory.

fn -load-alias {|name file|
  var body = (slurp < $file)
  -define-alias $name $body
}

The internal function alias:-save does the actual work of atomically writing an alias body to the corresponding file.

fn -save {|&verbose=$false name|
  if (has-key $aliases $name) {
    var tmp-file = (mktemp $dir/tmp.XXXXXXXXXX)
    var file = $dir/$name.elv
    echo $aliases[$name] > $tmp-file
    e:mv $tmp-file $file
    if $verbose {
      echo (styled "Alias "$name" saved to "$file"." green)
    }
  } else {
    echo (styled "Alias "$name" is not defined." red)
  }
}

The alias:save command is the user-facing interface to save an alias. It receives one or more alias names, and writes their definitions to the corresponding files. The &all option makes it save all the currently-defined aliases.

fn save {|&verbose=$false &all=$false @names|
  if $all {
    set names = [(keys $aliases)]
  }
  each {|n|
    -save &verbose=$verbose $n
  } $names
}

The alias:def function creates a new alias and loads it into the interactive namespace. The &use option can be used to specify a list of modules to load within the alias function (you can also specify the use command by hand as part of the alias). By default, an alias will add any arguments it receives to the end of its definition. But if the string $arg-replacer (default {}) appears in the definition (it has to appear as a space-separated word), then the arguments are inserted in its place, and NOT added at the end.

fn def {|&verbose=$false &save=$false &use=[] name @cmd|
  var use-statements = [(each {|m| put "use "$m";" } $use)]
  var args-at-end = '$@_args'
  var new-cmd = [
    (each {|e|
        if (eq $e $arg-replacer) {
          put '$@_args'
          set args-at-end = ''
        } else {
          repr $e
        }
    } $cmd)
  ]
  var body = ({
    echo "#alias:new" $name (if (not-eq $use []) { put "&use="(to-string $use) }) (each {|w| repr $w } $cmd)
    print "edit:add-var "$name'~ {|@_args| ' $@use-statements $@new-cmd $args-at-end '}'
  } | slurp)
  -define-alias $name $body
  if $save {
    save $name
  }
  if $verbose {
    echo (styled "Alias "$name" defined"(if $save { echo " and saved" } else { echo "" })"." green)
  }
}

alias:new is equivalent to alias:def.

var new~ = $def~

The alias:bash-alias command simply splits the arguments on the first equals sign, and calls alias:def with the two pieces.

fn bash-alias {|@args|
  var line = $@args
  var name cmd = (str:split &max=2 '=' $line)
  def $name $cmd
}

Listing aliases

To list aliases, we grep the aliases directory for the corresponding definition files. Each file has a marker at the beginning which includes the alias definition command. alias:list and alias:ls are equivalent.

fn list {
  keys $aliases | each {|n|
    echo (re:find '^#(alias:new .*)\n' $aliases[$n])[groups][1][text]
  }
}

var ls~ = $list~ # ls is an alias for list

Removing aliases

Removing an alias is achieved by removing its definition file. alias:rm and alias:undef are equivalent.

Alias removals do not take place in the current session, unless you manually remove them with the del command.

fn undef {|name|
  if (has-key $aliases $name) {
    var file = $dir/$name.elv
    e:rm -f $file
    del aliases[$name]
    edit:add-var $name"~" (external $name)
    echo (styled "Alias "$name" removed." green)
  } else {
    echo (styled "Alias "$name" does not exist." red)
  }
}

var rm~ = $undef~ # rm is an alias for undef

Load-time initialization

The init function is run automatically when the module is loaded. It creates the alias directory if needed, and loads all the existing alias files. Note that this does not export the functions, you need to use alias:export from your rc.elv for that.

fn init {
  if (not ?(test -d $dir)) {
    mkdir -p $dir
  }

  for file [(set _ = ?(put $dir/*.elv))] {
    var content = (cat $file | slurp)
    if (re:match '^#alias:new ' $content) {
      var name cmd = (re:find '^#alias:new (\S+)\s+(.*)\n' $content)[groups][1 2][text]
      def $name (edit:wordify $cmd)
    }
  }
}

init