summaryrefslogtreecommitdiffstats
path: root/Functions/Misc
diff options
context:
space:
mode:
Diffstat (limited to 'Functions/Misc')
-rw-r--r--Functions/Misc/is-at-least87
-rw-r--r--Functions/Misc/regexp-replace125
-rwxr-xr-xFunctions/Misc/zgetopt181
-rw-r--r--Functions/Misc/zkbd10
-rw-r--r--Functions/Misc/zmv2
5 files changed, 295 insertions, 110 deletions
diff --git a/Functions/Misc/is-at-least b/Functions/Misc/is-at-least
index 5985684be..954990dcf 100644
--- a/Functions/Misc/is-at-least
+++ b/Functions/Misc/is-at-least
@@ -1,62 +1,53 @@
#
-# Test whether $ZSH_VERSION (or some value of your choice, if a second argument
-# is provided) is greater than or equal to x.y.z-r (in argument one). In fact,
-# it'll accept any dot/dash-separated string of numbers as its second argument
-# and compare it to the dot/dash-separated first argument. Leading non-number
-# parts of a segment (such as the "zefram" in 3.1.2-zefram4) are not considered
-# when the comparison is done; only the numbers matter. Any left-out segments
-# in the first argument that are present in the version string compared are
-# considered as zeroes, eg 3 == 3.0 == 3.0.0 == 3.0.0.0 and so on.
+# is-at-least: compare two version strings
#
-# Usage examples:
-# is-at-least 3.1.6-15 && setopt NO_GLOBAL_RCS
-# is-at-least 3.1.0 && setopt HIST_REDUCE_BLANKS
-# is-at-least 586 $MACHTYPE && echo 'You could be running Mandrake!'
-# is-at-least $ZSH_VERSION || print 'Something fishy here.'
+# Usage: is-at-least minimum_version [current_version]
+#
+# Returns true if current_version is equal to or newer than minimum_version.
+# If current_version is omitted, $ZSH_VERSION is used.
+# {minimum,current}_version is a string consisting of a few 'segments'
+# joined by either '.' or '-'. For example:
+# 1.2-test 3.1.2-zefram4
+# 4.4.5beta23 12.34-patch23a
+#
+# A segment with no digits, such as 'test' above, is ignored.
+#
+# Two segments are compared numerically if the first differing characters
+# are both digits, and lexically otherwise.
+#
+# If number of segments in the two version strings differ, missing
+# segments are assumed to be '0'. This means
+# 5.9, 5.9.0, 5.9.0.0, 5.9-test
+# are all considered to be the same version.
#
-# Note that segments that contain no digits at all are ignored, and leading
-# text is discarded if trailing digits follow, because this was the meaning
-# of certain zsh version strings in the early 2000s. Other segments that
-# begin with digits are compared using NUMERIC_GLOB_SORT semantics, and any
-# other segments starting with text are compared lexically.
-
emulate -L zsh
+setopt extended_glob
-local IFS=".-" min_cnt=0 ver_cnt=0 part min_ver version order
+local IFS=".-" min_ver cur_ver n order
: ${2=$ZSH_VERSION}
# sort out the easy corner cases first
-[[ $1 = $2 ]] && return 0 # same version
-[[ -n $2 ]] || return 1 # no version
+[[ -z $1 || -z $2 ]] && return 1 # no version
+[[ $1 = $2 ]] && return 0 # same version
+# split into segments
min_ver=(${=1})
-version=(${=2} 0)
+cur_ver=(${=2})
-while (( $min_cnt <= ${#min_ver} )); do
- while [[ "$part" != <-> ]]; do
- (( ++ver_cnt > ${#version} )) && return 0
- if [[ ${version[ver_cnt]} = *[0-9][^0-9]* ]]; then
- # Contains a number followed by text. Not a zsh version string.
- order=( ${version[ver_cnt]} ${min_ver[ver_cnt]} )
- if [[ ${version[ver_cnt]} = <->* ]]; then
- # Leading digits, compare by sorting with numeric order.
- [[ $order != ${${(On)order}} ]] && return 1
- else
- # No leading digits, compare by sorting in lexical order.
- [[ $order != ${${(O)order}} ]] && return 1
- fi
- [[ $order[1] != $order[2] ]] && return 0
- fi
- part=${version[ver_cnt]##*[^0-9]}
- done
+# remove segments containing no digits
+min_ver=( ${min_ver:#[^0-9]##} )
+cur_ver=( ${cur_ver:#[^0-9]##} )
+(( $#min_ver == 0 || $#cur_ver == 0 )) && return 1
- while true; do
- (( ++min_cnt > ${#min_ver} )) && return 0
- [[ ${min_ver[min_cnt]} = <-> ]] && break
- done
-
- (( part > min_ver[min_cnt] )) && return 0
- (( part < min_ver[min_cnt] )) && return 1
- part=''
+# find the first segment that differs
+for (( n = 1; n <= $#min_ver; ++n )) do
+ [[ ${min_ver[n]} != ${cur_ver[n]-0} ]] && break
done
+
+# if all segments in $min_ver are equal to those in $cur_ver
+(( n > $#min_ver )) && return 0
+
+# now compare the two segments that differ
+order=( ${cur_ver[n]-0} ${min_ver[n]-0} )
+[[ $order != ${${(On)order}} ]] && return 1 || return 0
diff --git a/Functions/Misc/regexp-replace b/Functions/Misc/regexp-replace
index d4408f0f7..86b28c5aa 100644
--- a/Functions/Misc/regexp-replace
+++ b/Functions/Misc/regexp-replace
@@ -1,91 +1,98 @@
-# Replace all occurrences of a regular expression in a variable. The
-# variable is modified directly. Respects the setting of the
-# option RE_MATCH_PCRE.
+# Replace all occurrences of a regular expression in a scalar variable.
+# The variable is modified directly. Respects the setting of the option
+# RE_MATCH_PCRE, but otherwise sets the zsh emulation mode.
#
-# First argument: *name* (not contents) of variable.
-# Second argument: regular expression
-# Third argument: replacement string. This can contain all forms of
-# $ and backtick substitutions; in particular, $MATCH will be replaced
-# by the portion of the string matched by the regular expression.
+# Arguments:
+#
+# 1. *name* (not contents) of variable or more generally any lvalue;
+# expected to be scalar.
+#
+# 2. regular expression
+#
+# 3. replacement string. This can contain all forms of
+# $ and backtick substitutions; in particular, $MATCH will be
+# replaced by the portion of the string matched by the regular
+# expression. Parsing errors are fatal to the shell process.
-# we use positional parameters instead of variables to avoid
-# clashing with the user's variable. Make sure we start with 3 and only
-# 3 elements:
-argv=("$1" "$2" "$3")
+if (( $# < 2 || $# > 3 )); then
+ print -ru2 "Usage: $0 <varname> <regexp> [<replacement>]"
+ return 2
+fi
-# $4 records whether pcre is enabled as that information would otherwise
-# be lost after emulate -L zsh
-4=0
-[[ -o re_match_pcre ]] && 4=1
+local _regexp_replace_use_pcre=0
+[[ -o re_match_pcre ]] && _regexp_replace_use_pcre=1
emulate -L zsh
+local _regexp_replace_subject=${(P)1} \
+ _regexp_replace_regexp=$2 \
+ _regexp_replace_replacement=$3 \
+ _regexp_replace_result \
+ MATCH MBEGIN MEND
-local MATCH MBEGIN MEND
local -a match mbegin mend
-if (( $4 )); then
+if (( _regexp_replace_use_pcre )); then
# if using pcre, we're using pcre_match and a running offset
# That's needed for ^, \A, \b, and look-behind operators to work
# properly.
zmodload zsh/pcre || return 2
- pcre_compile -- "$2" && pcre_study || return 2
+ pcre_compile -- "$_regexp_replace_regexp" && pcre_study || return 2
- # $4 is the current *byte* offset, $5, $6 reserved for later use
- 4=0 6=
+ local _regexp_replace_offset=0 _regexp_replace_start _regexp_replace_stop _regexp_replace_new ZPCRE_OP
+ local -a _regexp_replace_finds
- local ZPCRE_OP
- while pcre_match -b -n $4 -- "${(P)1}"; do
- # append offsets and computed replacement to the array
- # we need to perform the evaluation in a scalar assignment so that if
- # it generates an array, the elements are converted to string (by
+ while pcre_match -b -n $_regexp_replace_offset -- "$_regexp_replace_subject"; do
+ # we need to perform the evaluation in a scalar assignment so that
+ # if it generates an array, the elements are converted to string (by
# joining with the first character of $IFS as usual)
- 5=${(e)3}
- argv+=(${(s: :)ZPCRE_OP} "$5")
+ _regexp_replace_new=${(Xe)_regexp_replace_replacement}
+
+ _regexp_replace_finds+=( ${(s[ ])ZPCRE_OP} "$_regexp_replace_new" )
# for 0-width matches, increase offset by 1 to avoid
# infinite loop
- 4=$((argv[-2] + (argv[-3] == argv[-2])))
+ (( _regexp_replace_offset = _regexp_replace_finds[-2] + (_regexp_replace_finds[-3] == _regexp_replace_finds[-2]) ))
done
- (($# > 6)) || return # no match
+ (( $#_regexp_replace_finds )) || return # no match
- set +o multibyte
+ unsetopt multibyte
- # $5 contains the result, $6 the current offset
- 5= 6=1
- for 2 3 4 in "$@[7,-1]"; do
- 5+=${(P)1[$6,$2]}$4
- 6=$(($3 + 1))
+ _regexp_replace_offset=1
+ for _regexp_replace_start _regexp_replace_stop _regexp_replace_new in "$_regexp_replace_finds[@]"; do
+ _regexp_replace_result+=${_regexp_replace_subject[_regexp_replace_offset,_regexp_replace_start]}$_regexp_replace_new
+ (( _regexp_replace_offset = _regexp_replace_stop + 1 ))
done
- 5+=${(P)1[$6,-1]}
-else
+ _regexp_replace_result+=${_regexp_replace_subject[_regexp_replace_offset,-1]}
+
+else # no PCRE
# in ERE, we can't use an offset so ^, (and \<, \b, \B, [[:<:]] where
# available) won't work properly.
- # $4 is the string to be matched
- 4=${(P)1}
-
- while [[ -n $4 ]]; do
- if [[ $4 =~ $2 ]]; then
- # append initial part and substituted match
- 5+=${4[1,MBEGIN-1]}${(e)3}
- # truncate remaining string
- if ((MEND < MBEGIN)); then
- # zero-width match, skip one character for the next match
- ((MEND++))
- 5+=${4[1]}
- fi
- 4=${4[MEND+1,-1]}
- # indicate we did something
- 6=1
- else
- break
+ local _regexp_replace_ok=0
+ while [[ $_regexp_replace_subject =~ $_regexp_replace_regexp ]]; do
+ # append initial part and substituted match
+ _regexp_replace_result+=$_regexp_replace_subject[1,MBEGIN-1]${(Xe)_regexp_replace_replacement}
+ # truncate remaining string
+ if (( MEND < MBEGIN )); then
+ # zero-width match, skip one character for the next match
+ (( MEND++ ))
+ _regexp_replace_result+=$_regexp_replace_subject[MBEGIN]
fi
+ _regexp_replace_subject=$_regexp_replace_subject[MEND+1,-1]
+ _regexp_replace_ok=1
+ [[ -z $_regexp_replace_subject ]] && break
done
- [[ -n $6 ]] || return # no match
- 5+=$4
+ (( _regexp_replace_ok )) || return
+ _regexp_replace_result+=$_regexp_replace_subject
fi
-eval $1=\$5
+# assign result to target variable if at least one substitution was
+# made. At this point, if the variable was originally array or assoc, it
+# is converted to scalar. If $1 doesn't contain a valid lvalue
+# specification, an exception is raised (exits the shell process if
+# non-interactive).
+: ${(P)1::="$_regexp_replace_result"}
+
diff --git a/Functions/Misc/zgetopt b/Functions/Misc/zgetopt
new file mode 100755
index 000000000..4d5481abd
--- /dev/null
+++ b/Functions/Misc/zgetopt
@@ -0,0 +1,181 @@
+#!/bin/zsh -f
+
+# wrapper around zparseopts which gives it an interface similar to util-linux's
+# getopt(1). see zshcontrib(1) for documentation
+
+emulate -L zsh -o extended_glob
+zmodload -i zsh/zutil || return 3
+
+local func=${funcstack[1]:-${0:t}}
+local caller=${funcstack[2]:-${ZSH_ARGZERO:t}}
+local errname=$caller:$func
+local optspec pat i posix=0
+local -a match mbegin mend optvv argvv
+local -a array lopts sopts name
+local -a specs no_arg_opts req_arg_opts opt_arg_opts tmp
+
+[[ $zsh_eval_context[-1] == toplevel ]] && errname=$func
+
+# same as leading + in short-opts spec
+(( $+POSIXLY_CORRECT )) && posix=1
+
+zparseopts -n $errname -D -F -G - \
+ {A,-array}:-=array \
+ {l,-longoptions,-long-options}+:-=lopts \
+ {n,-name}:-=name \
+ {o,-options}:-=sopts \
+|| {
+ print -ru2 "usage: $func [-A <array>] [-l <spec>] [-n <name>] [-o <spec>] -- <arg> ..."
+ return 2
+}
+
+name=( ${(@)name/#(-n|--name=)/} )
+[[ -n $name ]] || name=( $caller )
+
+(( $#array )) && array=( "${(@)array/#(-A|--array=)/}" )
+
+if [[ $zsh_eval_context[-1] != toplevel ]]; then
+ [[ $array == *[^A-Za-z0-9_.]* ]] && {
+ print -ru2 - "$errname: invalid array name: $array"
+ return 2
+ }
+ (( $#array )) || array=( argv )
+
+elif [[ -n $array ]]; then
+ print -ru2 - "$errname: -A option not meaningful unless called as function"
+ return 2
+fi
+
+# getopt requires a short option spec; we'll require either short or long
+(( $#sopts || $#lopts )) || {
+ print -ru2 - "$errname: missing option spec"
+ return 2
+}
+
+optspec=${(@)sopts/#(-o|--options=)/}
+sopts=( )
+
+for (( i = 1; i <= $#optspec; i++ )); do
+ # leading '+': act POSIXLY_CORRECT
+ if [[ $i == 1 && $optspec[i] == + ]]; then
+ posix=1
+ # leading '-': should leave operands interspersed with options, but this is
+ # not really possible with zparseopts
+ elif [[ $i == 1 && $optspec[i] == - ]]; then
+ print -ru2 - "$errname: optspec with leading - (disable operand collection) not supported"
+ return 2
+ # special characters: [+=\\] because they're special to zparseopts, ':'
+ # because it's special to getopt, '-' because it's the parsing terminator
+ elif [[ $optspec[i] == [+:=\\-] ]]; then
+ print -ru2 - "$errname: invalid short-option name: $optspec[i]"
+ return 2
+ # 'a'
+ elif [[ $optspec[i+1] != : ]]; then
+ sopts+=( $optspec[i] )
+ # 'a:'
+ elif [[ $optspec[i+2] != : ]]; then
+ sopts+=( $optspec[i]: )
+ (( i += 1 ))
+ # 'a::'
+ elif [[ $optspec[i+3] != : ]]; then
+ sopts+=( $optspec[i]:: )
+ (( i += 2 ))
+ fi
+done
+
+lopts=( ${(@)lopts/#(-l|--long(|-)options=)/} )
+lopts=( ${(@s<,>)lopts} )
+
+# don't allow characters that are special to zparseopts in long-option specs.
+# see above
+pat='(*[+=\\]*|:*|*:::##|*:[^:]*)'
+[[ -n ${(@M)lopts:#$~pat} ]] && {
+ print -ru2 - "$errname: invalid long-option spec: ${${(@M)lopts:#$~pat}[1]}"
+ return 2
+}
+
+lopts=( ${(@)lopts/#/-} )
+specs=( $sopts $lopts )
+
+# used below to identify options with optional optargs
+no_arg_opts=( ${(@)${(@M)specs:#*[^:]}/#/-} )
+req_arg_opts=( ${(@)${(@)${(@M)specs:#*[^:]:}/#/-}/%:#} )
+opt_arg_opts=( ${(@)${(@)${(@M)specs:#*::}/#/-}/%:#} )
+
+# getopt returns all instances of each option given, so add +
+specs=( ${(@)specs/%(#b)(:#)/+$match[1]} )
+
+# if we've got nothing now, the user probably gave -o '' or sth. pass an
+# explicit '' to zparseopts
+(( $#specs )) || specs=( '' )
+
+# POSIXLY_CORRECT: stop parsing options after first non-option argument
+if (( posix )); then
+ argvv=( "$@" )
+ zparseopts -n $name -a optvv -v argvv -D -F -G - "${(@)specs}" || return 1
+
+# default: permute options following non-option arguments
+else
+ zparseopts -n $name -a optvv -D -E -F -G - "${(@)specs}" || return 1
+ # -D + -E leaves an explicit -- in argv where-ever it might appear
+ local seen
+ while (( $# )); do
+ [[ -z $seen && $1 == -- ]] && seen=1 && shift && continue
+ argvv+=( "$1" )
+ shift
+ done
+fi
+
+# getopt outputs all optargs as separate parameters, even missing optional ones,
+# so we scan through and add/separate those if needed
+(( $#opt_arg_opts )) && {
+ local cur next
+ local -a old_optvv=( "${(@)optvv}" )
+ optvv=( )
+
+ for (( i = 1; i <= $#old_optvv; i++ )); do
+ cur=$old_optvv[i]
+ next=$old_optvv[i+1]
+ # option with no optarg
+ if [[ -n ${no_arg_opts[(r)$cur]} ]]; then
+ optvv+=( $cur )
+ # option with required optarg -- will appear in next element
+ elif [[ -n ${req_arg_opts[(r)$cur]} ]]; then
+ optvv+=( $cur "$next" )
+ (( i++ ))
+ # long option with optional optarg -- will appear in same element delimited
+ # by = (even if missing)
+ elif [[ $cur == *=* && -n ${opt_arg_opts[(r)${cur%%=*}]} ]]; then
+ optvv+=( ${cur%%=*} "${cur#*=}" )
+ # short option with optional optarg -- will appear in same element with no
+ # delimiter (thus the option appears alone if the optarg is missing)
+ elif [[ -n ${opt_arg_opts[(r)${(M)cur#-?}]} ]]; then
+ optvv+=( ${(M)cur#-?} "${cur#-?}" )
+ # ???
+ else
+ print -ru2 - "$errname: parse error, please report!"
+ print -ru2 - "$errname: specs: ${(j< >)${(@q+)specs}}"
+ print -ru2 - "$errname: old_optvv: ${(j< >)${(@q+)old_optvv}}"
+ print -ru2 - "$errname: cur: $cur"
+ optvv+=( $cur ) # i guess?
+ fi
+ done
+}
+
+# called as function, assign to array. use EXIT trap to assign in caller's
+# context
+if [[ -n $array ]]; then
+ trap "$array=( ${(j< >)${(@q+)optvv}} -- ${(j< >)${(@q+)argvv}} )" EXIT
+
+# called as function, print
+elif [[ $zsh_eval_context[-1] != toplevel ]]; then
+ print -r - "${(@q+)optvv}" -- "${(@q+)argvv}"
+
+# called as a script, print. use unconditional single-quoting. this is ugly but
+# it's the closest to what getopt does and it offers compatibility with legacy
+# shells
+else
+ print -r - "${(@qq)optvv}" -- "${(@qq)argvv}"
+fi
+
+return 0
diff --git a/Functions/Misc/zkbd b/Functions/Misc/zkbd
index 1065a84f1..493cc65ea 100644
--- a/Functions/Misc/zkbd
+++ b/Functions/Misc/zkbd
@@ -9,9 +9,15 @@
}
emulate -RL zsh
-local zkbd term key seq
+local zkbd override term key seq
-zkbd=${ZDOTDIR:-$HOME}/.zkbd
+zparseopts -D -F -- d:=override || return 1
+
+if (( $#override )); then
+ zkbd=$override[2]
+else
+ zkbd=${ZDOTDIR:-$HOME}/.zkbd
+fi
[[ -d $zkbd ]] || mkdir $zkbd || return 1
trap 'unfunction getmbkey getseq; command rm -f $zkbd/$TERM.tmp' 0
diff --git a/Functions/Misc/zmv b/Functions/Misc/zmv
index 2002af5a6..ed4868346 100644
--- a/Functions/Misc/zmv
+++ b/Functions/Misc/zmv
@@ -183,7 +183,7 @@ repl=$2
shift 2
if [[ -n $opt_s && $action != ln ]]; then
- print -r -- "$myname: invalid option: -s" >&2
+ print -r -- "$myname: bad option: -s" >&2
return 1
fi