[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
pushd/popd/dirs for es
I wrote a version of pushd/popd that uses no external programs at all
except to call an external `pwd' once when it is loaded. Afterward, `pwd'
is a shell function that merely prints $dirstack(1). Canonicalization of
the pwd is done internally. It's very fast.
I think this implementation is consistent with csh behavior.
I have not said anything to the mailing list about it before before, but I
mentioned to Paul Haahr that I'm in the process of writing a library of
useful stuff for es that I intend to announce when finished. This is one
of the things in it.
#
# dirs.es, 26-Apr-93 Noah Friedman <friedman@prep.ai.mit.edu>
# last modified 28-Apr-93
#
# Directory tracking stuff and directory stacks.
#
# Public domain.
#
# Because shared lexically scoped environments become separate across shell
# invocations (at least as of 0.83), we have just one function that
# performs all operations on the dirstack variable. Other routines merely
# access it indirectly.
# Attempt to preserve old value of dirstack if this file is reloaded.
let (dirstack = `{ if { !~ $#fn-dirs 0 } dirs pwd } )
{
fn dirs:access-dirstack \
{
# Ops basically listed in order of most likely frequency.
# `set1' and `set' are currently unused, but provided in case I need
# to repair damage manually.
if { ~ $1 pwd } \
{ echo $dirstack(1) } \
{ ~ $1 chdir } \
{ dirstack = <={ expand-file-name $2 $dirstack(1) } $dirstack(2 ...) } \
{ ~ $1 dirs } \
{ echo $dirstack } \
{ ~ $1 push } \
{ dirstack = <={ expand-file-name $2 $dirstack(1) } $dirstack } \
{ ~ $1 swap } \
{
~ $#dirstack 1 && throw error $0 pushd: No other directory
# This shouldn't be here, but it's faster & convenient.
local (cdpath = '') { $&cd $dirstack(2) }
dirstack = $dirstack(2) $dirstack(1) $dirstack(3 ...)
} \
{ ~ $1 pop } \
{
# Do not actually want to pop last element off dirstack.
~ $#dirstack 1 && throw error $0 popd: Directory stack empty
dirstack = $dirstack(2 ...)
} \
{ ~ $1 set1 } \
{ dirstack = $2 $dirstack(2 ...) } \
{ ~ $1 set } \
{ dirstack = $*(2 ...) } \
{ throw error $0 $0: $1: invalid op. }
}
}
fn chdir \
{
~ $#* 0 && * = $home
if { ~ $1 ./* ../* /* } \
{
local (cdpath = '') { $&cd $1 }
dirs:access-dirstack chdir $1
} \
{
let (dir =)
{
dir = <={ access -1 -d -n $1 $cdpath } ;
~ $#dir 0 && throw error $0 $0: $*(1) not in '$cdpath'
local (cdpath = '') { $&cd $dir }
dirs:access-dirstack chdir $dir
}
}
}
fn popd \
{
~ $#dirstack 1 && throw error $0 $0: Directory stack empty
dirs:access-dirstack pop
local (cdpath = '') { $&cd `pwd }
}
fn pushd \
{
if { ~ $#* 0 } \
{ dirs:access-dirstack swap } \
{ ~ $1 ./* ../* /* } \
{
local (cdpath = '') { $&cd $1 }
dirs:access-dirstack push $1
} \
{
let (dir =)
{
dir = <={ access -1 -d -n $1 $cdpath } ;
~ $#dir 0 && throw error $0 $0: $*(1) not in '$cdpath'
local (cdpath = '') { $&cd $dir }
dirs:access-dirstack push $1
}
}
}
#:docstring expand-file-name:
# Usage: expand-file-name filename {cwd}
#
# Convert filename to absolute (canonicalize it), and return result.
#
# Filenames containing . or .. as components are simplified.
# Second arg is directory to be considered the "current working directory"
# for the purposes of resolving "./foo" or "foo" if not the default cwd.
#:end docstring:
fn expand-file-name \
{
if { !~ $1 /* } \
{
if { ~ $2 () } \
{ * = `pwd^/$1 } \
{ * = $2^/^$1 }
}
let (list = <={ %split '/' $1 }; nlist =; dotdot =)
{
# Reverse list.
for (elt = $list) { nlist = $elt $nlist }
list = $nlist
nlist =
for (elt = $list)
if { ~ $elt '.' } \
{ true } \
{ ~ $elt '..' } \
{ dotdot = t $dotdot } \
{ ~ $#dotdot 0 } \
{ nlist = $elt $nlist } \
{ dotdot = $dotdot(2 ...) }
~ $#nlist 0 && nlist = ''
%flatten '/' '' $nlist
}
}
# Aliases
fn-[d = pushd
fn-]d = popd
fn-cd = chdir
fn-dirs = dirs:access-dirstack dirs
fn-pwd = dirs:access-dirstack pwd
# eof