1
"""Conversion pipeline templates.
6
Suppose you have some data that you want to convert to another format,
7
such as from GIF image format to PPM image format. Maybe the
8
conversion involves several steps (e.g. piping it through compress or
9
uuencode). Some of the conversion steps may require that their input
10
is a disk file, others may be able to read standard input; similar for
11
their output. The input to the entire conversion may also be read
12
from a disk file or from an open file, and similar for its output.
14
The module lets you construct a pipeline template by sticking one or
15
more conversion steps together. It will take care of creating and
16
removing temporary files if they are necessary to hold intermediate
17
data. You can then use the template to do conversions from many
18
different sources to many different destinations. The temporary
19
file names used are different each time the template is used.
21
The templates are objects so you can create templates for many
22
different conversion steps and store them in a dictionary, for
32
To add a conversion step to a template:
33
t.append(command, kind)
34
where kind is a string of two characters: the first is '-' if the
35
command reads its standard input or 'f' if it requires a file; the
36
second likewise for the output. The command must be valid /bin/sh
37
syntax. If input or output files are required, they are passed as
38
$IN and $OUT; otherwise, it must be possible to use the command in
41
To add a conversion step at the beginning:
42
t.prepend(command, kind)
44
To convert a file to another file using a template:
45
sts = t.copy(infile, outfile)
46
If infile or outfile are the empty string, standard input is read or
47
standard output is written, respectively. The return value is the
48
exit status of the conversion pipeline.
50
To open a file for reading or writing through a conversion pipeline:
51
fp = t.open(file, mode)
52
where mode is 'r' to read the file, or 'w' to write it -- just like
53
for the built-in function open() or for os.popen().
55
To create a new template object initialized to a given one:
58
For an example, see the function test() at the end of the file.
68
__all__ = ["Template"]
70
# Conversion step kinds
72
FILEIN_FILEOUT = 'ff' # Must read & write real files
73
STDIN_FILEOUT = '-f' # Must write a real file
74
FILEIN_STDOUT = 'f-' # Must read a real file
75
STDIN_STDOUT = '--' # Normal pipeline element
76
SOURCE = '.-' # Must be first, writes stdout
77
SINK = '-.' # Must be last, reads stdin
79
stepkinds = [FILEIN_FILEOUT, STDIN_FILEOUT, FILEIN_STDOUT, STDIN_STDOUT, \
84
"""Class representing a pipeline template."""
87
"""Template() returns a fresh pipeline template."""
92
"""t.__repr__() implements repr(t)."""
93
return '<Template instance, steps=%r>' % (self.steps,)
96
"""t.reset() restores a pipeline template to its initial state."""
100
"""t.clone() returns a new pipeline template with identical
101
initial state as the current one."""
103
t.steps = self.steps[:]
104
t.debugging = self.debugging
107
def debug(self, flag):
108
"""t.debug(flag) turns debugging on or off."""
109
self.debugging = flag
111
def append(self, cmd, kind):
112
"""t.append(cmd, kind) adds a new step at the end."""
113
if type(cmd) is not type(''):
115
'Template.append: cmd must be a string'
116
if kind not in stepkinds:
118
'Template.append: bad kind %r' % (kind,)
121
'Template.append: SOURCE can only be prepended'
122
if self.steps and self.steps[-1][1] == SINK:
124
'Template.append: already ends with SINK'
125
if kind[0] == 'f' and not re.search(r'\$IN\b', cmd):
127
'Template.append: missing $IN in cmd'
128
if kind[1] == 'f' and not re.search(r'\$OUT\b', cmd):
130
'Template.append: missing $OUT in cmd'
131
self.steps.append((cmd, kind))
133
def prepend(self, cmd, kind):
134
"""t.prepend(cmd, kind) adds a new step at the front."""
135
if type(cmd) is not type(''):
137
'Template.prepend: cmd must be a string'
138
if kind not in stepkinds:
140
'Template.prepend: bad kind %r' % (kind,)
143
'Template.prepend: SINK can only be appended'
144
if self.steps and self.steps[0][1] == SOURCE:
146
'Template.prepend: already begins with SOURCE'
147
if kind[0] == 'f' and not re.search(r'\$IN\b', cmd):
149
'Template.prepend: missing $IN in cmd'
150
if kind[1] == 'f' and not re.search(r'\$OUT\b', cmd):
152
'Template.prepend: missing $OUT in cmd'
153
self.steps.insert(0, (cmd, kind))
155
def open(self, file, rw):
156
"""t.open(file, rw) returns a pipe or file object open for
157
reading or writing; the file is the other end of the pipeline."""
159
return self.open_r(file)
161
return self.open_w(file)
163
'Template.open: rw must be \'r\' or \'w\', not %r' % (rw,)
165
def open_r(self, file):
166
"""t.open_r(file) and t.open_w(file) implement
167
t.open(file, 'r') and t.open(file, 'w') respectively."""
169
return open(file, 'r')
170
if self.steps[-1][1] == SINK:
172
'Template.open_r: pipeline ends width SINK'
173
cmd = self.makepipeline(file, '')
174
return os.popen(cmd, 'r')
176
def open_w(self, file):
178
return open(file, 'w')
179
if self.steps[0][1] == SOURCE:
181
'Template.open_w: pipeline begins with SOURCE'
182
cmd = self.makepipeline('', file)
183
return os.popen(cmd, 'w')
185
def copy(self, infile, outfile):
186
return os.system(self.makepipeline(infile, outfile))
188
def makepipeline(self, infile, outfile):
189
cmd = makepipeline(infile, self.steps, outfile)
192
cmd = 'set -x; ' + cmd
196
def makepipeline(infile, steps, outfile):
197
# Build a list with for each command:
198
# [input filename or '', command string, kind, output filename or '']
201
for cmd, kind in steps:
202
list.append(['', cmd, kind, ''])
204
# Make sure there is at least one step
207
list.append(['', 'cat', '--', ''])
209
# Take care of the input and output ends
211
[cmd, kind] = list[0][1:3]
212
if kind[0] == 'f' and not infile:
213
list.insert(0, ['', 'cat', '--', ''])
216
[cmd, kind] = list[-1][1:3]
217
if kind[1] == 'f' and not outfile:
218
list.append(['', 'cat', '--', ''])
219
list[-1][-1] = outfile
221
# Invent temporary files to connect stages that need files
224
for i in range(1, len(list)):
227
if lkind[1] == 'f' or rkind[0] == 'f':
228
(fd, temp) = tempfile.mkstemp()
231
list[i-1][-1] = list[i][0] = temp
234
[inf, cmd, kind, outf] = item
236
cmd = 'OUT=' + quote(outf) + '; ' + cmd
238
cmd = 'IN=' + quote(inf) + '; ' + cmd
239
if kind[0] == '-' and inf:
240
cmd = cmd + ' <' + quote(inf)
241
if kind[1] == '-' and outf:
242
cmd = cmd + ' >' + quote(outf)
246
for item in list[1:]:
247
[cmd, kind] = item[1:3]
250
cmd = '{ ' + cmd + '; }'
251
cmdlist = cmdlist + ' |\n' + cmd
253
cmdlist = cmdlist + '\n' + cmd
258
rmcmd = rmcmd + ' ' + quote(file)
259
trapcmd = 'trap ' + quote(rmcmd + '; exit') + ' 1 2 3 13 14 15'
260
cmdlist = trapcmd + '\n' + cmdlist + '\n' + rmcmd
265
# Reliably quote a string as a single argument for /bin/sh
267
_safechars = string.ascii_letters + string.digits + '!@%_-+=:,./' # Safe unquoted
268
_funnychars = '"`$\\' # Unsafe inside "double quotes"
272
if c not in _safechars:
277
return '\'' + file + '\''
283
return '"' + res + '"'
286
# Small test program and example
291
t.append('togif $IN $OUT', 'ff')
292
t.append('giftoppm', '--')
293
t.append('ppmtogif >$OUT', '-f')
294
t.append('fromgif $IN $OUT', 'ff')
296
FILE = '/usr/local/images/rgb/rogues/guido.rgb'
297
t.copy(FILE, '@temp')