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')