mirror of
git://git.code.sf.net/p/zsh/code
synced 2024-12-28 16:15:02 +01:00
36630: new function zsh_directory_name_generic
This commit is contained in:
parent
377e2400b7
commit
649d06a8cd
4 changed files with 342 additions and 2 deletions
|
@ -1,3 +1,8 @@
|
|||
2015-09-25 Peter Stephenson <p.w.stephenson@ntlworld.com>
|
||||
|
||||
* 36630: Doc/Zsh/contrib.yo, Doc/Zsh/manual.yo,
|
||||
Functions/Chpwd/zsh_directory_name_generic: new helper function.
|
||||
|
||||
2015-09-24 Barton E. Schaefer <schaefer@zsh.org>
|
||||
|
||||
* 36623: Doc/Zsh/contrib.yo: document bracketed-paste-magic and
|
||||
|
|
|
@ -12,6 +12,7 @@ such as shell functions, look for comments in the function source files.
|
|||
startmenu()
|
||||
menu(Utilities)
|
||||
menu(Recent Directories)
|
||||
menu(Other Directory Functions)
|
||||
menu(Version Control Information)
|
||||
menu(Prompt Themes)
|
||||
menu(ZLE Functions)
|
||||
|
@ -324,7 +325,7 @@ options tt(-Uz) are appropriate.
|
|||
)
|
||||
enditem()
|
||||
|
||||
texinode(Recent Directories)(Version Control Information)(Utilities)(User Contributions)
|
||||
texinode(Recent Directories)(Other Directory Functions)(Utilities)(User Contributions)
|
||||
cindex(recent directories, maintaining list of)
|
||||
cindex(directories, maintaining list of recent)
|
||||
findex(cdr)
|
||||
|
@ -585,7 +586,189 @@ avoid side effects if the change to the directory is to be invisible at the
|
|||
command line. See the contents of the function tt(chpwd_recent_dirs) for
|
||||
more details.
|
||||
|
||||
texinode(Version Control Information)(Prompt Themes)(Recent Directories)(User Contributions)
|
||||
texinode(Other Directory Functions)(Version Control Information)(Recent Directories)(User Contributions)
|
||||
cindex(directories, named, dynamic, helper function)
|
||||
cindex(dynamic directory naming, helper function)
|
||||
cindex(named directories, dynamic, helper function)
|
||||
findex(zsh_directory_name_generic)
|
||||
sect(Abbreviated dynamic references to directories)
|
||||
|
||||
The dynamic directory naming system is described in the subsection
|
||||
em(Dynamic named directories) of
|
||||
ifzman(the section em(Filename Expansion) in zmanref(expn))\
|
||||
ifnzman(noderef(Filename Expansion)). In this, a reference to
|
||||
tt(~[)var(...)tt(]) is expanded by a function found by the hooks
|
||||
mechanism.
|
||||
|
||||
The contributed function tt(zsh_directory_name_generic) provides a
|
||||
system allowing the user to refer to directories with only a limited
|
||||
amount of new code. It supports all three of the standard interfaces
|
||||
for directory naming: converting from a name to a directory, converting
|
||||
in the reverse direction to find a short name, and completion of names.
|
||||
|
||||
The main feature of this function is a path-like syntax,
|
||||
combining abbreviations at multiple levels separated by ":".
|
||||
As an example, ~[g:p:s] might specify:
|
||||
startitem()
|
||||
item(tt(g))(
|
||||
The top level directory for your git area. This first component
|
||||
has to match, or the function will retrun indicating another
|
||||
directory name hook function should be tried.
|
||||
)
|
||||
item(tt(p))(
|
||||
The name of a project within your git area.
|
||||
)
|
||||
item(tt(s))(
|
||||
The source area within that project.
|
||||
)
|
||||
enditem()
|
||||
This allows you to collapse references to long hierarchies to a very
|
||||
compact form, particularly if the hierarchies are similar across different
|
||||
areas of the disk.
|
||||
|
||||
Name components may be completed: if a description is shown at the top
|
||||
of the list of completions, it includes the path to which previous
|
||||
components expand, while the description for an individual completion
|
||||
shows the path segment it would add. No additional configuration is
|
||||
needed for this as the completion system is aware of the dynamic
|
||||
directory name mechanism.
|
||||
|
||||
subsect(Usage)
|
||||
|
||||
To use the function, first define a wrapper function for your specific
|
||||
case. We'll assume it's to be autoloaded. This can have any name but
|
||||
we'll refer to it as zdn_mywrapper. This wrapper function will define
|
||||
various variables and then call this function with the same arguments
|
||||
that the wrapper function gets. This configuration is described below.
|
||||
|
||||
Then arrange for the wrapper to be run as a zsh_directory_name hook:
|
||||
|
||||
example(autoload -Uz add-zsh-hook zsh_diretory_name_generic zdn_mywrapper
|
||||
add-zsh-hook -U zsh_directory_name zdn_mywrapper)
|
||||
|
||||
subsect(Configuration)
|
||||
|
||||
The wrapper function should define a local associative array zdn_top.
|
||||
Alternatively, this can be set with a style called tt(mapping). The
|
||||
context for the style is tt(:zdn:)var(wrapper-name) where
|
||||
var(wrapper-name) is the function calling zsh_directory_name_generic;
|
||||
for example:
|
||||
|
||||
example(zstyle :zdn:zdn_mywrapper: mapping zdn_mywrapper_top)
|
||||
|
||||
The keys in this associative array correspond to the first component of
|
||||
the name. The values are matching directories. They may have an
|
||||
optional suffix with a slash followed by a colon and the name of a
|
||||
variable in the same format to give the next component. (The slash
|
||||
before the colon is to disambiguate the case where a colon is needed in
|
||||
the path for a drive. There is otherwise no syntax for escaping this,
|
||||
so path components whose names start with a colon are not supported.) A
|
||||
special component tt(:default:) specifies a variable in the form
|
||||
tt(/:)var(var) (the path section is ignored and so is usually empty)
|
||||
that will be used for the next component if no variable is given for the
|
||||
path. Variables referred to within tt(zdn_top) have the same format as
|
||||
tt(zdn_top) itself, but contain relative paths.
|
||||
|
||||
For example,
|
||||
|
||||
example(local -A zdn_top=(
|
||||
g ~/git
|
||||
ga ~/alternate/git
|
||||
gs /scratch/$USER/git/:second2
|
||||
:default: /:second1
|
||||
))
|
||||
|
||||
This specifies the behaviour of a directory referred to as tt(~[g:...])
|
||||
or tt(~[ga:...]) or tt(~[gs:...]). Later path components are optional;
|
||||
in that case tt(~[g]) expands to tt(~/git), and so on. tt(gs) expands
|
||||
to tt(/scratch/$USER/git) and uses the associative array tt(second2) to
|
||||
match the second component; tt(g) and tt(ga) use the associative array
|
||||
tt(second1) to match the second component.
|
||||
|
||||
When expanding a name to a directory, if the first component is not tt(g) or
|
||||
tt(ga) or tt(gs), it is not an error; the function simply returns 1 so that a
|
||||
later hook function can be tried. However, matching the first component
|
||||
commits the function, so if a later component does not match, an error
|
||||
is printed (though this still does not stop later hooks from being
|
||||
executed).
|
||||
|
||||
For components after the first, a relative path is expected, but note that
|
||||
multiple levels may still appear. Here is an example of tt(second1):
|
||||
|
||||
example(local -A second1=(
|
||||
p myproject
|
||||
s somproject
|
||||
os otherproject/subproject/:third
|
||||
))
|
||||
|
||||
The path as found from tt(zdn_top) is extended with the matching
|
||||
directory, so tt(~[g:p]) becomes tt(~/git/myproject). The slash between
|
||||
is added automatically (it's not possible to have a later component
|
||||
modify the name of a directory already matched). Only tt(os) specifies
|
||||
a variable for a third component, and there's no tt(:default:), so it's
|
||||
an error to use a name like tt(~[g:p:x]) or tt(~[ga:s:y]) because
|
||||
there's nowhere to look up the tt(x) or tt(y).
|
||||
|
||||
The associative arrays need to be visible within this function; the
|
||||
generic function therefore uses internal variable names beginning
|
||||
tt(_zdn_) in order to avoid clashes. Note that the variable tt(reply)
|
||||
needs to be passed back to the shell, so should not be local in the
|
||||
calling function.
|
||||
|
||||
The function does not test whether directories assembled by component
|
||||
actually exist; this allows the system to work across automounted
|
||||
file systems. The error from the command trying to use a non-existent
|
||||
directory should be sufficient to indicate the problem.
|
||||
|
||||
subsect(Complete example)
|
||||
|
||||
Here is a full fictitious but usable autoloadable definition of the
|
||||
example function defined by the code above. So tt(~[gs:p:s]) expands
|
||||
to tt(/scratch/$USER/git/myscratchproject/top/srcdir) (with tt($USER)
|
||||
also expanded).
|
||||
|
||||
example(local -A zdn_top=(
|
||||
g ~/git
|
||||
ga ~/alternate/git
|
||||
gs /scratch/$USER/git/:second2
|
||||
:default: /:second1
|
||||
)
|
||||
|
||||
local -A second1=(
|
||||
p myproject
|
||||
s somproject
|
||||
os otherproject/subproject/:third
|
||||
)
|
||||
|
||||
local -A second2=(
|
||||
p myscratchproject
|
||||
s somescratchproject
|
||||
)
|
||||
|
||||
local -A third=(
|
||||
s top/srcdir
|
||||
d top/documentation
|
||||
)
|
||||
|
||||
# autoload not needed if you did this at initialisation...
|
||||
autoload -Uz zsh_directory_name_generic
|
||||
zsh_directory_name_generic "$@)
|
||||
|
||||
It is also possible to use global associative arrays, suitably named,
|
||||
and set the style for the context of your wrapper function to
|
||||
refer to this. Then your set up code would contain the following:
|
||||
|
||||
example(typeset -A zdn_mywrapper_top=(...)
|
||||
# ... and so on for other associative arrays ...
|
||||
zstyle ':zdn:zdn_mywrapper:' mapping zdn_mywrapper_top
|
||||
autoload -Uz add-zsh-hook zsh_directory_name_generic zdn_mywrapper
|
||||
add-zsh-hook -U zsh_directory_name zdn_mywrapper)
|
||||
|
||||
and the function tt(zdn_mywrapper) would contain only the following:
|
||||
|
||||
example(zsh_directory_name_generic "$@")
|
||||
|
||||
texinode(Version Control Information)(Prompt Themes)(Other Directory Functions)(User Contributions)
|
||||
sect(Gathering information from version control systems)
|
||||
cindex(version control utility)
|
||||
|
||||
|
|
|
@ -164,6 +164,7 @@ User Contributions
|
|||
|
||||
menu(Utilities)
|
||||
menu(Recent Directories)
|
||||
menu(Other Directory Functions)
|
||||
menu(Version Control Information)
|
||||
menu(Prompt Themes)
|
||||
menu(ZLE Functions)
|
||||
|
|
151
Functions/Chpwd/zsh_directory_name_generic
Normal file
151
Functions/Chpwd/zsh_directory_name_generic
Normal file
|
@ -0,0 +1,151 @@
|
|||
## zsh_directory_name_generic
|
||||
#
|
||||
# This function is useful as a hook function for the zsh_directory_name
|
||||
# facility.
|
||||
#
|
||||
# See the zsh-contrib manual page for more.
|
||||
|
||||
emulate -L zsh
|
||||
setopt extendedglob
|
||||
local -a match mbegin mend
|
||||
|
||||
# The variable containing the top level mapping.
|
||||
local _zdn_topvar
|
||||
|
||||
zmodload -i zsh/parameter
|
||||
zstyle -s ":zdn:${funcstack[2]}:" mapping _zdn_topvar || _zdn_topvar=zdn_top
|
||||
|
||||
if (( ! ${(P)#_zdn_topvar} )); then
|
||||
print -r -- "$0: $_zdn_topver is not set" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local _zdn_var=$_zdn_topvar
|
||||
local -A _zdn_assoc
|
||||
|
||||
if [[ $1 = n ]]; then
|
||||
# Turning a name into a directory.
|
||||
local _zdn_name=$2
|
||||
local -a _zdn_words
|
||||
local _zdn_dir _zdn_cpt
|
||||
|
||||
_zdn_words=(${(s.:.)_zdn_name})
|
||||
while (( ${#_zdn_words} )); do
|
||||
if [[ -z ${_zdn_var} ]]; then
|
||||
print -r -- "$0: too many components in directory name \`$_zdn_name'" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Subscripting (P)_zdn_var directly seems not to work.
|
||||
_zdn_assoc=(${(Pkv)_zdn_var})
|
||||
_zdn_cpt=${_zdn_assoc[${_zdn_words[1]}]}
|
||||
shift _zdn_words
|
||||
|
||||
if [[ -z $_zdn_cpt ]]; then
|
||||
# If top level component, just try another expansion
|
||||
if [[ $_zdn_var != $_zdn_top ]]; then
|
||||
# Committed to this expansion, so report failure.
|
||||
print -r -- "$0: no expansion for directory name \`$_zdn_name'" >&2
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
if [[ $_zdn_cpt = (#b)(*)/:([[:IDENT:]]##) ]]; then
|
||||
_zdn_cpt=$match[1]
|
||||
_zdn_var=$match[2]
|
||||
else
|
||||
# may be empty
|
||||
_zdn_var=${${_zdn_assoc[:default:]}##*/:}
|
||||
fi
|
||||
_zdn_dir=${_zdn_dir:+$_zdn_dir/}$_zdn_cpt
|
||||
done
|
||||
if (( ${#_zdn_dir} )); then
|
||||
typeset -ag reply
|
||||
reply=($_zdn_dir)
|
||||
return 0
|
||||
fi
|
||||
elif [[ $1 = d ]]; then
|
||||
# Turning a directory into a name.
|
||||
local _zdn_dir=$2
|
||||
local _zdn_rest=$_zdn_dir
|
||||
local -a _zdn_cpts
|
||||
local _zdn_pref _zdn_pref_raw _zdn_matched _zdn_cpt _zdn_name
|
||||
|
||||
while [[ -n $_zdn_var && -n $_zdn_rest ]]; do
|
||||
_zdn_assoc=(${(Pkv)_zdn_var})
|
||||
# Sorting in descending order will ensure prefixes
|
||||
# come after longer strings with that perfix, so
|
||||
# we match more specific directory names preferentially.
|
||||
_zdn_cpts=(${(Ov)_zdn_assoc})
|
||||
_zdn_cpt=''
|
||||
for _zdn_pref_raw in $_zdn_cpts; do
|
||||
_zdn_pref=${_zdn_pref_raw%/:*}
|
||||
[[ -z $_zdn_pref ]] && continue
|
||||
if [[ $_zdn_rest = $_zdn_pref(#b)(/|)(*) ]]; then
|
||||
_zdn_cpt=${(k)_zdn_assoc[(r)$_zdn_pref_raw]}
|
||||
# if we matched a /, too, add it...
|
||||
_zdn_matched+=$_zdn_pref$match[1]
|
||||
_zdn_rest=$match[2]
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -n $_zdn_cpt ]]; then
|
||||
_zdn_name+=${_zdn_name:+${_zdh_name}:}$_zdn_cpt
|
||||
if [[ ${_zdn_assoc[$_zdn_cpt]} = (#b)*/:([[:IDENT:]]##) ]]; then
|
||||
_zdn_var=$match[1]
|
||||
else
|
||||
_zdn_var=${${_zdn_assoc[:default:]}##*/:}
|
||||
fi
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -n $_zdn_name ]]; then
|
||||
# matched something, so report that.
|
||||
integer _zdn_len=${#_zdn_matched}
|
||||
[[ $_zdn_matched[-1] = / ]] && (( _zdn_len-- ))
|
||||
typeset -ag reply
|
||||
reply=($_zdn_name $_zdn_len)
|
||||
return 0
|
||||
fi
|
||||
# else let someone else have a go.
|
||||
elif [[ $1 = c ]]; then
|
||||
# Completion
|
||||
|
||||
if [[ -n $SUFFIX ]]; then
|
||||
_message "Can't complete in the middle of a dynamic directory name"
|
||||
else
|
||||
local -a _zdn_cpts
|
||||
local _zdn_word _zdn_cpt _zdn_desc _zdn_sofar expl
|
||||
|
||||
while [[ -n ${_zdn_var} && ${PREFIX} = (#b)([^:]##):* ]]; do
|
||||
_zdn_word=$match[1]
|
||||
compset -P '[^:]##:'
|
||||
_zdn_assoc=(${(Pkv)_zdn_var})
|
||||
_zdn_cpt=${_zdn_assoc[$_zdn_word]}
|
||||
# We only complete at the end so must match here
|
||||
[[ -z $_zdn_cpt ]] && return 1
|
||||
if [[ $_zdn_cpt = (#b)(*)/:([[:IDENT:]]##) ]]; then
|
||||
_zdn_cpt=$match[1]
|
||||
_zdn_var=$match[2]
|
||||
else
|
||||
_zdn_var=${${_zdn_assoc[:default:]}##*/:}
|
||||
fi
|
||||
_zdn_sofar+=${_zdn_sofar:+${_zdn_sofar}/}$_zdn_cpt
|
||||
done
|
||||
if [[ -n $_zdn_var ]]; then
|
||||
_zdn_assoc=(${(Pkv)_zdn_var})
|
||||
local -a _zdn_cpts
|
||||
for _zdn_cpt _zdn_desc in ${(kv)_zdn_assoc}; do
|
||||
[[ $_zdn_cpt = :* ]] && continue
|
||||
_zdn_cpts+=(${_zdn_cpt}:${_zdn_desc%/:[[:IDENT:]]##})
|
||||
done
|
||||
_describe -t dirnames "directory name under ${_zdn_sofar%%/}" \
|
||||
_zdn_cpts -S: -r ':]'
|
||||
return
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Failed
|
||||
return 1
|
||||
## end
|
Loading…
Reference in a new issue