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.
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
use re
use str
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 = '{}'
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
}
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 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
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