1*cda5da8dSAndroid Build Coastguard Worker"""Conversion pipeline templates. 2*cda5da8dSAndroid Build Coastguard Worker 3*cda5da8dSAndroid Build Coastguard WorkerThe problem: 4*cda5da8dSAndroid Build Coastguard Worker------------ 5*cda5da8dSAndroid Build Coastguard Worker 6*cda5da8dSAndroid Build Coastguard WorkerSuppose you have some data that you want to convert to another format, 7*cda5da8dSAndroid Build Coastguard Workersuch as from GIF image format to PPM image format. Maybe the 8*cda5da8dSAndroid Build Coastguard Workerconversion involves several steps (e.g. piping it through compress or 9*cda5da8dSAndroid Build Coastguard Workeruuencode). Some of the conversion steps may require that their input 10*cda5da8dSAndroid Build Coastguard Workeris a disk file, others may be able to read standard input; similar for 11*cda5da8dSAndroid Build Coastguard Workertheir output. The input to the entire conversion may also be read 12*cda5da8dSAndroid Build Coastguard Workerfrom a disk file or from an open file, and similar for its output. 13*cda5da8dSAndroid Build Coastguard Worker 14*cda5da8dSAndroid Build Coastguard WorkerThe module lets you construct a pipeline template by sticking one or 15*cda5da8dSAndroid Build Coastguard Workermore conversion steps together. It will take care of creating and 16*cda5da8dSAndroid Build Coastguard Workerremoving temporary files if they are necessary to hold intermediate 17*cda5da8dSAndroid Build Coastguard Workerdata. You can then use the template to do conversions from many 18*cda5da8dSAndroid Build Coastguard Workerdifferent sources to many different destinations. The temporary 19*cda5da8dSAndroid Build Coastguard Workerfile names used are different each time the template is used. 20*cda5da8dSAndroid Build Coastguard Worker 21*cda5da8dSAndroid Build Coastguard WorkerThe templates are objects so you can create templates for many 22*cda5da8dSAndroid Build Coastguard Workerdifferent conversion steps and store them in a dictionary, for 23*cda5da8dSAndroid Build Coastguard Workerinstance. 24*cda5da8dSAndroid Build Coastguard Worker 25*cda5da8dSAndroid Build Coastguard Worker 26*cda5da8dSAndroid Build Coastguard WorkerDirections: 27*cda5da8dSAndroid Build Coastguard Worker----------- 28*cda5da8dSAndroid Build Coastguard Worker 29*cda5da8dSAndroid Build Coastguard WorkerTo create a template: 30*cda5da8dSAndroid Build Coastguard Worker t = Template() 31*cda5da8dSAndroid Build Coastguard Worker 32*cda5da8dSAndroid Build Coastguard WorkerTo add a conversion step to a template: 33*cda5da8dSAndroid Build Coastguard Worker t.append(command, kind) 34*cda5da8dSAndroid Build Coastguard Workerwhere kind is a string of two characters: the first is '-' if the 35*cda5da8dSAndroid Build Coastguard Workercommand reads its standard input or 'f' if it requires a file; the 36*cda5da8dSAndroid Build Coastguard Workersecond likewise for the output. The command must be valid /bin/sh 37*cda5da8dSAndroid Build Coastguard Workersyntax. If input or output files are required, they are passed as 38*cda5da8dSAndroid Build Coastguard Worker$IN and $OUT; otherwise, it must be possible to use the command in 39*cda5da8dSAndroid Build Coastguard Workera pipeline. 40*cda5da8dSAndroid Build Coastguard Worker 41*cda5da8dSAndroid Build Coastguard WorkerTo add a conversion step at the beginning: 42*cda5da8dSAndroid Build Coastguard Worker t.prepend(command, kind) 43*cda5da8dSAndroid Build Coastguard Worker 44*cda5da8dSAndroid Build Coastguard WorkerTo convert a file to another file using a template: 45*cda5da8dSAndroid Build Coastguard Worker sts = t.copy(infile, outfile) 46*cda5da8dSAndroid Build Coastguard WorkerIf infile or outfile are the empty string, standard input is read or 47*cda5da8dSAndroid Build Coastguard Workerstandard output is written, respectively. The return value is the 48*cda5da8dSAndroid Build Coastguard Workerexit status of the conversion pipeline. 49*cda5da8dSAndroid Build Coastguard Worker 50*cda5da8dSAndroid Build Coastguard WorkerTo open a file for reading or writing through a conversion pipeline: 51*cda5da8dSAndroid Build Coastguard Worker fp = t.open(file, mode) 52*cda5da8dSAndroid Build Coastguard Workerwhere mode is 'r' to read the file, or 'w' to write it -- just like 53*cda5da8dSAndroid Build Coastguard Workerfor the built-in function open() or for os.popen(). 54*cda5da8dSAndroid Build Coastguard Worker 55*cda5da8dSAndroid Build Coastguard WorkerTo create a new template object initialized to a given one: 56*cda5da8dSAndroid Build Coastguard Worker t2 = t.clone() 57*cda5da8dSAndroid Build Coastguard Worker""" # ' 58*cda5da8dSAndroid Build Coastguard Worker 59*cda5da8dSAndroid Build Coastguard Worker 60*cda5da8dSAndroid Build Coastguard Workerimport re 61*cda5da8dSAndroid Build Coastguard Workerimport os 62*cda5da8dSAndroid Build Coastguard Workerimport tempfile 63*cda5da8dSAndroid Build Coastguard Workerimport warnings 64*cda5da8dSAndroid Build Coastguard Worker# we import the quote function rather than the module for backward compat 65*cda5da8dSAndroid Build Coastguard Worker# (quote used to be an undocumented but used function in pipes) 66*cda5da8dSAndroid Build Coastguard Workerfrom shlex import quote 67*cda5da8dSAndroid Build Coastguard Worker 68*cda5da8dSAndroid Build Coastguard Workerwarnings._deprecated(__name__, remove=(3, 13)) 69*cda5da8dSAndroid Build Coastguard Worker 70*cda5da8dSAndroid Build Coastguard Worker__all__ = ["Template"] 71*cda5da8dSAndroid Build Coastguard Worker 72*cda5da8dSAndroid Build Coastguard Worker# Conversion step kinds 73*cda5da8dSAndroid Build Coastguard Worker 74*cda5da8dSAndroid Build Coastguard WorkerFILEIN_FILEOUT = 'ff' # Must read & write real files 75*cda5da8dSAndroid Build Coastguard WorkerSTDIN_FILEOUT = '-f' # Must write a real file 76*cda5da8dSAndroid Build Coastguard WorkerFILEIN_STDOUT = 'f-' # Must read a real file 77*cda5da8dSAndroid Build Coastguard WorkerSTDIN_STDOUT = '--' # Normal pipeline element 78*cda5da8dSAndroid Build Coastguard WorkerSOURCE = '.-' # Must be first, writes stdout 79*cda5da8dSAndroid Build Coastguard WorkerSINK = '-.' # Must be last, reads stdin 80*cda5da8dSAndroid Build Coastguard Worker 81*cda5da8dSAndroid Build Coastguard Workerstepkinds = [FILEIN_FILEOUT, STDIN_FILEOUT, FILEIN_STDOUT, STDIN_STDOUT, \ 82*cda5da8dSAndroid Build Coastguard Worker SOURCE, SINK] 83*cda5da8dSAndroid Build Coastguard Worker 84*cda5da8dSAndroid Build Coastguard Worker 85*cda5da8dSAndroid Build Coastguard Workerclass Template: 86*cda5da8dSAndroid Build Coastguard Worker """Class representing a pipeline template.""" 87*cda5da8dSAndroid Build Coastguard Worker 88*cda5da8dSAndroid Build Coastguard Worker def __init__(self): 89*cda5da8dSAndroid Build Coastguard Worker """Template() returns a fresh pipeline template.""" 90*cda5da8dSAndroid Build Coastguard Worker self.debugging = 0 91*cda5da8dSAndroid Build Coastguard Worker self.reset() 92*cda5da8dSAndroid Build Coastguard Worker 93*cda5da8dSAndroid Build Coastguard Worker def __repr__(self): 94*cda5da8dSAndroid Build Coastguard Worker """t.__repr__() implements repr(t).""" 95*cda5da8dSAndroid Build Coastguard Worker return '<Template instance, steps=%r>' % (self.steps,) 96*cda5da8dSAndroid Build Coastguard Worker 97*cda5da8dSAndroid Build Coastguard Worker def reset(self): 98*cda5da8dSAndroid Build Coastguard Worker """t.reset() restores a pipeline template to its initial state.""" 99*cda5da8dSAndroid Build Coastguard Worker self.steps = [] 100*cda5da8dSAndroid Build Coastguard Worker 101*cda5da8dSAndroid Build Coastguard Worker def clone(self): 102*cda5da8dSAndroid Build Coastguard Worker """t.clone() returns a new pipeline template with identical 103*cda5da8dSAndroid Build Coastguard Worker initial state as the current one.""" 104*cda5da8dSAndroid Build Coastguard Worker t = Template() 105*cda5da8dSAndroid Build Coastguard Worker t.steps = self.steps[:] 106*cda5da8dSAndroid Build Coastguard Worker t.debugging = self.debugging 107*cda5da8dSAndroid Build Coastguard Worker return t 108*cda5da8dSAndroid Build Coastguard Worker 109*cda5da8dSAndroid Build Coastguard Worker def debug(self, flag): 110*cda5da8dSAndroid Build Coastguard Worker """t.debug(flag) turns debugging on or off.""" 111*cda5da8dSAndroid Build Coastguard Worker self.debugging = flag 112*cda5da8dSAndroid Build Coastguard Worker 113*cda5da8dSAndroid Build Coastguard Worker def append(self, cmd, kind): 114*cda5da8dSAndroid Build Coastguard Worker """t.append(cmd, kind) adds a new step at the end.""" 115*cda5da8dSAndroid Build Coastguard Worker if not isinstance(cmd, str): 116*cda5da8dSAndroid Build Coastguard Worker raise TypeError('Template.append: cmd must be a string') 117*cda5da8dSAndroid Build Coastguard Worker if kind not in stepkinds: 118*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.append: bad kind %r' % (kind,)) 119*cda5da8dSAndroid Build Coastguard Worker if kind == SOURCE: 120*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.append: SOURCE can only be prepended') 121*cda5da8dSAndroid Build Coastguard Worker if self.steps and self.steps[-1][1] == SINK: 122*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.append: already ends with SINK') 123*cda5da8dSAndroid Build Coastguard Worker if kind[0] == 'f' and not re.search(r'\$IN\b', cmd): 124*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.append: missing $IN in cmd') 125*cda5da8dSAndroid Build Coastguard Worker if kind[1] == 'f' and not re.search(r'\$OUT\b', cmd): 126*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.append: missing $OUT in cmd') 127*cda5da8dSAndroid Build Coastguard Worker self.steps.append((cmd, kind)) 128*cda5da8dSAndroid Build Coastguard Worker 129*cda5da8dSAndroid Build Coastguard Worker def prepend(self, cmd, kind): 130*cda5da8dSAndroid Build Coastguard Worker """t.prepend(cmd, kind) adds a new step at the front.""" 131*cda5da8dSAndroid Build Coastguard Worker if not isinstance(cmd, str): 132*cda5da8dSAndroid Build Coastguard Worker raise TypeError('Template.prepend: cmd must be a string') 133*cda5da8dSAndroid Build Coastguard Worker if kind not in stepkinds: 134*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.prepend: bad kind %r' % (kind,)) 135*cda5da8dSAndroid Build Coastguard Worker if kind == SINK: 136*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.prepend: SINK can only be appended') 137*cda5da8dSAndroid Build Coastguard Worker if self.steps and self.steps[0][1] == SOURCE: 138*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.prepend: already begins with SOURCE') 139*cda5da8dSAndroid Build Coastguard Worker if kind[0] == 'f' and not re.search(r'\$IN\b', cmd): 140*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.prepend: missing $IN in cmd') 141*cda5da8dSAndroid Build Coastguard Worker if kind[1] == 'f' and not re.search(r'\$OUT\b', cmd): 142*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.prepend: missing $OUT in cmd') 143*cda5da8dSAndroid Build Coastguard Worker self.steps.insert(0, (cmd, kind)) 144*cda5da8dSAndroid Build Coastguard Worker 145*cda5da8dSAndroid Build Coastguard Worker def open(self, file, rw): 146*cda5da8dSAndroid Build Coastguard Worker """t.open(file, rw) returns a pipe or file object open for 147*cda5da8dSAndroid Build Coastguard Worker reading or writing; the file is the other end of the pipeline.""" 148*cda5da8dSAndroid Build Coastguard Worker if rw == 'r': 149*cda5da8dSAndroid Build Coastguard Worker return self.open_r(file) 150*cda5da8dSAndroid Build Coastguard Worker if rw == 'w': 151*cda5da8dSAndroid Build Coastguard Worker return self.open_w(file) 152*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.open: rw must be \'r\' or \'w\', not %r' 153*cda5da8dSAndroid Build Coastguard Worker % (rw,)) 154*cda5da8dSAndroid Build Coastguard Worker 155*cda5da8dSAndroid Build Coastguard Worker def open_r(self, file): 156*cda5da8dSAndroid Build Coastguard Worker """t.open_r(file) and t.open_w(file) implement 157*cda5da8dSAndroid Build Coastguard Worker t.open(file, 'r') and t.open(file, 'w') respectively.""" 158*cda5da8dSAndroid Build Coastguard Worker if not self.steps: 159*cda5da8dSAndroid Build Coastguard Worker return open(file, 'r') 160*cda5da8dSAndroid Build Coastguard Worker if self.steps[-1][1] == SINK: 161*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.open_r: pipeline ends width SINK') 162*cda5da8dSAndroid Build Coastguard Worker cmd = self.makepipeline(file, '') 163*cda5da8dSAndroid Build Coastguard Worker return os.popen(cmd, 'r') 164*cda5da8dSAndroid Build Coastguard Worker 165*cda5da8dSAndroid Build Coastguard Worker def open_w(self, file): 166*cda5da8dSAndroid Build Coastguard Worker if not self.steps: 167*cda5da8dSAndroid Build Coastguard Worker return open(file, 'w') 168*cda5da8dSAndroid Build Coastguard Worker if self.steps[0][1] == SOURCE: 169*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Template.open_w: pipeline begins with SOURCE') 170*cda5da8dSAndroid Build Coastguard Worker cmd = self.makepipeline('', file) 171*cda5da8dSAndroid Build Coastguard Worker return os.popen(cmd, 'w') 172*cda5da8dSAndroid Build Coastguard Worker 173*cda5da8dSAndroid Build Coastguard Worker def copy(self, infile, outfile): 174*cda5da8dSAndroid Build Coastguard Worker return os.system(self.makepipeline(infile, outfile)) 175*cda5da8dSAndroid Build Coastguard Worker 176*cda5da8dSAndroid Build Coastguard Worker def makepipeline(self, infile, outfile): 177*cda5da8dSAndroid Build Coastguard Worker cmd = makepipeline(infile, self.steps, outfile) 178*cda5da8dSAndroid Build Coastguard Worker if self.debugging: 179*cda5da8dSAndroid Build Coastguard Worker print(cmd) 180*cda5da8dSAndroid Build Coastguard Worker cmd = 'set -x; ' + cmd 181*cda5da8dSAndroid Build Coastguard Worker return cmd 182*cda5da8dSAndroid Build Coastguard Worker 183*cda5da8dSAndroid Build Coastguard Worker 184*cda5da8dSAndroid Build Coastguard Workerdef makepipeline(infile, steps, outfile): 185*cda5da8dSAndroid Build Coastguard Worker # Build a list with for each command: 186*cda5da8dSAndroid Build Coastguard Worker # [input filename or '', command string, kind, output filename or ''] 187*cda5da8dSAndroid Build Coastguard Worker 188*cda5da8dSAndroid Build Coastguard Worker list = [] 189*cda5da8dSAndroid Build Coastguard Worker for cmd, kind in steps: 190*cda5da8dSAndroid Build Coastguard Worker list.append(['', cmd, kind, '']) 191*cda5da8dSAndroid Build Coastguard Worker # 192*cda5da8dSAndroid Build Coastguard Worker # Make sure there is at least one step 193*cda5da8dSAndroid Build Coastguard Worker # 194*cda5da8dSAndroid Build Coastguard Worker if not list: 195*cda5da8dSAndroid Build Coastguard Worker list.append(['', 'cat', '--', '']) 196*cda5da8dSAndroid Build Coastguard Worker # 197*cda5da8dSAndroid Build Coastguard Worker # Take care of the input and output ends 198*cda5da8dSAndroid Build Coastguard Worker # 199*cda5da8dSAndroid Build Coastguard Worker [cmd, kind] = list[0][1:3] 200*cda5da8dSAndroid Build Coastguard Worker if kind[0] == 'f' and not infile: 201*cda5da8dSAndroid Build Coastguard Worker list.insert(0, ['', 'cat', '--', '']) 202*cda5da8dSAndroid Build Coastguard Worker list[0][0] = infile 203*cda5da8dSAndroid Build Coastguard Worker # 204*cda5da8dSAndroid Build Coastguard Worker [cmd, kind] = list[-1][1:3] 205*cda5da8dSAndroid Build Coastguard Worker if kind[1] == 'f' and not outfile: 206*cda5da8dSAndroid Build Coastguard Worker list.append(['', 'cat', '--', '']) 207*cda5da8dSAndroid Build Coastguard Worker list[-1][-1] = outfile 208*cda5da8dSAndroid Build Coastguard Worker # 209*cda5da8dSAndroid Build Coastguard Worker # Invent temporary files to connect stages that need files 210*cda5da8dSAndroid Build Coastguard Worker # 211*cda5da8dSAndroid Build Coastguard Worker garbage = [] 212*cda5da8dSAndroid Build Coastguard Worker for i in range(1, len(list)): 213*cda5da8dSAndroid Build Coastguard Worker lkind = list[i-1][2] 214*cda5da8dSAndroid Build Coastguard Worker rkind = list[i][2] 215*cda5da8dSAndroid Build Coastguard Worker if lkind[1] == 'f' or rkind[0] == 'f': 216*cda5da8dSAndroid Build Coastguard Worker (fd, temp) = tempfile.mkstemp() 217*cda5da8dSAndroid Build Coastguard Worker os.close(fd) 218*cda5da8dSAndroid Build Coastguard Worker garbage.append(temp) 219*cda5da8dSAndroid Build Coastguard Worker list[i-1][-1] = list[i][0] = temp 220*cda5da8dSAndroid Build Coastguard Worker # 221*cda5da8dSAndroid Build Coastguard Worker for item in list: 222*cda5da8dSAndroid Build Coastguard Worker [inf, cmd, kind, outf] = item 223*cda5da8dSAndroid Build Coastguard Worker if kind[1] == 'f': 224*cda5da8dSAndroid Build Coastguard Worker cmd = 'OUT=' + quote(outf) + '; ' + cmd 225*cda5da8dSAndroid Build Coastguard Worker if kind[0] == 'f': 226*cda5da8dSAndroid Build Coastguard Worker cmd = 'IN=' + quote(inf) + '; ' + cmd 227*cda5da8dSAndroid Build Coastguard Worker if kind[0] == '-' and inf: 228*cda5da8dSAndroid Build Coastguard Worker cmd = cmd + ' <' + quote(inf) 229*cda5da8dSAndroid Build Coastguard Worker if kind[1] == '-' and outf: 230*cda5da8dSAndroid Build Coastguard Worker cmd = cmd + ' >' + quote(outf) 231*cda5da8dSAndroid Build Coastguard Worker item[1] = cmd 232*cda5da8dSAndroid Build Coastguard Worker # 233*cda5da8dSAndroid Build Coastguard Worker cmdlist = list[0][1] 234*cda5da8dSAndroid Build Coastguard Worker for item in list[1:]: 235*cda5da8dSAndroid Build Coastguard Worker [cmd, kind] = item[1:3] 236*cda5da8dSAndroid Build Coastguard Worker if item[0] == '': 237*cda5da8dSAndroid Build Coastguard Worker if 'f' in kind: 238*cda5da8dSAndroid Build Coastguard Worker cmd = '{ ' + cmd + '; }' 239*cda5da8dSAndroid Build Coastguard Worker cmdlist = cmdlist + ' |\n' + cmd 240*cda5da8dSAndroid Build Coastguard Worker else: 241*cda5da8dSAndroid Build Coastguard Worker cmdlist = cmdlist + '\n' + cmd 242*cda5da8dSAndroid Build Coastguard Worker # 243*cda5da8dSAndroid Build Coastguard Worker if garbage: 244*cda5da8dSAndroid Build Coastguard Worker rmcmd = 'rm -f' 245*cda5da8dSAndroid Build Coastguard Worker for file in garbage: 246*cda5da8dSAndroid Build Coastguard Worker rmcmd = rmcmd + ' ' + quote(file) 247*cda5da8dSAndroid Build Coastguard Worker trapcmd = 'trap ' + quote(rmcmd + '; exit') + ' 1 2 3 13 14 15' 248*cda5da8dSAndroid Build Coastguard Worker cmdlist = trapcmd + '\n' + cmdlist + '\n' + rmcmd 249*cda5da8dSAndroid Build Coastguard Worker # 250*cda5da8dSAndroid Build Coastguard Worker return cmdlist 251