about summary refs log tree commit diff
path: root/Functions/MIME/zsh-mime-handler
blob: 0165cc63045b2c1d0eff151e5480487d600e4323 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# 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 no_sh no_bg

zstyle -s $context handler handler ||
  handler="${zsh_mime_handlers[$suffix]}"
zstyle -s $context flags flags ||
  flags="${zsh_mime_flags[$suffix]}"

# Set to yes if we use eval instead of sh -c for complicated mailcap lines
# Can possibly break some mailcap entries which expect sh compatibility,
# but is faster, as a new process is not spawned.
zstyle -t $context current-shell && no_sh=yes

# Set to yes if the process shouldn't be backgrounded even if it doesn't need a
# terminal and display is set.
zstyle -t $context never-background && no_bg=yes

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
    if [[ $no_sh = yes ]]; then
      execargs=(eval $command)
    else
      execargs=(sh -c $command)
    fi
  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 && $no_sh != yes ]]; 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 [[ $no_bg = yes || $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