[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