diff options
-rw-r--r-- | ChangeLog | 6 | ||||
-rw-r--r-- | Doc/Zsh/contrib.yo | 157 | ||||
-rw-r--r-- | Functions/MIME/.distfiles | 3 | ||||
-rw-r--r-- | Functions/MIME/pick-web-browser | 112 | ||||
-rw-r--r-- | Functions/MIME/zsh-mime-handler | 142 | ||||
-rw-r--r-- | Functions/MIME/zsh-mime-setup | 259 |
6 files changed, 677 insertions, 2 deletions
diff --git a/ChangeLog b/ChangeLog index 849e62793..856f8c634 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2003-09-14 Peter Stephenson <pws@pwstephenson.fsnet.co.uk> + + * 19053 modified c.f. 19056: Functions/MIME, Doc/Zsh/contrib.yo: + Functions using suffix aliases for handling suffixes mailcap + style. + 2003-09-14 Clint Adams <clint@zsh.org> * 19076: Src/Modules/terminfo.c: don't call setupterm diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index 819ae6a98..cf1bada4b 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -13,6 +13,7 @@ startmenu() menu(Utilities) menu(Prompt Themes) menu(ZLE Functions) +menu(MIME functions) menu(Other Functions) endmenu() @@ -344,7 +345,7 @@ normally call a theme's setup function directly. ) enditem() -texinode(ZLE Functions)(Other Functions)(Prompt Themes)(User Contributions) +texinode(ZLE Functions)(MIME Functions)(Prompt Themes)(User Contributions) sect(ZLE Functions) subsect(Widgets) @@ -955,7 +956,159 @@ whether the tt(widget) style is used. ) enditem() -texinode(Other Functions)()(ZLE Functions)(User Contributions) +texinode(MIME Functions)(Other Functions)(ZLE Functions)(User Contributions) +sect(MIME Functions) + +Three functions are available to provide handling of files recognised by +extension, for example to dispatch a file tt(text.ps) when executed as a +command to an appropriate viewer. + +startitem() +xitem(tt(zsh-mime-setup [-flv])) +item(tt(zsh-mime-handler))( +These two functions use the files tt(~/.mime.types) and tt(/etc/mime.types), +which associate types and extensions, as well as tt(~/.mailcap) and +tt(/etc/mailcap) files, which associate types and the programs that +handle them. These are provided on many systems with the Multimedia +Internet Mail Extensions. + +To enable the system, the function tt(zsh-mime-setup) should be +autoloaded and run. This allows files with extensions to be treated +as executable; such files be completed by the function completion system. +The function tt(zsh-mime-handler) should not need to be called by the +user. + +The system works by setting up suffix aliases with `tt(alias -s)'. +Suffix aliases already installed by the user will not be overwritten. + +Repeated calls to tt(zsh-mime-setup) do not override the existing +mapping between suffixes and executable files unless the option tt(-f) +is given. Note, however, that this does not override existing suffix +aliases assigned to handlers other than tt(zsh-mime-handler). +Calling tt(zsh-mime-setup) with the option tt(-l) lists the existing +mapping without altering it. Calling tt(zsh-mime-setup) with the option +tt(-v) causes verbose output to be shown during the setup operation. + +The system respects the tt(mailcap) flags tt(needsterminal) and +tt(copiousoutput), see manref(mailcap)(4). + +The functions use the following styles, which are defined with the +tt(zstyle) builtin command (\ +ifzman(see zmanref(zshmodules))\ +ifnzman(noderef(The zsh/zutil Module))). They should be defined +before tt(zsh-mime-setup) is run. The contexts used all +start with tt(:mime:), with additional components in some cases. +It is recommended that a trailing tt(*) (suitably quoted) be appended +to style patterns in case the system is extended in future. Some +examples are given below. +startitem() +item(mime-types)( +A list of files in the format of tt(~/.mime.types) and +tt(/etc/mime.types) to be read during setup, replacing the default list +which consists of those two files. The context is tt(:mime:). +) +item(mailcap)( +A list of files in the format of tt(~/.mailcap) and +tt(/etc/mailcap) to be read during setup, replacing the default list +which consists of those two files. The context is tt(:mime:). +) +item(handler)( +Specifies a handler for a suffix; the suffix is given by the context as +tt(:mime:.)var(suffix)tt(:), and the format of the handler is exactly +that in tt(mailcap). Note in particular the `tt(.)' and trailing colon +to distinguish this use of the context. This overrides any handler +specified by the tt(mailcap) files. If the handler requires a terminal, +the tt(flags) style should be set to include the word tt(needsterminal), +or if the output is to be displayed through a pager (but not if the +handler is itself a pager), it should include tt(copiousoutput). +) +item(flags)( +Defines flags to go with a handler; the context is as for the +tt(handler) style, and the format is as for the flags in tt(mailcap). +) +item(pager)( +If set, will be used instead of tt($PAGER) or tt(more) to handle +suffixes where the tt(copiousoutput) flag is set. The context is +as for tt(handler), i.e. tt(:mime:.)var(suffix)tt(:) for handling +a file with the given var(suffix). +) +enditem() + +Examples: + +example(zstyle ':mime:*' mailcap ~/.mailcap /usr/local/etc/mailcap +zstyle ':mime:.txt' handler less %s +zstyle ':mime:.txt' flags needsterminal) + +When tt(zsh-mime-setup) is subsequently run, it will look for +tt(mailcap) entries in the two files given. Files of suffix tt(.txt) +will be handled by running `tt(less) var(file.txt)'. The flag +tt(needsterminal) is set to show that this program must run attached to a +terminal. + +As there are several steps to dispatching a command, the following +should be checked if attempting to execute a file by extension +tt(.)var(ext) does not have the expected effect. +starteit() +eit()( +The command `tt(alias -s) var(ext)' should show +`tt(ps=zsh-mime-handler)'. If it shows something else, another suffix +alias was already installed and was not overwritten. If it shows +nothing, no handler was installed: this is most likely because no +handler was found in the tt(.mime.types) and tt(mailcap) combination for +tt(.ext) files. In that case, appropriate handling should be added to +tt(~/.mime.types) and tt(mailcap). +) +eit()( +If the extension is handled by tt(zsh-mime-handler) but the file is +not opened correctly, either the handler defined for the type is +incorrect, or the flags associated with it are in appropriate. Running +tt(zsh-mime-setup -l) will show the handler and, if there are any, the +flags. A tt(%s) in the handler is replaced by the file (suitably quoted +if necessary). Check that the handler program listed lists and can +be run in the way shown. Also check that the flags tt(needsterminal) or +tt(copiousoutput) are set if the handler needs to be run under a +terminal; the second flag is used if the output should be sent to a pager. +An example of a suitable tt(mailcap) entry for such a program is: + +example(text/html; /usr/bin/lynx '%s'; needsterminal) +) +endeit() +) +item(tt(pick-web-browser))( +This function is separate from the two MIME functions described above +and can be assigned directly to a suffix: + +example(autoload -U pick-web-browser +alias -s html=pick-web-browser) + +It is provided as an intelligent front end to dispatch a web browser. +It will check if an X Windows display is available, and if so +if there is already a browser running which can accept a remote +connection. In that case, the file will be displayed in that browser; +you should check explicitly if it has appeared in the running browser's +window. Otherwise, it will start a new browser according to a builtin +set of preferences. + +Alternatively, tt(pick-web-browser) can be run as a zsh script. + +Two styles are available to customize the choice of browsers: +tt(x-browsers) when running under the X Windows System, and +tt(tty-browsers) otherwise. These are arrays in decreasing order +of preference consiting of the command name under which to start the +browser. They are looked up in the context tt(:mime:) (which may +be extended in future, so appending `tt(*)' is recommended). For +example, + +example(zstyle ':mime:*' x-browsers opera konqueror netscape) + +specifies that tt(pick-web-browser) should first look for a runing +instance of Opera, Konqueror or Netscape, in that order, and if it +fails to find any should attempt to start Opera. +) +enditem() + +texinode(Other Functions)()(MIME Functions)(User Contributions) sect(Other Functions) There are a large number of helpful functions in the tt(Functions/Misc) diff --git a/Functions/MIME/.distfiles b/Functions/MIME/.distfiles new file mode 100644 index 000000000..c6b05b777 --- /dev/null +++ b/Functions/MIME/.distfiles @@ -0,0 +1,3 @@ +DISTFILES_SRC=' +zsh-mime-setup zsh-mime-handler pick-web-browser +' diff --git a/Functions/MIME/pick-web-browser b/Functions/MIME/pick-web-browser new file mode 100644 index 000000000..6f4650c12 --- /dev/null +++ b/Functions/MIME/pick-web-browser @@ -0,0 +1,112 @@ +# Function to find a web browser to run on a URL or file. +# Can also be run as a script. It is suitable for use as +# a suffix alias: +# alias -s html=pick-web-browser +# +# The single argument is the URL or file name which may be of any type. +# The only processing which occurs is that if the argument is a file, +# it is converted into a URL. As the function takes account of +# any necessary conversions to the file name (for example, if it +# contains spaces), it is generally preferable to pass in raw file +# names rather than convert them to URLs elsewhere. +# +# The function takes account of the fact that many X Windows browsers +# which are already running on the current display can take a command +# to pass the URL to that process for handling. A typical sign +# that this has happened is that apparently nothing happens --- you +# need to check the browser window. +# +# If no $DISPLAY is set, the function tries to start a terminal-based +# browser instead. + +emulate -L zsh +setopt extendedglob cbases nonomatch + +local -a xbrowsers ttybrowsers + +# X Windows browsers which might be running and can accept +# a remote URL. You can change the order of preference. +# If none is already running, starts the first in the array. +zstyle -a :mime: x-browsers xbrowsers || + xbrowsers=(mozilla netscape opera konqueror) +# Preferred command line browser. Used if there is on $DISPLAY set. +zstyle -a :mime: tty-browsers ttybrowsers || + ttybrowsers=(links lynx) +# Characters in addition to alphanumerics which can appear literally +# in a URL. `-' should be the first if it appears, so append others +# to the end. +litc="-_./" + +local -a windows remoteargs match mbegin mend +local url browser + +url=$1 +if [[ -f $url ]]; then + if [[ $url = *[^-_[:alnum:]]* ]]; then + # Convert special characters into hex escapes. + local sofar + while [[ $url = (#b)([${litc}[:alnum:]]#)([^${litc}[:alnum:]])(*) ]] + do + sofar+="$match[1]%${$(( [#16] ##$match[2] ))##0x}" + url=$match[3] + done + url="$sofar$url" + fi + + # Turn this into a local URL + if [[ $url = /* ]]; then + url=file://$url + else + url=file://$PWD/$url + fi +fi + + +if [[ -n $DISPLAY ]]; then + # X Windows running + + # Get the name of all windows running; use the internal name, not + # the friendly name, which is less useful. + # + # The nasty but portable version. + # The nice but non-portable version uses Perl, even though perl + # is more portable. +# windows=(${(f)"$(xwininfo -root -all | +# sed -ne 's/.*".*": ("\(.*\)" ".*").*/\1/p' |sort | uniq)"}) + + windows=(${(f)"$(xwininfo -root -all | + perl -ne '/.*"(.*)": \("(.*)" "(.*)"\).*/ and $w{$2} = 1; + END { print join("\n", keys %w), "\n" }')"}) + + # Is any browser we've heard of running? + for browser in $xbrowsers; do + if [[ $windows[(I)(#i)$browser] -ne 0 ]]; then + if [[ $browser = konqueror ]]; then + # I'm sure there's documentation for this somewhere... + dcop $(dcop|grep konqueror) default openBrowserWindow $url + else + # Mozilla bells and whistles are described at: + # http://www.mozilla.org/unix/remote.html + $browser -remote "openURL($url)" + fi + return + fi + done + + # Start our preferred X Windows browser in the background. + for browser in $xbrowsers; do + if eval "[[ =$browser != \\=$browser ]]"; then + # The following is to make the job text more readable. + eval ${(q)browser} ${(q)url} "&" + break + fi + done +else + # Start up dumb terminal browser. + for browser in $ttybrowsers; do + if eval "[[ =$browser != \\=$browser ]]"; then + $browser $url + break + fi + done +fi diff --git a/Functions/MIME/zsh-mime-handler b/Functions/MIME/zsh-mime-handler new file mode 100644 index 000000000..b64fd54cd --- /dev/null +++ b/Functions/MIME/zsh-mime-handler @@ -0,0 +1,142 @@ +# Handler for MIME types using associative arrays +# zsh_mime_handlers and zsh_mime_flags set up by zsh-mime-setup. +# +# The only flags it handles are copiousoutput and needsterminal. +# copiousoutput is assumed to imply needsterminal. Apart from +# those, it tries to be a bit cunning about quoting, which +# can be a nightmare in MIME handling. If it sees something like +# netscape %s +# and it only has one file to handle (the usual case then it will handle it +# internally just by appending a file.) +# +# Anything else is handled by passing to sh -c, which is the only think +# with a high probability of working. If it sees something with +# quotes, e.g. +# /usr/bin/links "%s" +# it will assume someone else has tried to fix the quoting problem and not +# do that. If it sees something with no quotes but other metacharacters, +# e.g. +# cat %s | handler +# then it will do any quoting and pass the result to sh -c. +# So for example if the argument is "My File", the command executed +# is supposedly +# sh -c 'cat My\ File | handler' +# +# This note is mostly here so you can work out what I tried to do when +# it goes horribly wrong. + +emulate -L zsh +setopt extendedglob cbases + +# We need zformat from zsh/zutil for %s replacement. +zmodload -i zsh/zutil + +# Always called with a filename argument first. +# There might be other arguments; don't really know what to do +# with these, but if they came from e.g. `*.ps' then we might +# just as well pass them all down. However, we just take the +# suffix from the first since that's what invoked us via suffix -s. + +local suffix context +local -a match mbegin mend + +[[ $1 = (#b)*.([^.]##) ]] || return 1 +suffix=$match[1] +context=":mime:.${suffix}:" + +local handler flags + +zstyle -s $context handler handler || + handler="${zsh_mime_handlers[$suffix]}" +zstyle -s $context flags flags || + flags="${zsh_mime_flags[$suffix]}" + +local -a files +local hasmeta stdin + +# See if the handler has shell metacharacters in. +# Don't count whitespace since we can split that when it's unquoted. +if [[ $handler = *[\\\;\*\?\|\"\'\`\$]* ]]; then + hasmeta=1 +fi + +local -a execargs + +if [[ $handler = *%s* ]]; then + # We need to replace %s with the file(s). + local command + if [[ -n $hasmeta || $# -gt 1 ]]; then + # The handler is complicated, either due to special + # characters or multiple files. We are going to pass it + # down to sh, since it's probably written for sh syntax. + # + # See if it's a good idea to quote the filename(s). + # It isn't if there are already quotes in the handler, since + # that means somebody already tried to take account of that. + if [[ $handler = *[\'\"]* ]]; then + # Probably we ought not even to handle multiple + # arguments, but at least the error message ought + # to make it obvious what's going on. + zformat -f command $handler s:"$argv" + else + files=(${(q)argv}) + zformat -f command $handler s:"$files" + fi + execargs=(sh -c $command) + else + # Simple command, one filename. + # Split and add the file without extra quoting, + # since later we will just execute the array as is. + for command in ${=handler}; do + zformat -f command $command s:"$1" + execargs+=($command) + done + fi +else + # If there's no %s, the input is supposed to come from stdin. + stdin=1 + if [[ -n $hasmeta ]]; then + execargs=(sh -c "$handler") + else + execargs=(${=handler}) + fi +fi + +# Now execute the command in the appropriate fashion. +if [[ $flags = *copiousoutput* ]]; then + # We need to page the output. + # Careful in case PAGER is a set of commands and arguments. + local -a pager + zstyle -a $context pager pager || pager=(${=PAGER:-more}) + if [[ -n $stdin ]]; then + cat $argv | $execargs | $pager + else + $execargs | eval ${PAGER:-more} + fi +elif [[ $flags = *needsterminal* || -z $DISPLAY ]]; then + # Needs a terminal, so run synchronously. + # Obviously, if $DISPLAY is empty but the handler needs a + # GUI we are in trouble anyway. However, it's possible for + # the handler to be smart about this, like pick-web-browser, + # and even if it just produces an error message it's better to + # have it run synchronously. + if [[ -n $stdin ]]; then + cat $argv | $execargs + else + $execargs + fi +else + # Doesn't need a terminal and we have a $DISPLAY, so run + # it in the background. sh probably isn't smart enough to + # exec the last command in the list, but it's not a big deal. + # + # The following Rococo construction is to try to make + # the job output for the backgrounded command descriptive. + # Otherwise it's equivalent to removing the eval and all the quotes, + # including the (q) flags. + if [[ -n $stdin ]]; then + eval cat ${(q)argv} "|" ${(q)execargs} "&" + else + eval ${(q)execargs} "&" + fi +fi diff --git a/Functions/MIME/zsh-mime-setup b/Functions/MIME/zsh-mime-setup new file mode 100644 index 000000000..5f9168341 --- /dev/null +++ b/Functions/MIME/zsh-mime-setup @@ -0,0 +1,259 @@ +emulate -L zsh +setopt extendedglob cbases + +local opt o_verbose o_list + +autoload -U zsh-mime-handler + +while getopts "flv" opt; do + case $opt in + # List: show existing suffixes and their handlers then exit. + (l) + o_list=1 + ;; + + # Verbose; print diagnostics to stdout. + (v) + o_verbose=1 + ;; + + # Force; discard any existing settings before reading. + (f) + unset -m zsh_mime_\* + ;; + + (*) + [[ $opt = \? ]] || print -r "Option $opt not handled, complain" >&2 + return 1 + ;; + esac +done +(( OPTIND > 1 )) && shift $(( OPTIND - 1 )) + + +if [[ -n $o_list ]]; then + # List and return. + for suffix in ${(ko)zsh_mime_handlers}; do + print ${(r.10.)suffix}${zsh_mime_handlers[$suffix]} + if [[ -n ${zsh_mime_flags[$suffix]} ]]; then + print " flags: ${zsh_mime_flags[$suffix]}" + fi + done + return 0 +fi + + +# Handler for each suffix. +(( ${+zsh_mime_handlers} )) || typeset -gA zsh_mime_handlers +# Corresponding flags, if any, for handler +(( ${+zsh_mime_flags} )) || typeset -gA zsh_mime_flags + +# Internal maps read from MIME configuration files. +# Note we don't remember the types, just the mappings from suffixes +# to handlers and their flags. +typeset -A suffix_type_map type_handler_map type_flags_map + +local -a type_files cap_files array match mbegin mend +local file line type suffix exts elt flags line2 + +# Customizable list of files to examine. +zstyle -a :mime: mime-types type_files || + type_files=(~/.mime.types /etc/mime.types) +zstyle -a :mime: mailcap cap_files || + cap_files=(~/.mailcap /etc/mailcap) + +TRAPEXIT() { unfunction mime-setup-add-type >&/dev/null; return 0; } + +mime-setup-add-type() { + local type suffix + local -a array + + type=$1 + shift + + while (( $# )); do + # `.ps' instead of `ps' has been noted + suffix=${1##.} + shift + + if [[ -z $suffix_type_map[$suffix] ]]; then + [[ -n $o_verbose ]] && + print -r "Adding type $type for $suffix" >&2 + suffix_type_map[$suffix]=$type + else + # Skip duplicates. + array=(${=suffix_type_map[$suffix]}) + if [[ ${array[(I)$type]} -eq 0 ]]; then + [[ -n $o_verbose ]] && + print -r "Appending type $type for already defined $suffix" >&2 + suffix_type_map[$suffix]+=" $type" + fi + fi + done +} + +# Loop through files to find suffixes for MIME types. +# Earlier entries take precedence, so the files need to be listed +# with the user's own first. This also means pre-existing +# values in suffix_type_map are respected. +for file in $type_files; do + [[ -r $file ]] || continue + + # For once we rely on the fact that read handles continuation + # lines ending in backslashes, i.e. there's no -r. + while read line; do + # Skip blank or comment lines. + [[ $line = [[:space:]]#(\#*|) ]] && continue + + # There are two types of line you find in MIME type files. + # The original simple sort contains the type name then suffixes + # separated by whitespace. However, Netscape insists + # on adding lines with backslash continuation with + # key="value" pairs. So we'd better handle both. + if [[ $line = *=* ]]; then + # Gory. + # This relies on the fact that a typical entry: + # type=video/x-mpeg2 desc="MPEG2 Video" exts="mpv2,mp2v" + # looks like a parameter assignment. However, we really + # don't want to be screwed up by future extensions, + # so we split the elements to an array and pick out the + # ones we're interested in. + type= exts= + + # Syntactically split line to preserve quoted words. + array=(${(z)line}) + for elt in $array; do + if [[ $elt = (type|exts)=* ]]; then + eval $elt + fi + done + + # Get extensions by splitting on comma + array=(${(s.,.)exts}) + + [[ -n $type ]] && mime-setup-add-type $type $array + else + # Simple. + mime-setup-add-type ${=line} + fi + done <$file +done + + +# Loop through files to find handlers for types. +for file in $cap_files; do + [[ -r $file ]] || continue + + # Oh, great. We need to preserve backslashes inside the line, + # but need to manage continuation lines. + while read -r line; do + # Skip blank or comment lines. + [[ $line = [[:space:]]#(\#*|) ]] && continue + + while [[ $line = (#b)(*)\\ ]]; do + line=$match[1] + read -r line2 || break + line+=$line2 + done + + # Guess what, this file has a completely different format. + # See mailcap(4). + # The biggest unpleasantness here is that the fields are + # delimited by semicolons, but the command field, which + # is the one we want to extract, may itself contain backslashed + # semicolons. + if [[ $line = (#b)[[:space:]]#([^[:space:]\;]##)[[:space:]]#\;(*) ]] + then + # this is the only form we can handle, but there's no point + # issuing a warning for other forms. + type=$match[1] + line=$match[2] + # See if it has flags after the command. + if [[ $line = (#b)(([^\;\\]|\\\;|\\[^\;])#)\;(*) ]]; then + line=$match[1] + flags=$match[3] + else + flags= + fi + # Remove quotes from semicolons + line=${line//\\\;/\;} + # and remove any surrounding white space --- this might + # make the handler empty. + line=${${line##[[:space:]]#}%%[[:space:]]} + if [[ -z $type_handler_map[$type] ]]; then + if [[ -n $o_verbose ]]; then + print -r "Adding handler for type $type: + $line" >&2 + fi + type_handler_map[$type]=$line + type_flags_map[$type]=$flags + if [[ -n $flags && -n $o_verbose ]]; then + print -r " with flags $flags" >&2 + fi + elif [[ -n $o_verbose ]]; then + print -r "Skipping handler for already defined type $type: + $line" >&2 + if [[ -n $flags ]]; then + print -r " with flags $flags" >&2 + fi + fi + fi + done <$file +done + + +# Check for styles which override whatever is in the file. +# We need to make sure there is a handler set up; for some +# uses we may need to defer checking styles until zsh-mime-handler. +# How much we need to do here is a moot point. +zstyle -L | while read line; do + array=(${(Q)${(z)line}}) + if [[ $array[3] = (handler|flags) && \ + $array[2] = (#b):mime:.([^:]##):(*) ]]; then + suffix=$match[1] + # Make sure there is a suffix alias set up for this. + alias -s $suffix >&/dev/null || alias -s $suffix=zsh-mime-handler + fi +done + +# Now associate the suffixes directly with handlers. +# We just look for the first one with a handler. +# If there is no handler, we don't bother registering an alias +# for the suffix. + +for suffix line in ${(kv)suffix_type_map}; do + # Skip if we already have a handler. + [[ -n $zsh_mime_handlers[$suffix] ]] && continue + + # Split the space-separated list of types. + array=(${=line}) + + # Find the first type with a handler. + line2= + for type in $array; do + line2=${type_handler_map[$type]} + [[ -n $line2 ]] && break + done + + # See if there is a generic type/* handler. + # TODO: do we need to consider other forms of wildcard? + if [[ -z $line2 ]]; then + for type in $array; do + type="${type%%/*}/*" + line2=${type_handler_map[$type]} + [[ -n $line2 ]] && break + done + fi + + if [[ -n $line2 ]]; then + # Found a type with a handler. + # Install the zsh handler as an alias, but never override + # existing suffix handling. + alias -s $suffix >&/dev/null || alias -s $suffix=zsh-mime-handler + + zsh_mime_handlers[$suffix]=$line2 + zsh_mime_flags[$suffix]=$type_flags_map[$type] + fi +done + +true |