summaryrefslogtreecommitdiff
path: root/Misc
diff options
context:
space:
mode:
authorTanaka Akira <akr@users.sourceforge.net>1999-04-15 18:05:38 +0000
committerTanaka Akira <akr@users.sourceforge.net>1999-04-15 18:05:38 +0000
commite74702b467171dbdafb56dfe354794a212e020d9 (patch)
treec295b3e9b2e93e2de10331877442615b0f37e779 /Misc
parentc175751b501a3a4cb40ad4787340a597ea769be4 (diff)
downloadzsh-e74702b467171dbdafb56dfe354794a212e020d9.tar.gz
zsh-e74702b467171dbdafb56dfe354794a212e020d9.zip
Initial revision
Diffstat (limited to 'Misc')
-rw-r--r--Misc/new-completion-examples453
-rw-r--r--Misc/zftp-functions1281
2 files changed, 1734 insertions, 0 deletions
diff --git a/Misc/new-completion-examples b/Misc/new-completion-examples
new file mode 100644
index 000000000..659679891
--- /dev/null
+++ b/Misc/new-completion-examples
@@ -0,0 +1,453 @@
+# Define a new widget behaving like `expand-or-complete' but calling the
+# function `main-complete' to generate matches.
+
+zle -c my-comp expand-or-complete main-complete
+
+bindkey '\C-i' my-comp
+
+
+# Below is a proposed main loop and the things it needs.
+
+# One associative array for normal completions and one for patterns.
+
+typeset -A comps
+
+
+# These may be used to define completion handlers. First argument is the
+# name of the function/variable containing the definition, the other
+# arguments are the command names for which this definition should be used.
+# With only one argument the function/variable-name __$1 is used.
+
+defcomp() {
+ local v
+
+ if [[ $# -eq 1 ]] then
+ comps[$1]="__$1"
+ else
+ v="$1"
+ shift
+ for i; do
+ comps[$i]="$v"
+ done
+ fi
+}
+
+defpatcomp() {
+ if [[ ${+patcomps} == 1 ]] then
+ patcomps=("$patcomps[@]" "$2 $1" )
+ else
+ patcomps=( "$2 $1" )
+ fi
+}
+
+
+# These can be used to easily save and restore the state of the special
+# variables used by the completion code.
+
+alias compsave='local _opre _oipre _oargs _ocur;_opre="$PREFIX";_oipre="$IPREFIX";_oargs=( "$@" );_ocur="$CURRENT"'
+alias compreset='PREFIX="$_opre";IPREFIX="$_oipre";argv=( "$_oargs[@]" );CURRENT="$_ocur"'
+
+# This is an easy way to get completion for sub-commands.
+
+alias compsub='do-complete "$@" || return 1'
+
+# This searches $1 in the array for normal completions and calls the result.
+
+compalso() {
+ 1="$comps[$1]"
+ [[ -z "$1" ]] || call-complete "$@"
+}
+
+# This generates matches. The first argument is something we got from one
+# of the associative arrays above. This is expected to be either the name
+# of a variable in which case we use its value as arguments to complist,
+# or it is the name of a function in which case we call this function with
+# the arguments from the command line as its arguments.
+
+call-complete() {
+ local var
+
+ eval var\=\$\{\+$1\}
+ if [[ "$var" == 0 ]] then
+ "$@"
+ else
+ eval complist \$\{${1}\[\@\]\}
+ fi
+}
+
+# The main loop of the competion code. This is what is called when TAB is
+# pressed. The completion code gives us the special variables and the
+# arguments from the command line are gives as positional parameters.
+
+main-complete() {
+ emulate -R zsh
+ local comp
+ setopt localoptions nullglob rcexpandparam globdots
+ unsetopt markdirs globsubst shwordsplit nounset
+
+ # An entry for `--first--' is the replacement for `compctl -T'
+ # The `|| return 1' is used throughout: if a function producing matches
+ # returns non-zero this is interpreted as `do not try to produce more matches'
+ # (this is the replacement for `compctl -t').
+
+ comp="$comps[--first--]"
+ [[ -z "$comp" ]] || call-complete "$comp" "$@" || return 1
+
+ # For arguments we use the `do-complete' function below called via the
+ # convenience alias `compsub'.
+
+ if [[ $CONTEXT == argument || $CONTEXT == command ]] then
+ compsub
+ else
+ # Let's see if we have a special completion definition for the other
+ # possible contexts.
+
+ comp=''
+
+ case $CONTEXT in
+ redirect) comp="$comps[--redir--]";;
+ math) comp="$comps[--math--]";;
+ subscript) comp="$comps[--subscr--]";;
+ value) comp="$comps[--value--]";;
+ condition) comp="$comps[--cond--]";;
+ esac
+
+ # If not, we use default completion, if any.
+
+ [[ -z "$comp" ]] && comp="$comps[--default--]"
+ [[ -z "$comp" ]] || call-complete "$comp" "$@" || return 1
+ fi
+}
+
+# This does completion for a command (in command position and for the
+# arguments).
+
+do-complete() {
+ local comp cmd1 cmd2 pat val
+
+ # Completing in command position? If not we set up `cmd1' and `cmd2' as
+ # two strings we have search in the completion definition arrays (e.g.
+ # a path and the last path name component).
+
+ if [[ $CONTEXT == command ]] then
+ comp="$comps[--command--]"
+ [[ -z "$comp" ]] || call-complete "$comp" "$@" || return 1
+ return 0
+ elif [[ "$COMMAND[1]" == '=' ]] then
+ eval cmd1\=$COMMAND
+ cmd2="$COMMAND[2,-1]"
+ elif [[ "$COMMAND" == */* ]] then
+ cmd1="$COMMAND"
+ cmd2="${COMMAND:t}"
+ else
+ cmd1="$COMMAND"
+ eval cmd2=$(whence -p $COMMAND)
+ fi
+
+ # See if there are any matching pattern completions.
+
+ for i in "$patcomps[@]"; do
+ pat="${i% *}"
+ val="${i#* }"
+ if [[ "$cmd1" == $~pat || "$cmd2" == $~pat ]] then
+ call-complete "$val" "$@" || return 1
+ fi
+ done
+
+ # Now look up the two names in the normal completion array.
+
+ comp="${comps[$cmd1]:-$comps[$cmd2]}"
+
+ # And generate the matches, probably using default completion.
+
+ [[ -z "$comp" ]] && comp="$comps[--default--]"
+ [[ -z "$comp" ]] || call-complete "$comp" "$@" || return 1
+}
+
+# Do sub-completion for pre-command modifiers.
+
+defcomp __precmd - noglob nocorrect exec command builtin
+__precmd() {
+ COMMAND="$1"
+ shift
+ (( CURRENT-- ))
+ if [[ CURRENT -eq 0 ]] then
+ CONTEXT=command
+ else
+ CONTEXT=argument
+ fi
+ compsub
+}
+
+# Utility function for in-path completion.
+# First argument should be an complist-option (e.g. -f, -/, -g). The other
+# arguments should be glob patterns, one per argument.
+# E.g.: files -g '*.tex' '*.texi'
+# This is intended as a replacement for `complist -f', `complist -/', and
+# `complist -g ...' (but don't use it with other options).
+# This function behaves as if you have a matcher definition like:
+# compctl -M 'r:|[-.,_/]=* r:|=* m:{a-z}={A-Z} m:-=_ m:.=,'
+# so you may want to modify this.
+
+pfiles() {
+ local nm str pa pre epre a b c s rest
+
+ setopt localoptions nullglob rcexpandparam globdots extendedglob
+ unsetopt markdirs globsubst shwordsplit nounset
+
+ nm=$NMATCHES
+ if [[ $# -eq 0 ]] then
+ complist -f
+ elif [[ "$1" = -g ]] then
+ complist -g "$argv[2,-1]"
+ shift
+ else
+ complist $1
+ shift
+ fi
+ [[ -nmatches nm ]] || return
+
+ str="$PREFIX*$SUFFIX"
+
+ [[ -z "$1" ]] && 1='*'
+ if [[ $str[1] = \~ ]] then
+ pre="${str%%/*}/"
+ eval epre\=$pre
+ str="${str#*/}"
+ pa=''
+ else
+ pre=''
+ epre=''
+ if [[ $str[1] = / ]] then
+ str="$str[2,-1]"
+ pa='/'
+ else
+ pa=''
+ fi
+ fi
+ str="$str:gs/,/*,/:gs/_/*_/:gs./.*/.:gs/-/*[-_]/:gs/./*[.,]/:gs-*[.,]*[.,]*/-../-:gs.**.*."
+ while [[ "$str" = */* ]] do
+ rest="${str#*/}"
+ a="${epre}${pa}(#l)${str%%/*}(-/)"
+ a=( $~a )
+ if [[ $#a -eq 0 ]] then
+ return
+ elif [[ $#a -gt 1 ]] then
+ c=()
+ s=( $rest$@ )
+ s=( "${(@)s:gs.**.*.}" )
+ for i in $a; do
+ b=( $~i/(#l)$~s )
+ eval b\=\( \$\{b:/\*\(${(j:|:)fignore}\)\} \)
+ [[ $#b -ne 0 ]] && c=( $c $i )
+ done
+ if [[ $#c -eq 0 ]] then
+ return
+ elif [[ $#c -ne 1 ]] then
+ a="$epre$pa"
+ c=( $~c/(#l)$~s )
+ eval c\=\( \$\{c:/\*\(${(j:|:)fignore}\)\} \)
+ c=( ${c#$a} )
+ for i in $c; do
+ compadd -p "$pre$pa" -W "$a" -s "/${i#*/}" -f "${i%%/*}"
+ done
+ return
+ fi
+ a=( "$c[1]" )
+ fi
+ a="$a[1]"
+ pa="$pa${a##*/}/"
+ str="$rest"
+ done
+ a="$epre$pa"
+ s=( $str$@ )
+ s=( "${(@)s:gs.**.*.}" )
+ b=( $~a(#l)$~s )
+ eval b\=\( \$\{b:/\*\(${(j:|:)fignore}\)\} \)
+ compadd -p "$pre$pa" -W "$epre$pa" -f ${b#$a}
+}
+
+# Utility function for completing files of a given type or any file.
+# In many cases you will want to call this one instead of pfiles().
+
+files() {
+ local nm
+
+ nm=$NMATCHES
+ pfiles "$@"
+
+ [[ $# -ne 0 && -nmatches nm ]] && pfiles
+}
+
+# Simple default, command, and math completion defined with variables.
+
+defcomp __default --default--
+__default() {
+ files
+}
+
+defcomp __command --command--
+__command=( -c )
+
+defcomp __math --math--
+__math=( -v )
+
+defcomp __subscr --subscr--
+__subscr() {
+ compalso --math-- "$@"
+ # ...probably other stuff
+}
+
+# A simple pattern completion, just as an example.
+
+defpatcomp __x_options '*/X11/*'
+__x_options() {
+ complist -J options -k '(-display -name -xrm)'
+}
+
+# A better example: completion for `find'.
+
+defcomp find
+__find() {
+ compsave
+
+ if [[ -mbetween -(ok|exec) \\\; ]] then
+ compsub
+ elif [[ -iprefix - ]] then
+ complist -s 'daystart {max,min,}depth follow noleaf version xdev \
+ {a,c,}newer {a,c,m}{min,time} empty false {fs,x,}type gid inum links \
+ {i,}{l,}name {no,}{user,group} path perm regex size true uid used \
+ exec {f,}print{f,0,} ok prune ls'
+ compreset
+ elif [[ -position 1 ]] then
+ complist -g '. ..'
+ files -g '(-/)'
+ elif [[ -mcurrent -1 -((a|c|)newer|fprint(|0|f)) ]] then
+ files
+ elif [[ -current -1 -fstype ]] then
+ complist -k '(ufs 4.2 4.3 nfs tmp mfs S51K S52K)'
+ elif [[ -current -1 -group ]] then
+ complist -k groups
+ elif [[ -current -1 -user ]] then
+ complist -u
+ fi
+}
+
+# Various completions...
+
+defcomp __gunzip gunzip zcat
+__gunzip() {
+ files -g '*.[gG][z]'
+}
+
+defcomp gzip
+__gzip() {
+ files -g '*~*.[gG][zZ]'
+}
+
+defcomp xfig
+__xfig() {
+ files -g '*.fig'
+}
+
+defcomp __make make gmake
+__make() {
+ complist -s "\$(awk '/^[a-zA-Z0-9][^/ ]+:/ {print \$1}' FS=: [mM]akefile)"
+}
+
+defcomp __ps gs ghostview gview psnup psselect pswrap pstops pstruct lpr
+__ps() {
+ files -g '*([pP][sS]|eps)'
+}
+
+defcomp __which which whence
+__which=( -caF )
+
+defcomp __rlogin rlogin rsh ssh
+__rlogin() {
+ if [[ -position 1 ]] then
+ complist -k hosts
+ elif [[ -position 2 ]] then
+ complist -k '(-l)'
+ elif [[ -position 3 && -word 1 artus ]] then
+ complist -k '(puck root)'
+ fi
+}
+
+defcomp __dvi xdvi dvips dvibook dviconcat dvicopy dvidvi dviselect dvitodvi dvitype
+__dvi() {
+ files -g '*.(dvi|DVI)'
+}
+
+defcomp __dirs rmdir df du dircmp cd
+__dirs() {
+ files -/ '*(-/)'
+}
+
+defcomp __jobs fg bg jobs
+__jobs=(-j -P '%?')
+
+defcomp kill
+__kill() {
+ if [[ -iprefix '-' ]] then
+ complist -k signals
+ else
+ complist -P '%?' -j
+ fi
+}
+
+defcomp __uncompress uncompress zmore
+__uncompress() {
+ files -g '*.Z'
+}
+
+defcomp compress
+__compress() {
+ files -g '*~*.Z'
+}
+
+defcomp __tex tex latex glatex slitex gslitex
+__tex() {
+ files -g '*.(tex|TEX|texinfo|texi)'
+}
+
+defcomp __options setopt unsetopt
+__options=(-M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' -o)
+
+defcomp __funcs unfunction
+__funcs=(-F)
+
+defcomp __aliases unalias
+__aliases=(-a)
+
+defcomp __vars unset
+__vars=(-v)
+
+defcomp __enabled disable
+__enabled=(-FBwa)
+
+defcomp __disabled enable
+__disabled=(-dFBwa)
+
+defcomp __pdf acroread
+__pdf() {
+ files -g '*.(pdf|PDF)'
+}
+
+defcomp tar
+__tar() {
+ local nm tf
+ compsave
+
+ tf="$2"
+ nm=$NMATCHES
+ if [[ ( -mword 1 *t*f* || -mword 1 *x*f* ) && -position 3 100000 ]] then
+ complist -k "( $(tar tf $tf) )"
+ compreset
+ elif [[ -mword 1 *c*f* && -position 3 100000 ]] then
+ files
+ compreset
+ elif [[ -mcurrent -1 *f* && -position 2 ]] then
+ files -g '*.(tar|TAR)'
+ fi
+}
diff --git a/Misc/zftp-functions b/Misc/zftp-functions
new file mode 100644
index 000000000..a07e46d72
--- /dev/null
+++ b/Misc/zftp-functions
@@ -0,0 +1,1281 @@
+# zftp is a loadable module implementing an FTP client as a builtin
+# command so that you can use the shell command language and line
+# editing to make life easier. If your system has dynamically
+# load libraries and zsh was compiled to use them, it is probably
+# somewhere where it can be loaded at run time. Otherwise, it depends
+# whether the shell was compiled with zftp already built into it.
+#
+# Here is a suite of functions, plus assorted other code, to make
+# zftp work smoothly.
+#
+# Completion is implemented in a fairly natural way, except that
+# very little support has been provided for non-UNIX remote hosts.
+# On such machines, the safest thing to do is only try to complete
+# files in the current directory; this should be OK.
+#
+# Remote globbing for commands which retrieve files is also
+# implemented. This can be done in two different ways. The default
+# is for zsh to do the globbing locally. The advantage is that full
+# zsh pattern matching (respecting the setting of extendedglob) is
+# possible, and no assumption (apart from the restrictions on
+# directory handling noted above) is made about the behaviour of the
+# server. The disadvantage is that the entire filename list for the
+# current directory must be retrieved, and then zsh must laboriously
+# do pattern matching against every file, so it is potentially slow
+# for large directories. Only the non-directory part of file names is
+# globbed.
+#
+# The alternative will be used if $zfrglob has non-zero length.
+# Zsh then sends the pattern to the server for globbing. Best of
+# luck.
+#
+# To support remote globbing, some functions have been aliased
+# with 'noglob' in front. Currently, this has a dire effect on
+# completion unless the completeinaliases option is set, so
+# it is set below. This can conceivably cause you problems
+# if you expect completion for aliases automatically to give you
+# completion for the base command. I suspect that most people
+# don't even know that happens.
+#
+# The following functions are provided.
+#
+# General status changing and displaying functions:
+# zfparams
+# Simple front end to `zftp params', except it will automatically
+# query host, user and password. These are then stored to be
+# used with a `zfopen' with no arguments.
+# zfopen [ host [ user ... ] ]
+# Open a connection and login. Unless the option -1 (once)
+# is given, will store the parameters for the open (including
+# a password which is prompted for and not echoed) so that
+# if you call zfopen subsequently without arguments it will
+# reopen the same connection.
+# zfanon anonftphost
+# Open a connection for anonymous FTP. Tries to guess an
+# email address to use as the password, unless $EMAIL_ADDR is
+# already set. The first time, will tell you what it has guessed.
+# It's rude to set EMAIL_ADDR=mozilla.
+# zfcd [ dir | old new ]
+# Change directory on the server. This tries to mimic the behaviour
+# of the shell's cd. In particular,
+# zfcd change to '~' on server, if it interprets it
+# zfcd - change to previous directory of current connection
+# zfcd OLD NEW change directory from fooOLDbar to fooNEWbar
+# One piece of magic is builtin: an initial part of the directory
+# matching $HOME is translated back to `~'. Most UNIX servers
+# recognise the usual shell convention. So things like `zfcd $PWD'
+# is useful provide you are under your home directory and the
+# structure on the remote machine mirrors that on the local.
+# zfhere
+# Synonym for `zfcd $PWD', see above.
+# zfdir [args]
+# Show a long diretory list of the remote connection. Any
+# arguments are passed on to the server, apart from options.
+# Currently this always uses a pager to show the directory
+# list. Caching is implemented: zfdir on its own always shows
+# the current diretory, which is cached; zfdir with some other
+# directory arguments shows that, which is cached separately
+# and can be reviewed with `zfdir -r'. Other options:
+# -f force reget, overriding the cache, in case something's changed
+# -d delete the cache, but don't show anything.
+# To pass options to the server, use e.g. `zfdir -- -C'.
+# This also has the zfcd ~ hack.
+# zfls [args]
+# Short list of the long directory, depending on what [args]
+# do to the server. No options, no caching, no pager.
+# zftype [ a[scii] | i[mage] | b[inary] ]
+# Set or display the transfer type; currently only ASCII
+# and image (same as binary) types are supported.
+# zfclose
+# Close the connection.
+# zfstat
+# Print the zftp status from local variables; doesn't do any network
+# operations unless -v is supplied, in which case the server is
+# asked for its views on the status, too.
+#
+# Functions for retrieving data:
+# All accept the following options:
+# -G Don't do remote globbing (see above); the default is to do it.
+# -t Try to set local files to the same time as the remote ones.
+# Unfortunately we only know the remote time in GMT, so it's
+# a little tricky and you need perl 5 (installed as `perl')
+# for this to work. Suggestions welcome.
+# zfget file1 file2 ...
+# Retrieve each file from the server. The remote file is the
+# full name given, the local file is the non-directory part of that
+# (assuming UNIX file paths).
+# zfuget file1 file2 ..
+# Get with update. Check remote and local sizes and times and
+# retrieve files which are newer on the server. Will query
+# hard cases, which are where the remote file is newer but a
+# different size, or is older but the same size. With option -s
+# (silent) assumes it's best to retrieve the files in both those
+# cases. With -v (may be combined with -s), print the information
+# about the files being considered.
+# zfcget file1 ...
+# Assuming file1 was incompletely retrieved, try to get the rest of
+# it. This relies on a normal UNIX server behaviour which is not
+# as specified in the FTP standard and hence is not universal.
+# zfgcp file1 file2
+# zfgcp file1 file2 ... dir
+# Get with the behaviour of cp, i.e. copy remote file1 to local
+# file2, or get remote fileN into local diretory dir.
+#
+# Function for sending data:
+# zfput file1 file2 ...
+# Put the local files onto the server under the same name. The
+# local files are exactly as given; the remote files are the
+# non-diretory parts of that.
+# zfuput file1 file2 ..
+# Put the local files onto the server, with update. Works
+# similarly to zfuget.
+#
+# Utility functions:
+# zftp_chpwd
+# Show the new directory when it changes; try to put it into
+# an xterm on shelltool header. Works best alongside chpwd.
+# zftp_progress
+# Show the percentage of a file retrieved as it is coming; if the
+# size is not available show the size transferred so far. The
+# percentage may be wrong if sending data from a local pipe.
+# If you transfer files in the background, you should undefine
+# this before the transfer. It is smart enough not to print
+# anything when stderr is not a terminal.
+# zfcd_match
+# Function for remote directory completion.
+# zfget_match
+# Function for remote filename completion.
+# zfrglob varname
+# This is used for the remote globbing. The pattern resides
+# in $varname (note extra level of indirection), and on return
+# $varname will contain the list of matching files.
+# zfrtime locfile remfile [ time ]
+# This sad thing does the setting of local file times to those
+# of the remote, see horror story above.
+
+zmodload -ia zftp
+
+alias zfcd='noglob zfcd'
+alias zfget='noglob zfget'
+alias zfls='noglob zfls'
+alias zfdir='noglob zfdir'
+alias zfuget='noglob zfuget'
+# only way of getting that noglob out of the way at the moment
+setopt completealiases
+
+#
+# zftp completions
+#
+compctl -f -x 'p[1]' \
+ -k '(open params user login type ascii binary mode put putat
+ get getat append appendat ls dir local remote mkdir rmdir delete
+ close quit)' - \
+ 'w[1,cd][1,ls][1,dir][1,rmdir]' -K zfcd_match -S/ -q - \
+ 'W[1,get*]' -K zfget_match - 'w[1,delete][1,remote]' -K zfget_match - \
+ 'w[1,open][1,params]' -k hosts -- zftp
+compctl -K zfcd_match -S/ -q zfcd zfdir zfls
+compctl -K zfget_match zfget zfgcp zfuget zfcget
+compctl -k hosts zfopen zfparams
+
+function zfanon {
+ local opt optlist once
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ 1) once=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ if [[ -z $EMAIL_ADDR ]]; then
+ # Exercise in futility. There's a poem by Wallace Stevens
+ # called something like `N ways of looking at a blackbird',
+ # where N is somewhere around 0x14 to 0x18. Now zftp is
+ # ashamed to prsent `N ways of looking at a hostname'.
+ local domain host
+ # First, maybe we've already got it. Zen-like.
+ if [[ $HOST = *.* ]]; then
+ # assume this is the full host name
+ host=$HOST
+ elif [[ -f /etc/resolv.conf ]]; then
+ # Next, maybe we've got resolv.conf.
+ domain=$(awk '/domain/ { print $2 }' /etc/resolv.conf)
+ [[ -n $domain ]] && host=$HOST.$domain
+ fi
+ # Next, maybe we've got nlsookup. May not work on LINUX.
+ [[ -z $host ]] && host=$(nslookup $HOST | awk '/Name:/ { print $2 }')
+ if [[ -z $host ]]; then
+ # we're running out of ideas, but this should work.
+ # after all, i wrote it...
+ # don't want user to know about this, too embarrassed.
+ local oldvb=$ZFTP_VERBOSE oldtm=$ZFTP_TMOUT
+ ZFTP_VERBOSE=
+ ZFTP_TMOUT=5
+ if zftp open $host >& /dev/null; then
+ host=$ZFTP_HOST
+ zftp close $host
+ fi
+ ZFTP_VERBOSE=$oldvb
+ ZFTP_TMOUT=$oldtm
+ fi
+ if [[ -z $host ]]; then
+ print "Can't get your hostname. Define \$EMAIL_ADDR by hand."
+ return 1;
+ fi
+ EMAIL_ADDR="$USER@$host"
+ print "Using $EMAIL_ADDR as anonymous FTP password."
+ fi
+
+ if [[ $once = 1 ]]; then
+ zftp open $1 anonymous $EMAIL_ADDR
+ else
+ zftp params $1 anonymous $EMAIL_ADDR
+ zftp open
+ fi
+}
+
+function zfcd {
+ # zfcd: change directory on the remote server.
+ #
+ # Currently has the following features:
+ # --- an initial string matching $HOME in the directory is turned back into ~
+ # to be re-interpreted by the remote server.
+ # --- zfcd with no arguments changes directory to '~'
+ # --- `zfcd old new' and `zfcd -' work analagously to cd
+ # --- if the connection is not currently open, it will try to
+ # re-open it with the stored parameters as set by zfopen.
+ # If the connection timed out, however, it won't know until
+ # too late. In that case, just try the same zfcd command again
+ # (but now `zfcd -' and `zfcd old new' won't work).
+
+ # hack: if directory begins with $HOME, turn it back into ~
+ # there are two reasons for this:
+ # first, a ~ on the command line gets expanded even with noglob.
+ # (I suppose this is correct, but I wouldn't like to swear to it.)
+ # second, we can no do 'zfcd $PWD' and the like, and that will
+ # work just as long as the directory structures under the home match.
+
+ # Autoopen: if not already open, hope there are parameters set up to
+ # do so. If not, we get the right error message, so no harm done.
+ [[ -z $ZFTP_HOST ]] && { zfopen || return 1; }
+
+ if [[ $1 = $HOME || $1 = $HOME/* ]]; then
+ 1="~${1#$HOME}"
+ fi
+
+ if (( $# == 0 )); then
+ # Emulate `cd' behaviour
+ set -- '~'
+ elif [[ $# -eq 1 && $1 = - ]]; then
+ # Emulate `cd -' behaviour.
+ set -- $zflastdir
+ elif [[ $# -eq 2 ]]; then
+ # Emulate `cd old new' behaviour.
+ # We have to find a character not in $1 or $2; ! is a good bet.
+ eval set -- "\${ZFTP_PWD:s!$1!$2!}"
+ fi
+
+ # We have to remember the current directory before changing it
+ # if we want to keep it.
+ local lastdir=$ZFTP_PWD
+
+ zftp cd "$@" && zflastdir=$lastdir
+}
+
+function zfcd_match {
+ # see zfcd for details of this hack
+ if [[ $1 = $HOME || $1 = $HOME/* ]]; then
+ 1="~${1#$HOME}"
+ fi
+
+ # error messages only
+ local ZFTP_VERBOSE=45
+ # should we redirect 2>/dev/null or let the user see it?
+
+ if [[ $ZFTP_SYSTEM = UNIX* ]]; then
+ # hoo, aren't we lucky: this makes things so much easier
+ setopt localoptions rcexpandparam
+ local dir
+ if [[ $1 = ?*/* ]]; then
+ dir=${1%/*}
+ elif [[ $1 = /* ]]; then
+ dir=/
+ fi
+ # If we're using -F, we get away with using a directory
+ # to list, but not a glob. Don't ask me why.
+ # I hate having to rely on awk here.
+ reply=($(zftp ls -F $dir |
+ awk '/\/$/ { print substr($1, 0, length($1)-1) }'))
+ if [[ $dir = / ]]; then
+ reply=(${dir}$reply)
+ elif [[ -n $dir ]]; then
+ reply=($dir/$reply)
+ fi
+ else
+ # I simply don't know what to do here.
+ # Just use the list of files for the current directory.
+ zfget_match $*
+ fi
+
+}
+
+function zfcget {
+ # Continuation get of files from remote server.
+ # For each file, if it's shorter here, try to get the remainder from
+ # over there. This requires the server to support the REST command
+ # in the way many do but RFC959 doesn't specify.
+ # Options:
+ # -G don't to remote globbing, else do
+ # -t update the local file times to the same time as the remote.
+ # Currently this only works if you have the `perl' command,
+ # and that perl is version 5 with the standard library.
+ # See the function zfrtime for more gory details.
+
+ setopt localoptions
+ unsetopt ksharrays shwordsplit
+
+ local loc rem stat=0 optlist opt nglob remlist locst remst
+ local tmpfile=${TMPPREFIX}zfcget$$ rstat tsize time
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ G) nglob=1
+ ;;
+ t) time=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ for remlist in $*; do
+ # zfcd directory hack to put the front back to ~
+ if [[ $remlist = $HOME || $remlist = $HOME/* ]]; then
+ remlist="~${remlist#$HOME}"
+ fi
+ if [[ $nglob != 1 ]]; then
+ zfrglob remlist
+ fi
+ if (( $#remlist )); then
+ for rem in $remlist; do
+ loc=${rem:t}
+ if [[ ! -f $loc ]]; then
+ # File does not yet exist
+ zftp get $rem >$loc || stat=$?
+ else
+ # Compare the sizes.
+ locst=($(zftp local $loc))
+ zftp remote $rem >$tmpfile
+ rstat=$?
+ remst=($(<$tmpfile))
+ rm -f $tmpfile
+ if [[ $rstat = 2 ]]; then
+ print "Server does not support SIZE command.\n" \
+ "Assuming you know what you're doing..." 2>&1
+ zftp getat $rem $locst[1] >>$loc || stat=$?
+ continue
+ elif [[ $rstat = 1 ]]; then
+ print "Remote file not found: $rem" 2>&1
+ continue
+ fi
+ if [[ $locst[1] -gt $remst[1] ]]; then
+ print "Local file is larger!" 2>&1
+ continue;
+ elif [[ $locst[1] == $remst[1] ]]; then
+ print "Files are already the same size." 2>&1
+ continue
+ else
+ if zftp getat $rem $locst[1] >>$loc; then
+ [[ $time = 1 ]] && zfrtime $loc $rem $remst[2]
+ else
+ stat=1
+ fi
+ fi
+ fi
+ done
+ fi
+ done
+
+ return $stat
+}
+
+function zfclose {
+ zftp close
+}
+
+function zfdir {
+ # Long directory of remote server.
+ # The remote directory is cached. In fact, two caches are kept:
+ # one of the standard listing of the current directory, i.e. zfdir
+ # with no arguments, and another for everything else.
+ # To access the appropriate cache, just use zfdir with the same
+ # arguments as previously. zfdir -r will also re-use the `everything
+ # else' cache; you can always reuse the current directory cache just
+ # with zfdir on its own.
+ #
+ # The current directory cache is emptied when the directory changes;
+ # the other is kept until a new zfdir with a non-empty argument list.
+ # Both are removed when the connection is closed.
+ #
+ # zfdir -f will force the existing cache to be ignored, e.g. if you know
+ # or suspect the directory has changed.
+ # zfdir -d will remove both caches without listing anything.
+ # If you need to pass -r, -f or -d to the dir itself, use zfdir -- -d etc.
+
+ setopt localoptions unset
+ unsetopt shwordsplit ksharrays
+
+ local file opt optlist redir i newargs force
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ r) redir=1
+ ;;
+ f) force=1
+ ;;
+ d) [[ -n $zfcurdir && -f $zfcurdir ]] && rm -f $zfcurdir
+ [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir
+ zftp_fcache=()
+ return 0
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ # directory hack, see zfcd
+ for (( i = 1; i <= $#argv; i++ )); do
+ if [[ $argv[$i] = $HOME || $argv[$i] = $HOME/* ]]; then
+ argv[$i]="~${argv[$i]#$HOME}"
+ fi
+ done
+
+ if [[ $# -eq 0 ]]; then
+ # Cache it in the current directory file. This means that repeated
+ # calls to zfdir with no arguments always use a cached file.
+ [[ -z $zfcurdir ]] && zfcurdir=${TMPPREFIX}zfcurdir$$
+ file=$zfcurdir
+ else
+ # Last directly looked at was not the current one, or at least
+ # had non-standard arguments.
+ [[ -z $zfotherdir ]] && zfotherdir=${TMPPREFIX}zfotherdir$$
+ file=$zfotherdir
+ newargs="$*"
+ if [[ -f $file && $redir != 1 && $force -ne 1 ]]; then
+ # Don't use the cached file if the arguments changed.
+ [[ $newargs = $zfotherargs ]] || rm -f $file
+ fi
+ zfotherargs=$newargs
+ fi
+
+ if [[ $force -eq 1 ]]; then
+ rm -f $file
+ # if it looks like current directory has changed, better invalidate
+ # the filename cache, too.
+ (( $# == 0 )) && zftp_fcache=()
+ fi
+
+ if [[ -n $file && -f $file ]]; then
+ eval ${PAGER:-more} \$file
+ else
+ zftp dir $* | tee $file | eval ${PAGER-:more}
+ fi
+}
+
+function zfgcp {
+ # ZFTP get as copy: i.e. first arguments are remote, last is local.
+ # Supposed to work exactly like a normal copy otherwise, i.e.
+ # zfcp rfile lfile
+ # or
+ # zfcp rfile1 rfile2 rfile3 ... ldir
+ # Options:
+ # -G don't to remote globbing, else do
+ # -t update the local file times to the same time as the remote.
+ # Currently this only works if you have the `perl' command,
+ # and that perl is version 5 with the standard library.
+ # See the function zfrtime for more gory details.
+
+ setopt localoptions
+ unsetopt shwordsplit
+
+ local opt optlist nglob remlist rem loc stat=0 time
+
+ while [[ $1 == -* ]]; do
+ if [[ $1 == - || $1 == -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $opt in
+ G) nglob=1
+ ;;
+ t) time=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ # hmm, we should really check this after expanding the glob,
+ # but we shouldn't expand the last argument remotely anyway.
+ if [[ $# -gt 2 && ! -d $argv[-1] ]]; then
+ print "zfgcp: last argument must be a directory." 2>&1
+ return 1
+ elif [[ $# == 1 ]]; then
+ print "zfgcp: not enough arguments." 2>&1
+ return 1
+ fi
+
+ if [[ -d $argv[-1] ]]; then
+ local dir=$argv[-1]
+ argv[-1]=
+ for remlist in $*; do
+ # zfcd directory hack to put the front back to ~
+ if [[ $remlist = $HOME || $remlist = $HOME/* ]]; then
+ remlist="~${remlist#$HOME}"
+ fi
+ if [[ $nglob != 1 ]]; then
+ zfrglob remlist
+ fi
+ if (( $#remlist )); then
+ for rem in $remlist; do
+ loc=$dir/${rem:t}
+ if zftp get $rem >$loc; then
+ [[ $time = 1 ]] && zfrtime $rem $loc
+ else
+ stat=1
+ fi
+ done
+ fi
+ done
+ else
+ zftp get $1 >$2 || stat=$?
+ fi
+ return $stat
+}
+
+function zfget {
+ # Get files from remote server. Options:
+ # -G don't to remote globbing, else do
+ # -t update the local file times to the same time as the remote.
+ # Currently this only works if you have the `perl' command,
+ # and that perl is version 5 with the standard library.
+ # See the function zfrtime for more gory details.
+
+ local loc rem stat=0 optlist opt nglob remlist time
+
+ while [[ $1 == -* ]]; do
+ if [[ $1 == - || $1 == -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $opt in
+ G) nglob=1
+ ;;
+ t) time=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ for remlist in $*; do
+ # zfcd directory hack to put the front back to ~
+ if [[ $remlist == $HOME || $remlist == $HOME/* ]]; then
+ remlist="~${remlist#$HOME}"
+ fi
+ if [[ $nglob != 1 ]]; then
+ zfrglob remlist
+ fi
+ if (( $#remlist )); then
+ for rem in $remlist; do
+ loc=${rem:t}
+ if zftp get $rem >$loc; then
+ [[ $time = 1 ]] && zfrtime $rem $loc
+ else
+ stat=1
+ fi
+ done
+ fi
+ done
+
+ return $stat
+}
+
+function zfget_match {
+ # the zfcd hack: this may not be necessary here
+ if [[ $1 == $HOME || $1 == $HOME/* ]]; then
+ 1="~${1#$HOME}"
+ fi
+
+
+ if [[ $ZFTP_SYSTEM == UNIX* && $1 == */* ]]; then
+ # On the first argument to ls, we usually get away with a glob.
+ reply=($(zftp ls "$1*$2"))
+ else
+ if (( $#zftp_fcache == 0 )); then
+ # Always cache the current directory and use it
+ # even if the system is UNIX.
+ zftp_fcache=($(zftp ls))
+ fi
+ reply=($zftp_fcache);
+ fi
+}
+
+function zfhere {
+ # Change to the directory corresponding to $PWD on the server.
+ # See zfcd for how this works.
+ zfcd $PWD
+}
+
+function zfls {
+ # directory hack, see zfcd
+ if [[ $1 = $HOME || $1 = $HOME/* ]]; then
+ 1="~${1#$HOME}"
+ fi
+ zftp ls $*
+}
+
+function zfopen {
+ # Use zftp params to set parameters for open, rather than sending
+ # them straight to open. That way they are stored for a future open
+ # command.
+ #
+ # With option -1 (just this 1ce), don't do that.
+
+ local optlist opt once
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ 1) once=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ # This is where we should try and do same name-lookupage in
+ # both .netrc and .ncftp/bookmarks . We could even try saving
+ # the info in their for new hosts, like ncftp does.
+
+ if [[ $once = 1 ]]; then
+ zftp open $*
+ else
+ # set parameters, but only if there was at least a host
+ (( $# > 0 )) && zfparams $*
+ # now call with no parameters
+ zftp open
+ fi
+}
+
+function zfparams {
+ # Set to prompt for any user or password if not given.
+ # Don't worry about accounts here.
+ if (( $# > 0 )); then
+ (( $# < 2 )) && 2='?'
+ (( $# < 3 )) && 3='?'
+ fi
+ zftp params $*
+}
+
+function zfput {
+ # Simple put: dump every file under the same name, but stripping
+ # off any directory parts.
+ local loc rem stat=0
+ for loc in $*; do
+ rem=${loc:t}
+ zftp put $rem <$loc
+ [[ $? == 0 ]] || stat=$?
+ done
+ return $stat
+}
+
+function zfrglob {
+ # Do the remote globbing for zfput, etc.
+ # We have two choices:
+ # (1) Get the entire file list and match it one by one
+ # locally against the pattern.
+ # Causes problems if we are globbing directories (rare, presumably).
+ # But: we can cache the current directory, which
+ # we need for completion anyway. Works on any OS if you
+ # stick with a single directory. This is the default.
+ # (2) Use remote globbing, i.e. pass it to ls at the site.
+ # Faster, but only works with UNIX, and only basic globbing.
+ # We do this if $zfrglob is non-null.
+
+ # There is only one argument, the variable containing the
+ # pattern to be globbed. We set this back to an array containing
+ # all the matches.
+ setopt localoptions unset
+ unsetopt ksharrays
+
+ local pat dir nondir files i
+
+ eval pat=\$$1
+
+ # Check if we really need to do anything. Look for standard
+ # globbing characters, and if extendedglob is set and we are
+ # using zsh for the actual pattern matching also look for
+ # extendedglob characters.
+ if [[ $remlist != *[][*?]* &&
+ ( -n $zfrglob || ! -o extendedglob || $remlist != *[(|)~#^]* ) ]]; then
+ return 0
+ fi
+
+ if [[ $zfrglob != '' ]]; then
+ eval "$1=(\$(zftp ls \"$pat\" 2>/dev/null))"
+ else
+ if [[ $ZFTP_SYSTEM = UNIX* && $pat = */* ]]; then
+ # not the current directory and we know how to handle paths
+ if [[ $pat = ?*/* ]]; then
+ # careful not to remove too many slashes
+ dir=${pat%/*}
+ else
+ dir=/
+ fi
+ nondir=${pat##*/}
+ files=($(zftp ls "$dir" 2>/dev/null))
+ else
+ # we just have to do an ls and hope that's right
+ nondir=$pat
+ if (( $#zftp_fcache == 0 )); then
+ zftp_fcache=($(zftp ls))
+ fi
+ files=($zftp_fcache)
+ fi
+ # now we want to see which of the $files match $nondir
+ for (( i = 1; i <= $#files; i++)); do
+ # empty words are elided in array assignment
+ [[ $files[$i] = ${~nondir} ]] || files[$i]=''
+ done
+ eval "$1=(\$files)"
+ fi
+}
+
+function zfrtime {
+ # Set the modification time of file LOCAL to that of REMOTE.
+ # If the optional TIME is passed, it should be in the FTP format
+ # CCYYMMDDhhmmSS, i.e. no dot before the seconds, and in GMT.
+ # This is what both `zftp remote' and `zftp local' return.
+ #
+ # Unfortunately, since the time returned from FTP is GMT and
+ # your file needs to be set in local time, we need to do some
+ # hacking around with time. At the moment this requires perl 5
+ # with the standard library.
+
+ setopt localoptions unset
+ unsetopt ksharrays
+
+ local time gmtime loctime
+
+ if [[ -n $3 ]]; then
+ time=$3
+ else
+ time=($(zftp remote $2 2>/dev/null))
+ [[ -n $time ]] && time=$time[2]
+ fi
+ [[ -z $time ]] && return 1
+
+ # Now's the real *!@**!?!. We have the date in GMT and want to turn
+ # it into local time for touch to handle. It's just too nasty
+ # to handle in zsh; do it in perl.
+ if perl -mTime::Local -e '($file, $t) = @ARGV;
+ $yr = substr($t, 0, 4) - 1900;
+ $mon = substr($t, 4, 2) - 1;
+ $mday = substr($t, 6, 2) + 0;
+ $hr = substr($t, 8, 2) + 0;
+ $min = substr($t, 10, 2) + 0;
+ $sec = substr($t, 12, 2) + 0;
+ $time = Time::Local::timegm($sec, $min, $hr, $mday, $mon, $yr);
+ utime $time, $time, $file and return 0;' $1 $time 2>/dev/null; then
+ print "Setting time for $1 failed. Need perl 5." 2>1
+ fi
+
+ # If it wasn't for the GMT/local time thing, it would be this simple.
+ #
+ # time="${time[1,12]}.${time[13,14]}"
+ #
+ # touch -t $time $1
+
+}
+
+function zfstat {
+ # Give a zftp status report using local variables.
+ # With option -v, connect the remote host and ask it what it
+ # thinks the status is.
+
+ setopt localoptions unset
+ unsetopt ksharrays
+
+ local i stat=0 opt optlist verbose
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $opt in
+ v) verbose=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ # hack: in case the status from a subshell process hasn't been
+ # fixed yet
+ zftp type >&/dev/null
+
+ if [[ -n $ZFTP_HOST ]]; then
+ print "Host:\t\t$ZFTP_HOST"
+ print "IP:\t\t$ZFTP_IP"
+ [[ -n $ZFTP_SYSTEM ]] && print "System type:\t$ZFTP_SYSTEM"
+ if [[ -n $ZFTP_USER ]]; then
+ print "User:\t\t$ZFTP_USER "
+ [[ -n $ZFTP_ACCOUNT ]] && print "Account:\t$AFTP_ACCOUNT"
+ print "Directory:\t$ZFTP_PWD"
+ print -n "Transfer type:\t"
+ if [[ $ZFTP_TYPE = "I" ]]; then
+ print Image
+ elif [[ $ZFTP_TYPE = "A" ]]; then
+ print Ascii
+ else
+ print Unknown
+ fi
+ print -n "Transfer mode:\t"
+ if [[ $ZFTP_MODE = "S" ]]; then
+ print Stream
+ elif [[ $ZFTP_MODE = "B" ]]; then
+ print Block
+ else
+ print Unknown
+ fi
+ else
+ print "No user logged in."
+ fi
+ else
+ print "Not connected."
+ stat=1
+ fi
+
+ # things which may be set even if not connected:
+ [[ -n $ZFTP_REPLY ]] && print "Last reply:\t$ZFTP_REPLY"
+ print "Verbosity:\t$ZFTP_VERBOSE"
+ print -n "Preferences:\t"
+ for (( i = 1; i <= ${#ZFTP_PREFS}; i++ )); do
+ case $ZFTP_PREFS[$i] in
+ [pP]) print -n "Passive "
+ ;;
+ [sS]) print -n "Sendport "
+ ;;
+ [dD]) print -n "Dumb "
+ ;;
+ *) print -n "$ZFTP_PREFS[$i]???"
+ esac
+ done
+ print
+
+ if [[ -n $ZFTP_HOST && $verbose = 1 ]]; then
+ print "Status of remote server:"
+ # make sure we print the reply
+ local ZFTP_VERBOSE=045
+ zftp quote STAT
+ fi
+
+ return $stat
+}
+
+function zftp_chpwd {
+ # You may want to alter chpwd to call this when $ZFTP_USER is set.
+ # If so, call it with a non-zero first argument so it doesn't
+ # print the new FTP directory.
+
+ # Cancel the filename cache for the current directory.
+ zftp_fcache=()
+ # ...and also empty the stored directory listing cache.
+ # As this function is called when we close the connection, this
+ # is the only place we need to do these two things.
+ [[ -n $zfcurdir && -f $zfcurdir ]] && rm -f $zfcurdir
+
+ if [[ -z $ZFTP_USER ]]; then
+ # last call, after an FTP logout
+
+ # delete the non-current cached directory
+ [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir
+ zfotherargs=
+
+ # don't keep zflastdir between opens
+ zflastdir=
+
+ # return the display to standard
+ # uncomment the following line if you have a chpwd which shows directories
+ # chpwd
+ else
+ [[ -z $zflastdir ]] && zflastdir=$ZFTP_PWD
+ local args
+ if [[ -t 1 && -t 2 ]]; then
+ local str="$ZFTP_HOST:$ZFTP_PWD"
+ [[ -z $1 ]] && print $str
+ [[ ${#str} -lt 70 ]] && str="%m: %~ $str"
+ case $TERM in
+ sun-cmd) print -n -P "\033]l$str\033\\"
+ ;;
+ xterm) print -n -P "\033]2;$str\a"
+ ;;
+ esac
+ fi
+ fi
+}
+
+function zftp_progress {
+ # Basic progress metre, showing the percent of the file transferred.
+ # You want growing bars? You gotta write growing bars.
+
+ # Don't show progress unless stderr is a terminal
+ [[ ! -t 2 ]] && return 0
+
+ if [[ $ZFTP_TRANSFER = *F ]]; then
+ print 1>&2
+ elif [[ -n $ZFTP_TRANSFER ]]; then
+ if [[ -n $ZFTP_SIZE ]]; then
+ local frac="$(( ZFTP_COUNT * 100 / ZFTP_SIZE ))%"
+ print -n "\r$ZFTP_FILE ($ZFTP_SIZE bytes): $ZFTP_TRANSFER $frac" 1>&2
+ else
+ print -n "\r$ZFTP_FILE: $ZFTP_TRANSFER $ZFTP_COUNT" 1>&2
+ fi
+ fi
+}
+
+function zftype {
+ local type
+
+ if (( $# == 0 )); then
+ type=$(zftp type)
+ if [[ $type = I ]]; then
+ print "Current type is image (binary)"
+ return 0
+ elif [[ $type = A ]]; then
+ print "Current type is ASCII"
+ return 0
+ else
+ return 1
+ fi
+ else
+ if [[ $1 == [aA]([sS][cC]([iI][iI]|)|) ]]; then
+ type=A
+ elif [[ $1 == [iI]([mM]([aA][gG][eE]|)|) ||
+ $1 == [bB]([iI][nN]([aA][rR][yY]|)|) ]]; then
+ type=I
+ else
+ print "Type not recognised: $1" 2>&1
+ return 1
+ fi
+ zftp type $type
+ fi
+}
+
+function zfuget {
+ # Get a list of files from the server with update.
+ # In other words, only retrieve files which are newer than local
+ # ones. This depends on the clocks being adjusted correctly
+ # (i.e. if one is fifteen minutes out, for the next fifteen minutes
+ # updates may not be correctly calculated). However, difficult
+ # cases --- where the files are the same size, but the remote is newer,
+ # or have different sizes, but the local is newer -- are prompted for.
+ #
+ # Files are globbed on the remote host --- assuming, of course, they
+ # haven't already been globbed local, so use 'noglob' e.g. as
+ # `alias zfuget="noglob zfuget"'.
+ #
+ # Options:
+ # -G Glob: turn off globbing
+ # -v verbose: print more about the files listed.
+ # -s silent: don't ask, just guess. The guesses are:
+ # - if the files have different sizes but remote is older ) grab
+ # - if they have the same size but remote is newer )
+ # which is safe if the remote files are always the right ones.
+ # -t time: update the local file times to the same time as the remote.
+ # Currently this only works if you have the `perl' command,
+ # and that perl is version 5 with the standard library.
+ # See the function zfrtime for more gory details.
+
+ setopt localoptions
+ unsetopt ksharrays shwordsplit
+
+ local loc rem stat=0 locstats remstats doit tmpfile=${TMPPREFIX}zfuget$$
+ local rstat remlist verbose optlist opt bad i silent nglob time
+
+ zfuget_print_time() {
+ local tim=$1
+ print -n "$tim[1,4]/$tim[5,6]/$tim[7,8] $tim[9,10]:$tim[11,12].$tim[13,14]"
+ print -n GMT
+ }
+
+ zfuget_print () {
+ print -n "\nremote $rem ("
+ zfuget_print_time $remstats[2]
+ print -n ", $remstats[1] bytes)\nlocal $loc ("
+ zfuget_print_time $locstats[2]
+ print ", $locstats[1] bytes)"
+ }
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ v) verbose=1
+ ;;
+ s) silent=1
+ ;;
+ G) nglob=1
+ ;;
+ t) time=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ [[ -n $bad ]] && return 1
+
+ for remlist in $*; do
+ # zfcd directory hack to put the front back to ~
+ if [[ $remlist == $HOME || $remlist == $HOME/* ]]; then
+ remlist="~${remlist#$HOME}"
+ fi
+ if [[ $nglob != 1 ]]; then
+ zfrglob remlist
+ fi
+ if (( $#remlist )); then
+ for rem in $remlist; do
+ loc=${rem:t}
+ doit=y
+ remstats=()
+ if [[ -f $loc ]]; then
+ zftp local $loc >$tmpfile
+ locstats=($(<$tmpfile))
+ zftp remote $rem >$tmpfile
+ rstat=$?
+ remstats=($(<$tmpfile))
+ rm -f $tmpfile
+ if [[ $rstat = 2 ]]; then
+ print "Server does not implement full command set required." 1>&2
+ return 1
+ elif [[ $rstat = 1 ]]; then
+ print "File not found on server: $rem" 1>&2
+ stat=1
+ continue
+ fi
+ [[ $verbose = 1 ]] && zfuget_print
+ if (( $locstats[1] != $remstats[1] )); then
+ # Files have different sizes
+ if [[ $locstats[2] > $remstats[2] && $silent != 1 ]]; then
+ [[ $verbose != 1 ]] && zfuget_print
+ print "Local file $loc more recent than remote," 1>&2
+ print -n "but sizes are different. Transfer anyway [y/n]? " 1>&2
+ read -q doit
+ fi
+ else
+ # Files have same size
+ if [[ $locstats[2] < $remstats[2] ]]; then
+ if [[ $silent != 1 ]]; then
+ [[ $verbose != 1 ]] && zfuget_print
+ print "Local file $loc has same size as remote," 1>&2
+ print -n "but local file is older. Transfer anyway [y/n]? " 1>&2
+ read -q doit
+ fi
+ else
+ # presumably same file, so don't get it.
+ [[ $verbose = 1 ]] && print Not transferring
+ doit=n
+ fi
+ fi
+ else
+ [[ $verbose = 1 ]] && print New file $loc
+ fi
+ if [[ $doit = y ]]; then
+ if zftp get $rem >$loc; then
+ if [[ $time = 1 ]]; then
+ # if $remstats is set, it's second element is the remote time
+ zfrtime $loc $rem $remstats[2]
+ fi
+ else
+ stat=$?
+ fi
+
+ fi
+ done
+ fi
+ done
+ return $stat
+}
+
+function zfuput {
+ # Put a list of files from the server with update.
+ # See zfuget for details.
+ #
+ # Options:
+ # -v verbose: print more about the files listed.
+ # -s silent: don't ask, just guess. The guesses are:
+ # - if the files have different sizes but remote is older ) grab
+ # - if they have the same size but remote is newer )
+ # which is safe if the remote files are always the right ones.
+
+ setopt localoptions
+ unsetopt ksharrays shwordsplit
+
+ local loc rem stat=0 locstats remstats doit tmpfile=${TMPPREFIX}zfuput$$
+ local rstat verbose optlist opt bad i silent
+
+ zfuput_print_time() {
+ local tim=$1
+ print -n "$tim[1,4]/$tim[5,6]/$tim[7,8] $tim[9,10]:$tim[11,12].$tim[13,14]"
+ print -n GMT
+ }
+
+ zfuput_print () {
+ print -n "\nremote $rem ("
+ zfuput_print_time $remstats[2]
+ print -n ", $remstats[1] bytes)\nlocal $loc ("
+ zfuput_print_time $locstats[2]
+ print ", $locstats[1] bytes)"
+ }
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ v) verbose=1
+ ;;
+ s) silent=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ [[ -n $bad ]] && return 1
+
+ if [[ $ZFTP_VERBOSE = *5* ]]; then
+ # should we turn it off locally?
+ print "Messages with code 550 are harmless." >&2
+ fi
+
+ for loc in $*; do
+ rem=${loc:t}
+ doit=y
+ remstats=()
+ if [[ ! -f $loc ]]; then
+ print "$loc: file not found" >&2
+ stat=1
+ continue
+ fi
+ zftp local $loc >$tmpfile
+ locstats=($(<$tmpfile))
+ zftp remote $rem >$tmpfile
+ rstat=$?
+ remstats=($(<$tmpfile))
+ rm -f $tmpfile
+ if [[ $rstat = 2 ]]; then
+ print "Server does not implement full command set required." 1>&2
+ return 1
+ elif [[ $rstat = 1 ]]; then
+ [[ $verbose = 1 ]] && print New file $loc
+ else
+ [[ $verbose = 1 ]] && zfuput_print
+ if (( $locstats[1] != $remstats[1] )); then
+ # Files have different sizes
+ if [[ $locstats[2] < $remstats[2] && $silent != 1 ]]; then
+ [[ $verbose != 1 ]] && zfuput_print
+ print "Remote file $rem more recent than local," 1>&2
+ print -n "but sizes are different. Transfer anyway [y/n]? " 1>&2
+ read -q doit
+ fi
+ else
+ # Files have same size
+ if [[ $locstats[2] > $remstats[2] ]]; then
+ if [[ $silent != 1 ]]; then
+ [[ $verbose != 1 ]] && zfuput_print
+ print "Remote file $rem has same size as local," 1>&2
+ print -n "but remote file is older. Transfer anyway [y/n]? " 1>&2
+ read -q doit
+ fi
+ else
+ # presumably same file, so don't get it.
+ [[ $verbose = 1 ]] && print Not transferring
+ doit=n
+ fi
+ fi
+ fi
+ if [[ $doit = y ]]; then
+ zftp put $rem <$loc || stat=$?
+ fi
+ done
+ return $stat
+}