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