1#compdef beet 2 3# zsh completion for beets music library manager and MusicBrainz tagger: http://beets.radbox.org/ 4 5# Default values for BEETS_LIBRARY & BEETS_CONFIG needed for the cache checking function. 6# They will be updated under the assumption that the config file is in the same directory as the library. 7local BEETS_LIBRARY=~/.config/beets/library.db 8local BEETS_CONFIG=~/.config/beets/config.yaml 9# Use separate caches for file locations, command completions, and query completions. 10# This allows the use of different rules for when to update each one. 11zstyle ":completion:${curcontext}:" cache-policy _beet_check_cache 12_beet_check_cache () { 13 local cachefile="$(basename ${1})" 14 if [[ ! -a "${1}" ]] || [[ "${1}" -ot =beet ]]; then 15 # always update the cache if it doesnt exist, or if the beet executable changes 16 return 0 17 fi 18 case cachefile; in 19 (beetslibrary) 20 if [[ ! -a "${~BEETS_LIBRARY}" ]] || [[ "${1}" -ot "${~BEETS_CONFIG}" ]]; then 21 return 0 22 fi 23 ;; 24 (beetscmds) 25 _retrieve_cache beetslibrary 26 if [[ "${1}" -ot "${~BEETS_CONFIG}" ]]; then 27 return 0 28 fi 29 ;; 30 esac 31 return 1 32} 33 34# useful: argument to _regex_arguments for matching any word 35local matchany=/$'[^\0]##\0'/ 36# arguments to _regex_arguments for completing files and directories 37local -a files dirs 38files=("$matchany" ':file:file:_files') 39dirs=("$matchany" ':dir:directory:_dirs') 40 41# Retrieve or update caches 42if ! _retrieve_cache beetslibrary || _cache_invalid beetslibrary; then 43 local BEETS_LIBRARY="${$(beet config|grep library|cut -f 2 -d ' '):-${BEETS_LIBRARY}}" 44 local BEETS_CONFIG="${$(beet config -p):-${BEETS_CONFIG}}" 45 _store_cache beetslibrary BEETS_LIBRARY BEETS_CONFIG 46fi 47 48if ! _retrieve_cache beetscmds || _cache_invalid beetscmds; then 49 local -a subcommands fields beets_regex_words_subcmds beets_regex_words_help query modify 50 local subcmd cmddesc matchquery matchmodify field fieldargs queryelem modifyelem 51 # Useful function for joining grouped lines of output into single lines (taken from _completion_helpers) 52 _join_lines() { 53 awk -v SEP="$1" -v ARG2="$2" -v START="$3" -v END2="$4" 'BEGIN {if(START==""){f=1}{f=0}; 54 if(ARG2 ~ "^[0-9]+"){LINE1 = "^[[:space:]]{,"ARG2"}[^[:space:]]"}else{LINE1 = ARG2}} 55 ($0 ~ END2 && f>0 && END2!="") {exit} 56 ($0 ~ START && f<1) {f=1; if(length(START)!=0){next}} 57 ($0 ~ LINE1 && f>0) {if(f<2){f=2; printf("%s",$0)}else{printf("\n%s",$0)}; next} 58 (f>1) {gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0); printf("%s%s",SEP, $0); next} 59 END {print ""}' 60 } 61 # Variables used for completing subcommands and queries 62 subcommands=(${${(f)"$(beet help | _join_lines ' ' 3 'Commands:')"}[@]}) 63 fields=($(beet fields | grep -G '^ ' | sort -u | colrm 1 2)) 64 for field in "${fields[@]}" 65 do 66 fieldargs="$fieldargs '$field:::{_beet_field_values $field}'" 67 done 68 queryelem="_values -S : 'query field (add an extra : to match by regexp)' '::' $fieldargs" 69 modifyelem="_values -S = 'modify field (replace = with ! to remove field)' $(echo "'${^fields[@]}:: '")" 70 # regexps for matching query and modify terms on the command line 71 matchquery=/"(${(j/|/)fields[@]})"$':[^\0]##\0'/ 72 matchmodify=/"(${(j/|/)fields[@]})"$'(=[^\0]##|!)\0'/ 73 # create completion function for queries 74 _regex_arguments _beet_query "$matchany" \# \( "$matchquery" ":query:query string:$queryelem" \) \# 75 local "beets_query"="$(which _beet_query)" 76 # arguments for _regex_arguments for completing lists of queries and modifications 77 beets_query_args=( \( "$matchquery" ":query:query string:{_beet_query}" \) \# ) 78 beets_modify_args=( \( "$matchmodify" ":modify:modify string:$modifyelem" \) \# ) 79 # now build arguments for _beet and _beet_help completion functions 80 beets_regex_words_subcmds=('(') 81 for i in ${subcommands[@]}; do 82 subcmd="${i[(w)1]}" 83 # remove first word and parenthesised alias, replace : with -, [ with (, ] with ), and remove single quotes 84 cmddesc="${${${${${i[(w)2,-1]##\(*\) #}//:/-}//\[/(}//\]/)}//\'/}" 85 # update arguments needed for creating _beet 86 beets_regex_words_subcmds+=(/"${subcmd}"$'\0'/ ":subcmds:subcommands:((${subcmd}:${cmddesc// /\ }))") 87 beets_regex_words_subcmds+=(\( "${matchany}" ":option:option:{_beet_subcmd ${subcmd}}" \) \# \|) 88 # update arguments needed for creating _beet_help 89 beets_regex_words_help+=("${subcmd}:${cmddesc}") 90 done 91 beets_regex_words_subcmds[-1]=')' 92 _store_cache beetscmds beets_regex_words_subcmds beets_regex_words_help beets_query_args beets_modify_args beets_query 93else 94 # Evaluate the variable containing the query completer function 95 eval "${beets_query}" 96fi 97 98# Function for getting unique values for field from database (you may need to change the path to the database). 99_beet_field_values() { 100 local -a output fieldvals 101 local sqlcmd="select distinct $1 from items;" 102 _retrieve_cache beetslibrary 103 case ${1} 104 in 105 lyrics) 106 fieldvals= 107 ;; 108 *) 109 if [[ "$(sqlite3 ${~BEETS_LIBRARY} ${sqlcmd} 2>&1)" =~ "no such column" ]]; then 110 sqlcmd="select distinct value from item_attributes where key=='$1' and value!='';" 111 fi 112 output="$(sqlite3 ${~BEETS_LIBRARY} ${sqlcmd} 2>/dev/null | sed -rn '/^-+$/,${{/^[- ]+$/n};p}')" 113 fieldvals=("${(f)output[@]}") 114 ;; 115 esac 116 compadd -P \" -S \" -M 'm:{[:lower:][:upper:]}={[:upper:][:lower:]}' -Q -a fieldvals 117} 118 119# This function takes a beet subcommand as its first argument, and then uses _regex_words to set ${reply[@]} 120# to an array containing arguments for the _regex_arguments function. 121_beet_subcmd_options() { 122 local shortopt optarg optdesc 123 local matchany=/$'[^\0]##\0'/ 124 local -a regex_words 125 regex_words=() 126 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}')"}[@]} 127 do 128 opt="${i[(w)1]/,/}" 129 optarg="${${${i## #[-a-zA-Z]# }##[- ]##*}%%[, ]*}" 130 optdesc="${${${${${i[(w)2,-1]/[A-Z, ]#--[-a-z]##[=A-Z]# #/}//:/-}//\[/(}//\]/)}//\'/}" 131 case $optarg; in 132 ("") 133 if [[ "$1" == "import" && "$opt" == "-L" ]]; then 134 regex_words+=("$opt:$optdesc:\${beets_query_args[@]}") 135 else 136 regex_words+=("$opt:$optdesc") 137 fi 138 ;; 139 (LOG) 140 local -a files 141 files=("$matchany" ':file:file:_files') 142 regex_words+=("$opt:$optdesc:\$files") 143 ;; 144 (CONFIG) 145 local -a configfile 146 configfile=("$matchany" ':file:config file:{_files -g *.yaml}') 147 regex_words+=("$opt:$optdesc:\$configfile") 148 ;; 149 (LIB|LIBRARY) 150 local -a libfile 151 libfile=("$matchany" ':file:database file:{_files -g *.db}') 152 regex_words+=("$opt:$optdesc:\$libfile") 153 ;; 154 (DIR|DIRECTORY) 155 local -a dirs 156 dirs=("$matchany" ':dir:directory:_dirs') 157 regex_words+=("$opt:$optdesc:\$dirs") 158 ;; 159 (SOURCE) 160 if [[ "${1}" -eq lastgenre ]]; then 161 local -a lastgenresource 162 lastgenresource=(/$'(artist|album|track)\0'/ ':source:genre source:(artist album track)') 163 regex_words+=("$opt:$optdesc:\$lastgenresource") 164 else 165 regex_words+=("$opt:$optdesc:\$matchany") 166 fi 167 ;; 168 (*) 169 regex_words+=("$opt:$optdesc:\$matchany") 170 ;; 171 esac 172 done 173 _regex_words options "$1 options" "${regex_words[@]}" 174} 175 176## Function for completing subcommands. It calls another completion function which is first created if it doesn't already exist. 177_beet_subcmd() { 178 local -a options 179 local subcmd="${1}" 180 if [[ ! $(type _beet_${subcmd} | grep function) =~ function ]]; then 181 if ! _retrieve_cache "beets${subcmd}" || _cache_invalid "beets${subcmd}"; then 182 local matchany=/$'[^\0]##\0'/ 183 local -a files 184 files=("$matchany" ':file:file:_files') 185 # get arguments for completing subcommand options 186 _beet_subcmd_options "$subcmd" 187 options=("${reply[@]}" \#) 188 _retrieve_cache beetscmds 189 case ${subcmd}; in 190 (import) 191 _regex_arguments _beet_import "${matchany}" /"${subcmd}"$'\0'/ "${options[@]}" "${files[@]}" \# 192 ;; 193 (modify) 194 _regex_arguments _beet_modify "${matchany}" /"${subcmd}"$'\0'/ "${options[@]}" \ 195 "${beets_query_args[@]}" "${beets_modify_args[@]}" 196 ;; 197 (fields|migrate|version|config) 198 _regex_arguments _beet_${subcmd} "${matchany}" /"${subcmd}"$'\0'/ "${options[@]}" 199 ;; 200 (help) 201 _regex_words subcmds "subcommands" "${beets_regex_words_help[@]}" 202 _regex_arguments _beet_help "${matchany}" /$'help\0'/ "${options[@]}" "${reply[@]}" 203 ;; 204 (*) # Other commands have options followed by a query 205 _regex_arguments _beet_${subcmd} "${matchany}" /"${subcmd}"$'\0'/ "${options[@]}" "${beets_query_args[@]}" 206 ;; 207 esac 208 # Store completion function in a cache file 209 local "beets_${subcmd}"="$(which _beet_${subcmd})" 210 _store_cache "beets${subcmd}" "beets_${subcmd}" 211 else 212 # Evaluate the function which is stored in $beets_${subcmd} 213 local var="beets_${subcmd}" 214 eval "${(P)var}" 215 fi 216 fi 217 _beet_${subcmd} 218} 219 220# Global options 221local -a globalopts 222_regex_words options "global options" '-c:path to configuration file:$files' '-v:print debugging information' \ 223 '-l:library database file to use:$files' '-h:show this help message and exit' '-d:destination music directory:$dirs' 224globalopts=("${reply[@]}") 225 226# Create main completion function 227_regex_arguments _beet "$matchany" \( "${globalopts[@]}" \# \) "${beets_regex_words_subcmds[@]}" 228 229# Set tag-order so that options are completed separately from arguments 230zstyle ":completion:${curcontext}:" tag-order '! options' 231 232# Execute the completion function 233_beet "$@" 234 235# Local Variables: 236# mode:shell-script 237# End: 238