zsh-users
 help / color / mirror / code / Atom feed
* Dynamic directory name function
@ 2015-09-22 19:42 Peter Stephenson
  2015-09-23  4:07 ` Bart Schaefer
  0 siblings, 1 reply; 6+ messages in thread
From: Peter Stephenson @ 2015-09-22 19:42 UTC (permalink / raw)
  To: Zsh Users

For some time now, I've had a hook function for the zsh_directory_name
mechanism that allows me to refer to directory hierarchies in a brief
fashion.  For example ~[g:p:s] may mean the directory is in my main git
client, in a certain project, and in the source directory of that
project.  So if I have multiple git areas and projects with similar
structure I can cover a lot of ground very easily.

For almost as long I've been meaning to turn this into a generic
function that you can configure just with a few variables.  I've finally
done that, and this has allowed me to add the completion support that
was missing before.

The documentation should give enough indication of whether this is
useful or not.

I expect I'll add some version of this to the distribution at some
point.

pws


## zsh_directory_name_generic
#
# This function is useful as a hook function for the zsh_directory_name
# facility.  It provides dynamic directory naming in both directions,
# i.e. from name to directory for use in ~[...] and from directory to
# name for use in prompts etc., as well as completion.
#
# 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:
#  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.
#  p - The name of a project within your git area.
#  s - The source area within that project.
# 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.
#
# To use it,
# - 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_wrapper.  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.
# - Arrange for the wrapper to be run as a zsh_directory_name hook:
#     autoload -Uz add-zsh-hook zsh_diretory_name_generic zdn_wrapper
#     add-zsh-hook -U zsh_directory_name zdn_wrapper
#
# Configuration:
#
# The wrapper function should define a local associative array zdn_top.
# The keys in this 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 :default: specifies a variable in the form /: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 zdn_top have the same format as zdn_top.
#
# For 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 ~[g:...] or
# ~[ga:...] or ~[gs:...].  Later path components are optional; in that
# case ~[g] expands to ~/git, and so on.  gs expands to /scratch/$USER/git
# and uses the associative array second2 to match the second component;
# g and ga use the associative array second1 to match the second component.
#
# When expanding a name to a directory, if the first component is not g
# or ga or 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 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 second1:
#
#   local -A second1=(
#     p   myproject
#     s   somproject
#     os  otherproject/subproject/:third
#   )
#
# The path as found from zdn_top is extended with the matching directory,
# so ~[g:p] becomes ~/git/myproject.  The "/" between is added automatically
# (it's not possible to have a later component modify the name of a directory
# already matched).  Only "os" specifies a variable for a third component,
# and there's no :default:, so it's an error to use a name like ~[g:p:x]
# or ~[ga:s:y] because there's nowhere to look up the x or y.
#
# The associative arrays need to be visible within this function; the
# function therefore uses internal variable names beginning _zdn_ in
# order to avoid clashes.  Note that the variable "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.
#
# Here is a full fictitious but usable autoloadable definition of the
# example function defined by the code above.  So ~[gs:p:s] expands
# to /scratch/$USER/git/myscratchproject/top/srcdir.
#
#   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 -Uz zsh_directory_name_generic # paranoia in case we forgot
#   zsh_directory_name_generic "$@"

emulate -L zsh
setopt extendedglob
local -a match mbegin mend

# The variable containing the top level mapping.
local _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
    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_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 in $_zdn_cpts; do
      _zdn_pref=${_zdn_pref%/:*}
      [[ -z $_zdn_pref ]] && continue
      if [[ $_zdn_rest = $_zdn_pref(#b)(/|)(*) ]]; then
	_zdn_cpt=${(k)_zdn_assoc[(r)$_zdn_pref]}
	# 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_pt]} = (#b)*/:(*) ]]; 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-- ))
    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


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Dynamic directory name function
  2015-09-22 19:42 Dynamic directory name function Peter Stephenson
@ 2015-09-23  4:07 ` Bart Schaefer
  2015-09-23  8:48   ` Peter Stephenson
  0 siblings, 1 reply; 6+ messages in thread
From: Bart Schaefer @ 2015-09-23  4:07 UTC (permalink / raw)
  To: Zsh Users

On Sep 22,  8:42pm, Peter Stephenson wrote:
} Subject: Dynamic directory name function
}
} For almost as long I've been meaning to turn this into a generic
} function that you can configure just with a few variables.  I've finally
} done that, and this has allowed me to add the completion support that
} was missing before.

I have one immediate suggestion:  Use zstyle instead of variables.  Look
them up in the context of the wrapper function name (with some prefix to
disambiguate from other zstyle uses).  Remember that there's nothing in
the zstyle builtin that requires you to use colon-separated context names
or wildcards.

Places where you currently use e.g.  _zdn_assoc=(${(Pkv)_zdn_var}) become

    zstyle -a ZDN_${0} zdn_var _zdn_assoc

or the like; it becomes possible to define multiple wrapper functions if
for some reason that's useful.

Thoughts?


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Dynamic directory name function
  2015-09-23  4:07 ` Bart Schaefer
@ 2015-09-23  8:48   ` Peter Stephenson
  2015-09-23  9:55     ` Peter Stephenson
  2015-09-24  6:10     ` Bart Schaefer
  0 siblings, 2 replies; 6+ messages in thread
From: Peter Stephenson @ 2015-09-23  8:48 UTC (permalink / raw)
  To: Zsh Users

On Tue, 22 Sep 2015 21:07:56 -0700
Bart Schaefer <schaefer@brasslantern.com> wrote:
> On Sep 22,  8:42pm, Peter Stephenson wrote:
> } Subject: Dynamic directory name function
> }
> } For almost as long I've been meaning to turn this into a generic
> } function that you can configure just with a few variables.  I've finally
> } done that, and this has allowed me to add the completion support that
> } was missing before.
> 
> I have one immediate suggestion:  Use zstyle instead of variables.

I did think about that.  But having written the wrapper function, with
the variables the way they are, everything immediately fell out neatly
and clearly with associative arrays having a natural meaning.  zstyle
would just obscure the way the hierarchy was linked for no real gain ---
quite possibly to the point where I couldn't remember how to do it even
myself and wouldn't actually use it, in which case the whole thing would
be a waste of time.  (I can never remember how the more complicated
styles work in completion.)

But maybe you can come up with an example of how you'd set styles that
shows it working neatly and obviously.

pws


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Dynamic directory name function
  2015-09-23  8:48   ` Peter Stephenson
@ 2015-09-23  9:55     ` Peter Stephenson
  2015-09-24  6:10     ` Bart Schaefer
  1 sibling, 0 replies; 6+ messages in thread
From: Peter Stephenson @ 2015-09-23  9:55 UTC (permalink / raw)
  To: Zsh Users

On Wed, 23 Sep 2015 09:48:21 +0100
Peter Stephenson <p.stephenson@samsung.com> wrote:
> On Tue, 22 Sep 2015 21:07:56 -0700
> Bart Schaefer <schaefer@brasslantern.com> wrote:
> > On Sep 22,  8:42pm, Peter Stephenson wrote:
> > } Subject: Dynamic directory name function
> > }
> > } For almost as long I've been meaning to turn this into a generic
> > } function that you can configure just with a few variables.  I've finally
> > } done that, and this has allowed me to add the completion support that
> > } was missing before.
> > 
> > I have one immediate suggestion:  Use zstyle instead of variables.
> 
> I did think about that.  But having written the wrapper function, with
> the variables the way they are, everything immediately fell out neatly
> and clearly with associative arrays having a natural meaning.  zstyle
> would just obscure the way the hierarchy was linked for no real gain ---
> quite possibly to the point where I couldn't remember how to do it even
> myself and wouldn't actually use it, in which case the whole thing would
> be a waste of time.

However, it *is* very easy to set a zstyle for the top-level variable.
That's already good enough for you to keep all the definitions outside the
wrapper, if you want.  (I don't --- I like having them in one place.)

zmodload -i zsh/parameter
zstyle -s ":zdn:${funcstack[2]}:" mapping _zdn_topvar || _zdn_topvar=zdn_top

I've found a couple of bugs.  Probably a good idea if I stick this in
the distribution sooner rather than later to propagate fixes easily.
For record, the latest is below.

pws

## zsh_directory_name_generic
#
# This function is useful as a hook function for the zsh_directory_name
# facility.  It provides dynamic directory naming in both directions,
# i.e. from name to directory for use in ~[...] and from directory to
# name for use in prompts etc., as well as completion.
#
# 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:
#  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.
#  p - The name of a project within your git area.
#  s - The source area within that project.
# 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.
#
# To use it,
# - 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_wrapper.  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.
# - Arrange for the wrapper to be run as a zsh_directory_name hook:
#     autoload -Uz add-zsh-hook zsh_diretory_name_generic zdn_wrapper
#     add-zsh-hook -U zsh_directory_name zdn_wrapper
#
# Configuration:
#
# The wrapper function should define a local associative array zdn_top.
# Alternatively, this can be set with a style.  The context for the
# style is ':zdn:<wrapper-name>:' where <wrapper-name> is the function
# calling zsh_directory_name_generic.
#
# The keys in this 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 :default: specifies a variable in the form /: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 zdn_top have the same format as zdn_top.
#
# For 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 ~[g:...] or
# ~[ga:...] or ~[gs:...].  Later path components are optional; in that
# case ~[g] expands to ~/git, and so on.  gs expands to /scratch/$USER/git
# and uses the associative array second2 to match the second component;
# g and ga use the associative array second1 to match the second component.
#
# When expanding a name to a directory, if the first component is not g
# or ga or 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 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 second1:
#
#   local -A second1=(
#     p   myproject
#     s   somproject
#     os  otherproject/subproject/:third
#   )
#
# The path as found from zdn_top is extended with the matching directory,
# so ~[g:p] becomes ~/git/myproject.  The "/" between is added automatically
# (it's not possible to have a later component modify the name of a directory
# already matched).  Only "os" specifies a variable for a third component,
# and there's no :default:, so it's an error to use a name like ~[g:p:x]
# or ~[ga:s:y] because there's nowhere to look up the x or y.
#
# The associative arrays need to be visible within this function; the
# function therefore uses internal variable names beginning _zdn_ in
# order to avoid clashes.  Note that the variable "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.
#
# Here is a full fictitious but usable autoloadable definition of the
# example function defined by the code above.  So ~[gs:p:s] expands
# to /scratch/$USER/git/myscratchproject/top/srcdir.
#
#   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 -Uz zsh_directory_name_generic # paranoia in case we forgot
#   zsh_directory_name_generic "$@"

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
    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-- ))
    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


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Dynamic directory name function
  2015-09-23  8:48   ` Peter Stephenson
  2015-09-23  9:55     ` Peter Stephenson
@ 2015-09-24  6:10     ` Bart Schaefer
  2015-09-24  8:37       ` Peter Stephenson
  1 sibling, 1 reply; 6+ messages in thread
From: Bart Schaefer @ 2015-09-24  6:10 UTC (permalink / raw)
  To: Zsh Users

On Sep 23,  9:48am, Peter Stephenson wrote:
}
} I did think about that.  But having written the wrapper function, with
} the variables the way they are, everything immediately fell out neatly
} and clearly with associative arrays having a natural meaning.

As long as they're all local to the wrapper or use a naming convention
to avoid clashing with any other variables, I suppose that's fine.
Naming convention is mostly what zstyle is for.

The other useful thing about zstyle is that the naming convention
doesn't have to fit any other convention (such as, what is legal in
the name of an identifier).

} (I can never remember how the more complicated
} styles work in completion.)

As was observed in a thread some months ago, allowing wildcards to
match delimiters makes it possible to define zstyles that are shortcuts
but are also ambiguous.  At some level it might have made things more
understandable if, like slashes in globbing, we picked a delimiter and
always required the same number of them to appear in the definition
and the lookup (or at least required "**" for calling out ambiguity).

But that ship is already leaving the harbor if not past the horizon.

} But maybe you can come up with an example of how you'd set styles that
} shows it working neatly and obviously.

I'd probably have to understand dynamic directory naming to do a very
good job of that, and I've never had a need for that feature so I've
always sort of ignored it.  Partial path completion is sufficient.

However, as far as I understand zsh_directory_name_generic from looking
at the code and comments, you always start with the zdn_top hash, and
its values can contain substrings of the form ":identifier" where that
identifier then refers to another hash, and so on.

So if I've got ~[x:y], I have to find key "x" in zdn_top, and either
that has to have a ":identifier" in its value, or there has to be a
":default:" key with another ":identifier", and in either case the
hash referenced by that identifier has to have key "y", and so on.

Right so far?

Then as usual with zstyle you have to decide on what is context and
what is style.  I guess to follow your model closely, we should make
the identifier be the style, and the context is the the name of the
wrapper function.

So you'd define the styles like this:

    zstyle ZDN_wrapper_name zdn_top \
	g   ~/git \
	ga  ~/alternate/git \
	gs  /scratch/$USER/git/:second2 \
	:default: /:second1

    zstyle ZDN_wrapper_name second1 \
    	p  myproject \
	s  somproject \
	os otherproject/subproject/:third

    zstyle ZDN_wrapper_name third \
	s  top/srcdir \
	d  top/documentation

(rest omitted for brevity).  Everything works just like what you have
now, except you assign with

    zstyle -a ZDN_${funcstack[2]} $_zdn_var _zdn_assoc

in place of

    _zdn_assoc=(${(Pkv)_zdn_var})

I don't see we've lost any readability/remember-ability at this level,
but now those declarations can be global instead of local to each
wrapper, so we can have multiple wrappers share them by doing

    zstyle 'ZDN_*' third ...

The other thing that this points out is that the name is now the only
thing unique to the wrapper; we could do

    functions[wrapper_name]=$functions[zsh_directory_name_generic]

and it would all still work (well, except for having to adjust the
$funcstack[2] reference).  So maybe that's not the right context to
use in the first place.  Maybe we should be doing

    zstyle ZDN_/scratch/$USER/git/ next-part \
	p myscratchproject \
	s somescratchproject

and looking up with (if I'm reading your source correctly)

    if zstyle -a ZDN_${_zdn_sofar}/ next-part _zdn_assoc; then ...

and then you don't even need the :identifier thing to point to yet
another hash, you just either declare what the next parts are or
you don't.  There's probably some clean way to replace ":default:"
with a wildcard in the style pattern but the way you've set it up
I expect it would need it's own lookup, perhaps

    zstyle -a ZDN_${zdn_words[1]} default _zdn_assoc

which I think would need some restructuring of your loop; I don't
really have a good vision of that part.


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Dynamic directory name function
  2015-09-24  6:10     ` Bart Schaefer
@ 2015-09-24  8:37       ` Peter Stephenson
  0 siblings, 0 replies; 6+ messages in thread
From: Peter Stephenson @ 2015-09-24  8:37 UTC (permalink / raw)
  To: Zsh Users

On Wed, 23 Sep 2015 23:10:24 -0700
Bart Schaefer <schaefer@brasslantern.com> wrote:
> On Sep 23,  9:48am, Peter Stephenson wrote:
> }
> } I did think about that.  But having written the wrapper function, with
> } the variables the way they are, everything immediately fell out neatly
> } and clearly with associative arrays having a natural meaning.
> 
> As long as they're all local to the wrapper or use a naming convention
> to avoid clashing with any other variables, I suppose that's fine.
> Naming convention is mostly what zstyle is for.

That's probably what I'll go with, unless someone who is actually going
to use this really feels the need for styles.  But thanks for the
comments.

> However, as far as I understand zsh_directory_name_generic from looking
> at the code and comments, you always start with the zdn_top hash, and
> its values can contain substrings of the form ":identifier" where that
> identifier then refers to another hash, and so on.

Yes, although I've just modified it so you can start with a style to
find the top variable.  Given the variables can be linked how you like
(nothing needs to be defined locally), the difference between the two
isn't that great.

> So if I've got ~[x:y], I have to find key "x" in zdn_top, and either
> that has to have a ":identifier" in its value, or there has to be a
> ":default:" key with another ":identifier", and in either case the
> hash referenced by that identifier has to have key "y", and so on.
> 
> Right so far?

Yes.

> Then as usual with zstyle you have to decide on what is context and
> what is style.  I guess to follow your model closely, we should make
> the identifier be the style, and the context is the the name of the
> wrapper function.
> 
> So you'd define the styles like this:
> 
>     zstyle ZDN_wrapper_name zdn_top \
> 	g   ~/git \
> 	ga  ~/alternate/git \
> 	gs  /scratch/$USER/git/:second2 \
> 	:default: /:second1
> 
>     zstyle ZDN_wrapper_name second1 \
>     	p  myproject \
> 	s  somproject \
> 	os otherproject/subproject/:third
> 
>     zstyle ZDN_wrapper_name third \
> 	s  top/srcdir \
> 	d  top/documentation
> 
> (rest omitted for brevity).
>...
> I don't see we've lost any readability/remember-ability at this level,

OK, that's pretty similar.  There's the repetition of ZDN_wrapper_name,
but that's more to do with protecting the namespace (you'd need it as
the prefix to an associative array if you wanted to be careful, unless
you do what I do and define it locally).  There's also the backslashes,
but more careful people than me probably don't mind those.

> but now those declarations can be global instead of local to each
> wrapper, so we can have multiple wrappers share them by doing
> 
>     zstyle 'ZDN_*' third ...

Yes, but you can do that just by having them reference the same
global associative arrays.  With a zstyle pointing to the top
associative array you can again define everything outside the wrapper.

> Maybe we should be doing
> 
>     zstyle ZDN_/scratch/$USER/git/ next-part \
> 	p myscratchproject \
> 	s somescratchproject

This is now at the level where if somebody put it in front of me I'd
find it too fiddly to use.

I think, really, it's six and two threes (or six of one and half a dozen
of the other, as the wordier people born south of Tyneside say).  zstyle
gives you a few extra tricks if you can probe the obscurity, although
obscurity is an entirely subjective concept.  Variables allow you to
define the whole thing inside a single function with no globals the way
I originally did, which doesn't really fit the zstyle model, and I would
think anyone who knows what an associative array is can pick it up the
entire system very quickly.

However, it was certainly useful to compare.

pws


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2015-09-24  8:38 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-09-22 19:42 Dynamic directory name function Peter Stephenson
2015-09-23  4:07 ` Bart Schaefer
2015-09-23  8:48   ` Peter Stephenson
2015-09-23  9:55     ` Peter Stephenson
2015-09-24  6:10     ` Bart Schaefer
2015-09-24  8:37       ` Peter Stephenson

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).