1emulate -L zsh 2setopt extendedglob cbases 3 4local opt o_verbose o_list i 5 6autoload -Uz zsh-mime-handler 7 8while getopts "flv" opt; do 9 case $opt in 10 # List: show existing suffixes and their handlers then exit. 11 (l) 12 o_list=1 13 ;; 14 15 # Verbose; print diagnostics to stdout. 16 (v) 17 o_verbose=1 18 ;; 19 20 # Force; discard any existing settings before reading. 21 (f) 22 unset -m zsh_mime_\* 23 ;; 24 25 (*) 26 [[ $opt = \? ]] || print -r "Option $opt not handled, complain" >&2 27 return 1 28 ;; 29 esac 30done 31(( OPTIND > 1 )) && shift $(( OPTIND - 1 )) 32 33 34if [[ -n $o_list ]]; then 35 # List and return. Remember that suffixes may be overridden by styles. 36 # However, we require some sort of standard handler to be present, 37 # so we don't need to search styles for suffixes that aren't 38 # handled. Yet. 39 local list_word 40 local -a handlers 41 if (( $# )); then 42 handlers=(${(k)zsh_mime_handlers[(I)${(j.|.)*}]}) 43 else 44 handlers=(${(k)zsh_mime_handlers}) 45 fi 46 for suffix in ${(o)handlers}; do 47 zstyle -s ":mime:.$suffix:" handler list_word || 48 list_word=${zsh_mime_handlers[$suffix]} 49 print ${(r.10.)suffix}$list_word 50 zstyle -s ":mime:.$suffix:" flags list_word || 51 list_word=${zsh_mime_flags[$suffix]} 52 if [[ -n $list_word ]]; then 53 print " flags: $list_word" 54 fi 55 done 56 return 0 57fi 58 59 60# Handler for each suffix. 61(( ${+zsh_mime_handlers} )) || typeset -gA zsh_mime_handlers 62# Corresponding flags, if any, for handler 63(( ${+zsh_mime_flags} )) || typeset -gA zsh_mime_flags 64 65# Internal maps read from MIME configuration files. 66# Note we don't remember the types, just the mappings from suffixes 67# to handlers and their flags. 68typeset -A suffix_type_map type_handler_map type_flags_map 69 70local -a default_type_files default_cap_files 71local -a type_files cap_files array match mbegin mend 72local file line type suffix exts elt flags line2 73integer ind 74 75default_type_files=(~/.mime.types /usr/local/etc/mime.types) 76default_cap_files=(~/.mailcap /usr/local/etc/mailcap) 77 78# Customizable list of files to examine. 79if zstyle -a :mime: mime-types type_files; then 80 while (( (ind = ${type_files[(I)+]}) > 0 )); do 81 type_files[$ind]=($default_type_files) 82 done 83else 84 type_files=($default_type_files) 85fi 86 87if zstyle -a :mime: mailcap cap_files; then 88 while (( (ind = ${cap_files[(I)+]}) > 0 )); do 89 cap_files[$ind]=($default_cap_files) 90 done 91else 92 cap_files=($default_cap_files) 93fi 94 95{ 96 mime-setup-add-type() { 97 local type suffix 98 local -a array 99 100 type=$1 101 shift 102 103 while (( $# )); do 104 # `.ps' instead of `ps' has been noted 105 suffix=${1##.} 106 shift 107 108 if [[ -z $suffix_type_map[$suffix] ]]; then 109 [[ -n $o_verbose ]] && 110 print -r "Adding type $type for $suffix" >&2 111 suffix_type_map[$suffix]=$type 112 else 113 # Skip duplicates. 114 array=(${=suffix_type_map[$suffix]}) 115 if [[ ${array[(I)$type]} -eq 0 ]]; then 116 [[ -n $o_verbose ]] && 117 print -r "Appending type $type for already defined $suffix" >&2 118 suffix_type_map[$suffix]+=" $type" 119 fi 120 fi 121 done 122 } 123 124 # Loop through files to find suffixes for MIME types. 125 # Earlier entries take precedence, so the files need to be listed 126 # with the user's own first. This also means pre-existing 127 # values in suffix_type_map are respected. 128 for file in $type_files; do 129 [[ -r $file ]] || continue 130 131 # For once we rely on the fact that read handles continuation 132 # lines ending in backslashes, i.e. there's no -r. 133 while read line; do 134 # Skip blank or comment lines. 135 [[ $line = [[:space:]]#(\#*|) ]] && continue 136 137 # There are two types of line you find in MIME type files. 138 # The original simple sort contains the type name then suffixes 139 # separated by whitespace. However, Netscape insists 140 # on adding lines with backslash continuation with 141 # key="value" pairs. So we'd better handle both. 142 if [[ $line = *=* ]]; then 143 # Gory. 144 # This relies on the fact that a typical entry: 145 # type=video/x-mpeg2 desc="MPEG2 Video" exts="mpv2,mp2v" 146 # looks like a parameter assignment. However, we really 147 # don't want to be screwed up by future extensions, 148 # so we split the elements to an array and pick out the 149 # ones we're interested in. 150 type= exts= 151 152 # Syntactically split line to preserve quoted words. 153 array=(${(z)line}) 154 for elt in $array; do 155 if [[ $elt = (type|exts)=* ]]; then 156 eval $elt 157 fi 158 done 159 160 # Get extensions by splitting on comma 161 array=(${(s.,.)exts}) 162 163 [[ -n $type ]] && mime-setup-add-type $type $array 164 else 165 # Simple. 166 mime-setup-add-type ${=line} 167 fi 168 done <$file 169 done 170} always { 171 unfunction mime-setup-add-type >&/dev/null 172} 173 174local -a pats_prio o_prios 175local o_overwrite sentinel 176typeset -A type_prio_flags_map type_prio_src_map type_prio_mprio_map 177integer src_id prio mprio 178 179# A list of keywords indicating the methods used to break ties amongst multiple 180# entries. The following keywords are accepted: 181# files: The order of files read: Entries from files read earlier are preferred 182# (The default value of the variable is a list with this keyword alone) 183# priority: The priority flag is matched in the entry. Can be a value from 0 to 184# 9. The default priority is 5. Higher priorities are preferred. 185# flags: See the mailcap-prio-flags option 186# place: Always overrides. Useful for specifying that entries read later are 187# preferred. 188# 189# As the program reads mailcap entries, if it encounters a duplicate 190# entry, each of the keywords in the list are checked to see if the new 191# entry can override the existing entry. If none of the keywords are able 192# to decide whether the new entry should be preferred to the older one, the 193# new entry is discarded. 194zstyle -a :mime: mailcap-priorities o_prios || o_prios=(files) 195 196# This style is used as an argument for the flags test in mailcap-priorities. 197# This is a list of patterns, each of which is tested against the flags for the 198# mailcap entry. An match with a pattern ahead in the list is preferred as 199# opposed to a match later in the list. An unmatched item is least preferred. 200zstyle -a :mime: mailcap-prio-flags pats_prio 201 202# Loop through files to find handlers for types. 203((src_id = 0)) 204for file in $cap_files; do 205 [[ -r $file ]] || continue 206 207 ((src_id = src_id + 1)) 208 # Oh, great. We need to preserve backslashes inside the line, 209 # but need to manage continuation lines. 210 while read -r line; do 211 # Skip blank or comment lines. 212 [[ $line = [[:space:]]#(\#*|) ]] && continue 213 214 while [[ $line = (#b)(*)\\ ]]; do 215 line=$match[1] 216 read -r line2 || break 217 line+=$line2 218 done 219 220 # Guess what, this file has a completely different format. 221 # See mailcap(4). 222 # The biggest unpleasantness here is that the fields are 223 # delimited by semicolons, but the command field, which 224 # is the one we want to extract, may itself contain backslashed 225 # semicolons. 226 if [[ $line = (#b)[[:space:]]#([^[:space:]\;]##)[[:space:]]#\;(*) ]] 227 then 228 # this is the only form we can handle, but there's no point 229 # issuing a warning for other forms. 230 type=$match[1] 231 line=$match[2] 232 # See if it has flags after the command. 233 if [[ $line = (#b)(([^\;\\]|\\\;|\\[^\;])#)\;(*) ]]; then 234 line=$match[1] 235 flags=$match[3] 236 else 237 flags= 238 fi 239 # Remove quotes from semicolons 240 line=${line//\\\;/\;} 241 # and remove any surrounding white space --- this might 242 # make the handler empty. 243 line=${${line##[[:space:]]#}%%[[:space:]]} 244 245 ((prio = 0)) 246 for i in $pats_prio; do 247 # print -r "Comparing $i with '$flags'" >&2 248 [[ $flags = ${~i} ]] && break 249 # print -r "Comparison failed" >&2 250 ((prio = prio + 1)) 251 done 252 ((mprio=5)) 253 [[ $flags = (#b)*priority=([0-9])* ]] && mprio=$match[1] 254 sentinel=no 255 if [[ -n $type_handler_map[$type] ]]; then 256 for i in $o_prios; do 257 case $i in 258 (files) 259 if [[ $src_id -lt $type_prio_src_map[$type] ]]; then 260 sentinel=yes; break 261 elif [[ $src_id -gt $type_prio_src_map[$type] ]]; then 262 sentinel=no; break 263 fi 264 ;; 265 (priority) 266 if [[ $mprio -gt $type_prio_mprio_map[$type] ]]; then 267 sentinel=yes; break 268 elif [[ $mprio -lt $type_prio_mprio_map[$type] ]]; then 269 sentinel=no; break 270 fi 271 ;; 272 (flags) 273 if [[ $prio -lt $type_prio_flags_map[$type] ]]; then 274 sentinel=yes; break 275 elif [[ $prio -gt $type_prio_flags_map[$type] ]]; then 276 sentinel=no; break 277 fi 278 ;; 279 (place) 280 sentinel=yes 281 break 282 ;; 283 esac 284 done 285 else 286 sentinel=yes 287 fi 288 289 if [[ $sentinel = yes ]]; then 290 if [[ -n $o_verbose ]]; then 291 if [[ -n $type_handler_map[$type] ]]; then 292 print -r "Overriding" >&2 293 else 294 print -r "Adding" >&2 295 fi 296 print -r " handler for type $type:" >&2 297 print -r " $line" >&2 298 fi 299 type_handler_map[$type]=$line 300 type_flags_map[$type]=$flags 301 type_prio_src_map[$type]=$src_id 302 type_prio_flags_map[$type]=$prio 303 type_prio_mprio_map[$type]=$mprio 304 if [[ -n $flags && -n $o_verbose ]]; then 305 print -r " with flags $flags" >&2 306 fi 307 elif [[ -n $o_verbose ]]; then 308 print -r "Skipping handler for already defined type $type:" >&2 309 print -r " $line" >&2 310 if [[ -n $flags ]]; then 311 print -r " with flags $flags" >&2 312 fi 313 fi 314 fi 315 done <$file 316done 317 318 319# Check for styles which override whatever is in the file. 320# We need to make sure there is a handler set up; for some 321# uses we may need to defer checking styles until zsh-mime-handler. 322# How much we need to do here is a moot point. 323zstyle -L | while read line; do 324 array=(${(Q)${(z)line}}) 325 if [[ $array[3] = (handler|flags) && \ 326 $array[2] = (#b):mime:.([^:]##):(*) ]]; then 327 suffix=$match[1] 328 # Make sure there is a suffix alias set up for this. 329 alias -s $suffix >&/dev/null || alias -s $suffix=zsh-mime-handler 330 # Also for upper case variant 331 alias -s ${(U)suffix} >&/dev/null || alias -s ${(U)suffix}=zsh-mime-handler 332 fi 333done 334 335# Now associate the suffixes directly with handlers. 336# We just look for the first one with a handler. 337# If there is no handler, we don't bother registering an alias 338# for the suffix. 339 340for suffix line in ${(kv)suffix_type_map}; do 341 # Skip if we already have a handler. 342 [[ -n $zsh_mime_handlers[$suffix] ]] && continue 343 344 # Split the space-separated list of types. 345 array=(${=line}) 346 347 # Find the first type with a handler. 348 line2= 349 for type in $array; do 350 line2=${type_handler_map[$type]} 351 [[ -n $line2 ]] && break 352 done 353 354 # See if there is a generic type/* handler. 355 # TODO: do we need to consider other forms of wildcard? 356 if [[ -z $line2 ]]; then 357 for type in $array; do 358 type="${type%%/*}/*" 359 line2=${type_handler_map[$type]} 360 [[ -n $line2 ]] && break 361 done 362 fi 363 364 if [[ -n $line2 ]]; then 365 # Found a type with a handler. 366 # Install the zsh handler as an alias, but never override 367 # existing suffix handling. 368 alias -s $suffix >&/dev/null || alias -s $suffix=zsh-mime-handler 369 alias -s ${(U)suffix} >&/dev/null || alias -s ${(U)suffix}=zsh-mime-handler 370 371 zsh_mime_handlers[$suffix]=$line2 372 zsh_mime_flags[$suffix]=$type_flags_map[$type] 373 fi 374done 375 376true 377