Update _beet; incremental caching

Lots of changes to implement incremental caching scheme.
This commit is contained in:
vapniks 2018-05-09 21:25:40 +01:00 committed by GitHub
parent 63abe83a01
commit 7e0fbef9fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -2,51 +2,106 @@
# zsh completion for beets music library manager and MusicBrainz tagger: http://beets.radbox.org/
# Cache will be updated if it is older than the beets database or binary.
# Need to set BEETS_LIBRARY to some preliminary value since it is used by the cache checking function.
typeset -g BEETS_LIBRARY=~/.config/beets/library.db
# Default values for BEETS_LIBRARY & BEETS_CONFIG needed for the cache checking function.
# They will be updated under the assumption that the config file is in the same directory as the library.
local BEETS_LIBRARY=~/.config/beets/library.db
local BEETS_CONFIG=~/.config/beets/config.yaml
# Use separate caches for file locations, command completions, and query completions.
# This allows the use of different rules for when to update each one.
zstyle ":completion:${curcontext}:" cache-policy _beet_check_cache
_beet_check_cache () {
[[ ! -a "${1}" ]] || [[ ! -a ${~BEETS_LIBRARY} ]] || [[ "${1}" -ot ${~BEETS_LIBRARY} ]] || [[ "${1}" -ot =beet ]]
local cachefile="$(basename ${1})"
if [[ ! -a "${1}" ]] || [[ "${1}" -ot =beet ]]; then
# always update the cache if it doesnt exist, or if the beet executable changes
return 0
fi
case cachefile; in
(beetslibrary)
if [[ ! -a "${~BEETS_LIBRARY}" ]] || [[ "${1}" -ot "${~BEETS_CONFIG}" ]]; then
return 0
fi
;;
(beetscmds)
_retrieve_cache beetslibrary
if [[ "${1}" -ot "${~BEETS_CONFIG}" ]]; then
return 0
fi
;;
esac
return 1
}
# Try to retrieve the cache, and find out if it needs to be updated
if ! _retrieve_cache beets || _cache_invalid beets; then
local updatecache=1
# Location of database
typeset -g BEETS_LIBRARY="$(beet config|grep library|cut -f 2 -d ' ')"
# List of all fields
local -a fields
fields=(`beet fields | grep -G '^ ' | sort -u | colrm 1 2`)
fi
# useful: argument to _regex_arguments for matching any word
local matchany=/$'[^\0]##\0'/
# arguments to _regex_arguments for completing files and directories
local -a files dirs
files=("$matchany" ':file:file:_files')
dirs=("$matchany" ':dir:directory:_dirs')
# Deal with completions for querying and modifying fields..
local fieldargs matchquery matchmodify
# Retrieve or update caches
if ! _retrieve_cache beetslibrary || _cache_invalid beetslibrary; then
local BEETS_LIBRARY="${$(beet config|grep library|cut -f 2 -d ' '):-${BEETS_LIBRARY}}"
local BEETS_CONFIG="$(dirname ${BEETS_LIBRARY})/config.yaml"
_store_cache beetslibrary BEETS_LIBRARY BEETS_CONFIG
fi
# regexps for matching query and modify terms on the command line
matchquery=/"(${(j/|/)fields[@]})"$':[^\0]##\0'/
matchmodify=/"(${(j/|/)fields[@]})"$'(=[^\0]##|!)\0'/
# Function for joining grouped lines of output into single lines (taken from _completion_helpers)
function _join_lines() {
awk -v SEP="$1" -v ARG2="$2" -v START="$3" -v END2="$4" 'BEGIN {if(START==""){f=1}{f=0};
if ! _retrieve_cache beetscmds || _cache_invalid beetscmds; then
local -a subcommands fields beets_regex_words_subcmds beets_regex_words_help query modify
local subcmd cmddesc matchquery matchmodify field fieldargs queryelem modifyelem
# Useful function for joining grouped lines of output into single lines (taken from _completion_helpers)
_join_lines() {
awk -v SEP="$1" -v ARG2="$2" -v START="$3" -v END2="$4" 'BEGIN {if(START==""){f=1}{f=0};
if(ARG2 ~ "^[0-9]+"){LINE1 = "^[[:space:]]{,"ARG2"}[^[:space:]]"}else{LINE1 = ARG2}}
($0 ~ END2 && f>0 && END2!="") {exit}
($0 ~ START && f<1) {f=1; if(length(START)!=0){next}}
($0 ~ LINE1 && f>0) {if(f<2){f=2; printf("%s",$0)}else{printf("\n%s",$0)}; next}
(f>1) {gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0); printf("%s%s",SEP, $0); next}
END {print ""}'
}
}
# Variables used for completing subcommands and queries
subcommands=(${${(f)"$(beet help | _join_lines ' ' 3 'Commands:')"}[@]})
fields=($(beet fields | grep -G '^ ' | sort -u | colrm 1 2))
for field in "${fields[@]}"
do
fieldargs="$fieldargs '$field:::{_beet_field_values $field}'"
done
queryelem="_values -S : 'query field (add an extra : to match by regexp)' '::' $fieldargs"
modifyelem="_values -S = 'modify field (replace = with ! to remove field)' $(echo "'${^fields[@]}:: '")"
# regexps for matching query and modify terms on the command line
matchquery=/"(${(j/|/)fields[@]})"$':[^\0]##\0'/
matchmodify=/"(${(j/|/)fields[@]})"$'(=[^\0]##|!)\0'/
# create completion function for queries
_regex_arguments _beet_query "$matchany" \# \( "$matchquery" ":query:query string:$queryelem" \) \#
local "beets_query"="$(which _beet_query)"
# arguments for _regex_arguments for completing lists of queries and modifications
beets_query_args=( \( "$matchquery" ":query:query string:{_beet_query}" \) \# )
beets_modify_args=( \( "$matchmodify" ":modify:modify string:$modifyelem" \) \# )
# now build arguments for _beet and _beet_help completion functions
beets_regex_words_subcmds=('(')
for i in ${subcommands[@]}; do
subcmd="${i[(w)1]}"
# remove first word and parenthesised alias, replace : with -, [ with (, ] with ), and remove single quotes
cmddesc="${${${${${i[(w)2,-1]##\(*\) #}//:/-}//\[/(}//\]/)}//\'/}"
# update arguments needed for creating _beet
beets_regex_words_subcmds+=(/"${subcmd}"$'\0'/ ":subcmds:subcommands:((${subcmd}:${cmddesc// /\ }))")
beets_regex_words_subcmds+=(\( "${matchany}" ":option:option:{_beet_subcmd ${subcmd}}" \) \# \|)
# update arguments needed for creating _beet_help
beets_regex_words_help+=("${subcmd}:${cmddesc}")
done
beets_regex_words_subcmds[-1]=')'
_store_cache beetscmds beets_regex_words_subcmds beets_regex_words_help beets_query_args beets_modify_args beets_query
else
# Evaluate the variable containing the query completer function
eval "${beets_query}"
fi
# Function for getting unique values for field from database (you may need to change the path to the database).
function _beet_field_values()
{
_beet_field_values() {
local -a output fieldvals
local sqlcmd="select distinct $1 from items;"
case $1
in
_retrieve_cache beetslibrary
case ${1}
in
lyrics)
fieldvals=
;;
@ -60,73 +115,12 @@ function _beet_field_values()
esac
compadd -P \" -S \" -M 'm:{[:lower:][:upper:]}={[:upper:][:lower:]}' -Q -a fieldvals
}
# store call to _values function for completing query terms
# (first build arguments for completing field values)
local field
for field in "${fields[@]}"
do
fieldargs="$fieldargs '$field:::{_beet_field_values $field}'"
done
local queryelem modifyelem
queryelem="_values -S : 'query field (add an extra : to match by regexp)' '::' $fieldargs"
# store call to _values function for completing modify terms (no need to complete field values)
modifyelem="_values -S = 'modify field (replace = with ! to remove field)' $(echo "'${^fields[@]}:: '")"
# Create completion function for queries
_regex_arguments _beet_query "$matchany" \# \( "$matchquery" ":query:query string:$queryelem" \) \( "$matchquery" ":query:query string:$queryelem" \) \#
# store regexps for completing lists of queries and modifications
local -a query modify
query=( \( "$matchquery" ":query:query string:{_beet_query}" \) \( "$matchquery" ":query:query string:{_beet_query}" \) \# )
modify=( \( "$matchmodify" ":modify:modify string:$modifyelem" \) \( "$matchmodify" ":modify:modify string:$modifyelem" \) \# )
# arguments to _regex_arguments for completing files and directories
local -a files dirs
files=("$matchany" ':file:file:_files')
dirs=("$matchany" ':dir:directory:_dirs')
# Individual options used by subcommands, and global options (must be single quoted).
# Its much faster if these are hard-coded rather generated using _beet_subcmd_options
local helpopt formatopt albumopt dontmoveopt writeopt nowriteopt pretendopt pathopt destopt copyopt nocopyopt
local inferopt noinferopt resumeopt noresumeopt nopromptopt logopt individualopt confirmopt retagopt skipopt noskipopt
local flatopt groupopt editopt defaultopt noconfirmopt exactopt removeopt configopt debugopt
helpopt='-h:show this help message and exit'
formatopt='-f:print with custom format:$matchany'
albumopt='-a:match albums instead of tracks'
dontmoveopt='-M:dont move files in library'
writeopt='-w:write new metadata to files tags (default)'
nowriteopt='-W:dont write metadata (opposite of -w)'
pretendopt='-p:show all changes but do nothing'
pathopt='-p:print paths for matched items or albums'
destopt='-d:destination music directory:$dirs'
copyopt='-c:copy tracks into library directory (default)'
nocopyopt='-C:dont copy tracks (opposite of -c)'
inferopt='-a:infer tags for imported files (default)'
noinferopt='-A:dont infer tags for imported files (opposite of -a)'
resumeopt='-p:resume importing if interrupted'
noresumeopt='-P:do not try to resume importing'
nopromptopt='-q:never prompt for input, skip albums instead'
logopt='-l:file to log untaggable albums for later review:$files'
individualopt='-s:import individual tracks instead of full albums'
confirmopt='-t:always confirm all actions'
retagopt='-L:retag items matching a query:${query[@]}'
skipopt='-i:skip already-imported directories'
noskipopt='-I:do not skip already-imported directories'
flatopt='--flat:import an entire tree as a single album'
groupopt='-g:group tracks in a folder into separate albums'
editopt='-e:edit user configuration with $EDITOR'
defaultopt='-d:include the default configuration'
copynomoveopt='-c:copy instead of moving'
noconfirmopt='-y:skip confirmation'
exactopt='-e:get exact file sizes'
removeopt='-d:also remove files from disk'
configopt='-c:path to configuration file:$files'
debugopt='-v:print debugging information'
libopt='-l:library database file to use:$files'
# This function takes a beet subcommand as its first argument, and then uses _regex_words to set ${reply[@]}
# to an array containing arguments for the _regex_arguments function.
function _beet_subcmd_options()
{
_beet_subcmd_options() {
local shortopt optarg optdesc
local matchany=/$'[^\0]##\0'/
local -a regex_words
regex_words=()
for i in ${${(f)"$(beet help $1 | awk '/^ +-/{if(x)print x;x=$0;next}/^ *$/{if(x) exit}{if(x) x=x$0}END{print x}')"}[@]}
@ -134,17 +128,18 @@ function _beet_subcmd_options()
opt="${i[(w)1]/,/}"
optarg="${${${i## #[-a-zA-Z]# }##[- ]##*}%%[, ]*}"
optdesc="${${${${${i[(w)2,-1]/[A-Z, ]#--[-a-z]##[=A-Z]# #/}//:/-}//\[/(}//\]/)}//\'/}"
case $optarg
in
case $optarg; in
("")
if [[ "$1" == "import" && "$opt" == "-L" ]]; then
regex_words+=("$opt:$optdesc:\${query[@]}")
regex_words+=("$opt:$optdesc:\${beets_query_args[@]}")
else
regex_words+=("$opt:$optdesc")
fi
;;
(LOG)
regex_words+=("$opt:$optdesc:\$files")
local -a files
files=("$matchany" ':file:file:_files')
regex_words+=("$opt:$optdesc:\$files")
;;
(CONFIG)
local -a configfile
@ -157,10 +152,12 @@ function _beet_subcmd_options()
regex_words+=("$opt:$optdesc:\$libfile")
;;
(DIR|DIRECTORY)
local -a dirs
dirs=("$matchany" ':dir:directory:_dirs')
regex_words+=("$opt:$optdesc:\$dirs")
;;
(SOURCE)
if [[ $1 -eq lastgenre ]]; then
if [[ "${1}" -eq lastgenre ]]; then
local -a lastgenresource
lastgenresource=(/$'(artist|album|track)\0'/ ':source:genre source:(artist album track)')
regex_words+=("$opt:$optdesc:\$lastgenresource")
@ -176,93 +173,58 @@ function _beet_subcmd_options()
_regex_words options "$1 options" "${regex_words[@]}"
}
# Now build the arguments to _regex_arguments for each subcommand.
if [[ -n $updatecache ]]; then
local -a options regex_words_subcmds regex_words_help
local subcmd cmddesc
for i in ${${(f)"$(beet help | _join_lines ' ' 3 'Commands:')"[@]}[@]}
do
subcmd="${i[(w)1]}"
# remove first word and parenthesised alias, replace : with -, [ with (, ] with ), and remove single quotes
cmddesc="${${${${${i[(w)2,-1]##\(*\) #}//:/-}//\[/(}//\]/)}//\'/}"
case $subcmd
in
(config)
_regex_words options "config options" "$helpopt" "$pathopt" "$editopt" "$defaultopt"
options=("${reply[@]}")
;;
(import)
_regex_words options "import options" "$helpopt" "$writeopt" "$nowriteopt" "$copyopt" "$nocopyopt"\
"$inferopt" "$noinferopt" "$resumeopt" "$noresumeopt" "$nopromptopt" "$logopt" "$individualopt" "$confirmopt"\
"$retagopt" "$skipopt" "$noskipopt" "$flatopt" "$groupopt"
options=( "${reply[@]}" \# "${files[@]}" \# )
;;
(list)
_regex_words options "list options" "$helpopt" "$pathopt" "$albumopt" "$formatopt"
options=( "$reply[@]" \# "${query[@]}" )
;;
(modify)
_regex_words options "modify options" "$helpopt" "$dontmoveopt" "$writeopt" "$nowriteopt" "$albumopt" \
"$noconfirmopt" "$formatopt"
options=( "${reply[@]}" \# "${query[@]}" "${modify[@]}" )
;;
(move)
_regex_words options "move options" "$helpopt" "$albumopt" "$destopt" "$copynomoveopt"
options=( "${reply[@]}" \# "${query[@]}")
;;
(remove)
_regex_words options "remove options" "$helpopt" "$albumopt" "$removeopt"
options=( "${reply[@]}" \# "${query[@]}" )
;;
(stats)
_regex_words options "stats options" "$helpopt" "$exactopt"
options=( "${reply[@]}" \# "${query[@]}" )
;;
(update)
_regex_words options "update options" "$helpopt" "$albumopt" "$dontmoveopt" "$pretendopt" "$formatopt"
options=( "${reply[@]}" \# "${query[@]}" )
;;
(write)
_regex_words options "write options" "$helpopt" "$pretendopt"
options=( "${reply[@]}" \# "${query[@]}" )
;;
(fields|migrate|version)
options=()
;;
(help)
# The help subcommand is treated separately
continue
;;
(*) # completions for plugin commands are generated using _beet_subcmd_options
_beet_subcmd_options "$subcmd"
options=( \( "${reply[@]}" \# "${query[@]}" \) )
;;
esac
# Create variable for holding option for this subcommand, and assign to it (needs to have a unique name).
typeset -a opts_for_$subcmd
set -A opts_for_$subcmd ${options[@]} # Assignment MUST be done using set (other methods fail).
regex_words_subcmds+=("$subcmd:$cmddesc:\${(@)opts_for_$subcmd}")
# Add to regex_words args for help subcommand
regex_words_help+=("$subcmd:$cmddesc")
done
_store_cache beets regex_words_subcmds regex_words_help BEETS_LIBRARY fields
fi
## Function for completing subcommands. It calls another completion function which is first created if it doesn't already exist.
_beet_subcmd() {
local -a options
local subcmd="${1}"
if [[ ! $(type _beet_${subcmd} | grep function) =~ function ]]; then
if ! _retrieve_cache "beets${subcmd}" || _cache_invalid "beets${subcmd}"; then
local matchany=/$'[^\0]##\0'/
local -a files
files=("$matchany" ':file:file:_files')
# get arguments for completing subcommand options
_beet_subcmd_options "$subcmd"
options=("${reply[@]}" \#)
_retrieve_cache beetscmds
case ${subcmd}; in
(import)
_regex_arguments _beet_import "${matchany}" /"${subcmd}"$'\0'/ "${options[@]}" "${files[@]}" \#
;;
(modify)
_regex_arguments _beet_modify "${matchany}" /"${subcmd}"$'\0'/ "${options[@]}" \
"${beets_query_args[@]}" "${beets_modify_args[@]}"
;;
(fields|migrate|version|config)
_regex_arguments _beet_${subcmd} "${matchany}" /"${subcmd}"$'\0'/ "${options[@]}"
;;
(help)
_regex_words subcmds "subcommands" "${beets_regex_words_help[@]}"
_regex_arguments _beet_help "${matchany}" /$'help\0'/ "${options[@]}" "${reply[@]}"
;;
(*) # Other commands have options followed by a query
_regex_arguments _beet_${subcmd} "${matchany}" /"${subcmd}"$'\0'/ "${options[@]}" "${beets_query_args[@]}"
;;
esac
# Store completion function in a cache file
local "beets_${subcmd}"="$(which _beet_${subcmd})"
_store_cache "beets${subcmd}" "beets_${subcmd}"
else
# Evaluate the function which is stored in $beets_${subcmd}
local var="beets_${subcmd}"
eval "${(P)var}"
fi
fi
_beet_${subcmd}
}
local -a opts_for_help
_regex_words subcmds "subcommands" "${regex_words_help[@]}"
opts_for_help=("${reply[@]}")
regex_words_subcmds+=('help:show help:$opts_for_help')
# Argument for global options
# Global options
local -a globalopts
_regex_words options "global options" "$configopt" "$debugopt" "$libopt" "$helpopt" "$destopt"
_regex_words options "global options" '-c:path to configuration file:$files' '-v:print debugging information' \
'-l:library database file to use:$files' '-h:show this help message and exit' '-d:destination music directory:$dirs'
globalopts=("${reply[@]}")
# Create main completion function
local -a subcmds
_regex_words subcmds "subcommands" "${regex_words_subcmds[@]}"
subcmds=("${reply[@]}")
_regex_arguments _beet "$matchany" \( "${globalopts[@]}" \# \) "${subcmds[@]}"
_regex_arguments _beet "$matchany" \( "${globalopts[@]}" \# \) "${beets_regex_words_subcmds[@]}"
# Set tag-order so that options are completed separately from arguments
zstyle ":completion:${curcontext}:" tag-order '! options'
@ -273,4 +235,3 @@ _beet "$@"
# Local Variables:
# mode:shell-script
# End: