76213c3006eecf0b5765999d57e144e3fac0a84a
[OpenColorIO-Configs.git] / aces_1.0.0 / python / aces_ocio / process.py
1 #!/usr/bin/env python\r
2 # -*- coding: utf-8 -*-\r
3 \r
4 """\r
5 A process wrapper class that maintains the text output and execution status\r
6 of a process or a list of other process wrappers which carry such data.\r
7 """\r
8 \r
9 import os\r
10 import sys\r
11 import traceback\r
12 \r
13 \r
14 def readText(textFile):\r
15     if (textFile != ""):\r
16         fp = open(textFile, 'rb')\r
17         # Create a text/plain message\r
18         text = (fp.read())\r
19         fp.close()\r
20     return text\r
21 \r
22 \r
23 # readText\r
24 \r
25 def writeText(text, textFile):\r
26     if (textFile != ""):\r
27         fp = open(textFile, 'wb')\r
28         # Create a text/plain message\r
29         fp.write(text)\r
30         fp.close()\r
31     return text\r
32 \r
33 \r
34 # readText\r
35 \r
36 class Process:\r
37     """\r
38     A process with logged output\r
39     """\r
40 \r
41     def __init__(self,\r
42                  description=None,\r
43                  cmd=None,\r
44                  args=[],\r
45                  cwd=None,\r
46                  env=None,\r
47                  batchWrapper=False):\r
48         """Initialize the standard class variables"""\r
49         self.cmd = cmd\r
50         if not description:\r
51             self.description = cmd\r
52         else:\r
53             self.description = description\r
54         self.status = None\r
55         self.args = args\r
56         self.start = None\r
57         self.end = None\r
58         self.log = []\r
59         self.echo = True\r
60         self.cwd = cwd\r
61         self.env = env\r
62         self.batchWrapper = batchWrapper\r
63         self.processKeys = []\r
64 \r
65     # __init__\r
66 \r
67     def getElapsedSeconds(self):\r
68         import math\r
69 \r
70         if self.end and self.start:\r
71             delta = (self.end - self.start)\r
72             formatted = "%s.%s" % (delta.days * 86400 + delta.seconds,\r
73                                    int(math.floor(delta.microseconds / 1e3)))\r
74         else:\r
75             formatted = None\r
76         return formatted\r
77 \r
78     # getElapsedtime\r
79 \r
80     def writeKey(self, writeDict, key=None, value=None, startStop=None):\r
81         "Write a key, value pair in a supported format"\r
82         if key != None and (value != None or startStop != None):\r
83             indent = '\t' * writeDict['indentationLevel']\r
84             if writeDict['format'] == 'xml':\r
85                 if startStop == 'start':\r
86                     writeDict['logHandle'].write("%s<%s>\n" % (indent, key))\r
87                 elif startStop == 'stop':\r
88                     writeDict['logHandle'].write("%s</%s>\n" % (indent, key))\r
89                 else:\r
90                     writeDict['logHandle'].write(\r
91                         "%s<%s>%s</%s>\n" % (indent, key, value, key))\r
92             else:  # writeDict['format'] == 'txt':\r
93                 writeDict['logHandle'].write(\r
94                     "%s%40s : %s\n" % (indent, key, value))\r
95 \r
96     def writeLogHeader(self, writeDict):\r
97         import platform\r
98 \r
99         # Retrieve operating environment information\r
100         user = None\r
101         try:\r
102             user = os.getlogin()\r
103         except:\r
104             try:\r
105                 user = os.getenv("USERNAME")\r
106                 if user == None:\r
107                     user = os.getenv("USER")\r
108             except:\r
109                 user = "unknown_user"\r
110         try:\r
111             (sysname, nodename, release, version, machine,\r
112              processor) = platform.uname()\r
113         except:\r
114             (sysname, nodename, release, version, machine, processor) = (\r
115                 "unknown_sysname", "unknown_nodename", "unknown_release",\r
116                 "unknown_version", "unknown_machine", "unknown_processor")\r
117         try:\r
118             hostname = platform.node()\r
119         except:\r
120             hostname = "unknown_hostname"\r
121 \r
122         self.writeKey(writeDict, 'process', None, 'start')\r
123         writeDict['indentationLevel'] += 1\r
124 \r
125         self.writeKey(writeDict, 'description', self.description)\r
126         self.writeKey(writeDict, 'cmd', self.cmd)\r
127         if self.args: self.writeKey(writeDict, 'args', ' '.join(self.args))\r
128         self.writeKey(writeDict, 'start', self.start)\r
129         self.writeKey(writeDict, 'end', self.end)\r
130         self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds())\r
131 \r
132         self.writeKey(writeDict, 'user', user)\r
133         self.writeKey(writeDict, 'sysname', sysname)\r
134         self.writeKey(writeDict, 'nodename', nodename)\r
135         self.writeKey(writeDict, 'release', release)\r
136         self.writeKey(writeDict, 'version', version)\r
137         self.writeKey(writeDict, 'machine', machine)\r
138         self.writeKey(writeDict, 'processor', processor)\r
139 \r
140         if len(self.processKeys) > 0:\r
141             self.writeKey(writeDict, 'processKeys', None, 'start')\r
142             for pair in self.processKeys:\r
143                 (key, value) = pair\r
144                 writeDict['indentationLevel'] += 1\r
145                 self.writeKey(writeDict, key, value)\r
146                 writeDict['indentationLevel'] -= 1\r
147             self.writeKey(writeDict, 'processKeys', None, 'stop')\r
148 \r
149         self.writeKey(writeDict, 'status', self.status)\r
150 \r
151     # writeLogHeader\r
152 \r
153     def writeLogFooter(self, writeDict):\r
154         writeDict['indentationLevel'] -= 1\r
155         self.writeKey(writeDict, 'process', None, 'stop')\r
156 \r
157     # writeLogFooter\r
158 \r
159     def writeLog(self, logHandle=sys.stdout, indentationLevel=0, format='xml'):\r
160         """\r
161         Write logging information to the specified handle\r
162         """\r
163 \r
164         writeDict = {}\r
165         writeDict['logHandle'] = logHandle\r
166         writeDict['indentationLevel'] = indentationLevel\r
167         writeDict['format'] = format\r
168 \r
169         if logHandle:\r
170             self.writeLogHeader(writeDict)\r
171 \r
172             if self.log:\r
173                 self.writeKey(writeDict, 'output', None, 'start')\r
174                 if format == 'xml':\r
175                     logHandle.write("<![CDATA[\n")\r
176                 for line in self.log:\r
177                     logHandle.write('%s%s\n' % ("", line))\r
178                 if format == 'xml':\r
179                     logHandle.write("]]>\n")\r
180                 self.writeKey(writeDict, 'output', None, 'stop')\r
181 \r
182             self.writeLogFooter(writeDict)\r
183 \r
184     # writeLog\r
185 \r
186     def writeLogToDisk(self, logFilename=None, format='xml', header=None):\r
187         if logFilename:\r
188             try:\r
189                 # This also doesn't seem like the best structure...\r
190                 # 3.1\r
191                 try:\r
192                     logHandle = open(logFilename, mode='wt', encoding="utf-8")\r
193                 # 2.6\r
194                 except:\r
195                     logHandle = open(logFilename, mode='wt')\r
196             except:\r
197                 print("Couldn't open log : %s" % logFilename)\r
198                 logHandle = None\r
199 \r
200         if logHandle:\r
201             if header:\r
202                 if format == 'xml':\r
203                     logHandle.write("<![CDATA[\n")\r
204                 logHandle.write(header)\r
205                 if format == 'xml':\r
206                     logHandle.write("]]>\n")\r
207             self.writeLog(logHandle)\r
208             logHandle.close()\r
209 \r
210     # writeLogToDisk\r
211 \r
212     def logLine(self, line):\r
213         "Add a line of text to the log"\r
214         self.log.append(line.rstrip())\r
215         if self.echo:\r
216             print("%s" % line.rstrip())\r
217 \r
218     # logLine\r
219 \r
220     def execute(self):\r
221         """\r
222         Execute this process\r
223         """\r
224         import re\r
225         import datetime\r
226         import traceback\r
227 \r
228         try:\r
229             import subprocess as sp\r
230         except:\r
231             sp = None\r
232 \r
233         self.start = datetime.datetime.now()\r
234 \r
235         cmdargs = [self.cmd]\r
236         cmdargs.extend(self.args)\r
237 \r
238         if self.echo:\r
239             if sp:\r
240                 print(\r
241                     "\n%s : %s\n" % (self.__class__, sp.list2cmdline(cmdargs)))\r
242             else:\r
243                 print("\n%s : %s\n" % (self.__class__, " ".join(cmdargs)))\r
244 \r
245         # intialize a few variables that may or may not be set later\r
246         process = None\r
247         tmpWrapper = None\r
248         stdout = None\r
249         stdin = None\r
250         parentenv = os.environ\r
251         parentcwd = os.getcwd()\r
252 \r
253         try:\r
254             # Using subprocess\r
255             if sp:\r
256                 if self.batchWrapper:\r
257                     cmd = " ".join(cmdargs)\r
258                     tmpWrapper = os.path.join(self.cwd, "process.bat")\r
259                     writeText(cmd, tmpWrapper)\r
260                     print("%s : Running process through wrapper %s\n" % (\r
261                         self.__class__, tmpWrapper))\r
262                     process = sp.Popen([tmpWrapper], stdout=sp.PIPE,\r
263                                        stderr=sp.STDOUT,\r
264                                        cwd=self.cwd, env=self.env)\r
265                 else:\r
266                     process = sp.Popen(cmdargs, stdout=sp.PIPE,\r
267                                        stderr=sp.STDOUT,\r
268                                        cwd=self.cwd, env=self.env)\r
269 \r
270             # using os.popen4\r
271             else:\r
272                 if self.env:\r
273                     os.environ = self.env\r
274                 if self.cwd:\r
275                     os.chdir(self.cwd)\r
276 \r
277                 stdin, stdout = os.popen4(cmdargs, 'r')\r
278         except:\r
279             print("Couldn't execute command : %s" % cmdargs[0])\r
280             traceback.print_exc()\r
281 \r
282         # Using subprocess\r
283         if sp:\r
284             if process != None:\r
285                 # pid = process.pid\r
286                 # log.logLine("process id %s\n" % pid)\r
287 \r
288                 try:\r
289                     # This is more proper python, and resolves some issues with\r
290                     # a process ending before all of its output has been\r
291                     # processed, but it also seems to stall when the read\r
292                     # buffer is near or over it's limit. this happens\r
293                     # relatively frequently with processes that generate lots\r
294                     # of print statements.\r
295                     for line in process.stdout:\r
296                         self.logLine(line)\r
297                     #\r
298                     # So we go with the, um, uglier  option below\r
299 \r
300                     # This is now used to ensure that the process has finished\r
301                     line = ""\r
302                     while line != None and process.poll() == None:\r
303                         try:\r
304                             line = process.stdout.readline()\r
305                         except:\r
306                             break\r
307                         # 3.1\r
308                         try:\r
309                             self.logLine(str(line, encoding="utf-8"))\r
310                         # 2.6\r
311                         except:\r
312                             self.logLine(line)\r
313                 except:\r
314                     self.logLine("Logging error : %s" % sys.exc_info()[0])\r
315 \r
316                 self.status = process.returncode\r
317 \r
318                 if self.batchWrapper and tmpWrapper:\r
319                     try:\r
320                         os.remove(tmpWrapper)\r
321                     except:\r
322                         print("Couldn't remove temp wrapper : %s" % tmpWrapper)\r
323                         traceback.print_exc()\r
324 \r
325         # Using os.popen4\r
326         else:\r
327             exitCode = -1\r
328             try:\r
329                 # print("reading stdout lines")\r
330                 stdoutLines = stdout.readlines()\r
331                 exitCode = stdout.close()\r
332 \r
333                 stdout.close()\r
334                 stdin.close()\r
335 \r
336                 if self.env:\r
337                     os.environ = parentenv\r
338                 if self.cwd:\r
339                     os.chdir(parentcwd)\r
340 \r
341                 if len(stdoutLines) > 0:\r
342                     for line in stdoutLines:\r
343                         self.logLine(line)\r
344 \r
345                 if not exitCode:\r
346                     exitCode = 0\r
347             except:\r
348                 self.logLine("Logging error : %s" % sys.exc_info()[0])\r
349 \r
350             self.status = exitCode\r
351 \r
352         self.end = datetime.datetime.now()\r
353         # execute\r
354 \r
355 \r
356 # Process\r
357 \r
358 class ProcessList(Process):\r
359     """\r
360     A list of processes with logged output\r
361     """\r
362 \r
363     def __init__(self, description, blocking=True, cwd=None, env=None):\r
364         Process.__init__(self, description, None, None, cwd, env)\r
365         "Initialize the standard class variables"\r
366         self.processes = []\r
367         self.blocking = blocking\r
368 \r
369     # __init__\r
370 \r
371     def generateReport(self, writeDict):\r
372         """\r
373         Generate a log based on the success of the child processes\r
374         """\r
375         if self.processes:\r
376             _status = True\r
377             indent = '\t' * (writeDict['indentationLevel'] + 1)\r
378 \r
379             self.log = []\r
380 \r
381             for child in self.processes:\r
382                 if isinstance(child, ProcessList):\r
383                     child.generateReport(writeDict)\r
384 \r
385                 childResult = ""\r
386                 key = child.description\r
387                 value = child.status\r
388                 if writeDict['format'] == 'xml':\r
389                     childResult = (\r
390                         "%s<result description=\"%s\">%s</result>" % (\r
391                             indent, key, value))\r
392                 else:  # writeDict['format'] == 'txt':\r
393                     childResult = ("%s%40s : %s" % (indent, key, value))\r
394                 self.log.append(childResult)\r
395 \r
396                 if child.status != 0:\r
397                     _status = False\r
398             if not _status:\r
399                 self.status = -1\r
400             else:\r
401                 self.status = 0\r
402         else:\r
403             self.log = ["No child processes available to generate a report"]\r
404             self.status = -1\r
405 \r
406     def writeLogHeader(self, writeDict):\r
407         self.writeKey(writeDict, 'processList', None, 'start')\r
408         writeDict['indentationLevel'] += 1\r
409 \r
410         self.writeKey(writeDict, 'description', self.description)\r
411         self.writeKey(writeDict, 'start', self.start)\r
412         self.writeKey(writeDict, 'end', self.end)\r
413         self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds())\r
414 \r
415         self.generateReport(writeDict)\r
416 \r
417         self.writeKey(writeDict, 'status', self.status)\r
418 \r
419     # writeLogHeader\r
420 \r
421     def writeLogFooter(self, writeDict):\r
422         writeDict['indentationLevel'] -= 1\r
423         self.writeKey(writeDict, 'processList', None, 'stop')\r
424 \r
425     # writeLogFooter\r
426 \r
427     def writeLog(self, logHandle=sys.stdout, indentationLevel=0, format='xml'):\r
428         """\r
429         Write logging information to the specified handle\r
430         """\r
431 \r
432         writeDict = {}\r
433         writeDict['logHandle'] = logHandle\r
434         writeDict['indentationLevel'] = indentationLevel\r
435         writeDict['format'] = format\r
436 \r
437         if logHandle:\r
438             self.writeLogHeader(writeDict)\r
439 \r
440             if self.log:\r
441                 self.writeKey(writeDict, 'output', None, 'start')\r
442                 for line in self.log:\r
443                     logHandle.write('%s%s\n' % ("", line))\r
444                 self.writeKey(writeDict, 'output', None, 'stop')\r
445 \r
446             if self.processes:\r
447                 self.writeKey(writeDict, 'processes', None, 'start')\r
448                 for child in self.processes:\r
449                     child.writeLog(logHandle, indentationLevel + 1, format)\r
450                 self.writeKey(writeDict, 'processes', None, 'stop')\r
451 \r
452             self.writeLogFooter(writeDict)\r
453 \r
454     # writeLog\r
455 \r
456     def execute(self):\r
457         """\r
458         Execute this list of processes\r
459         """\r
460         import datetime\r
461 \r
462         self.start = datetime.datetime.now()\r
463 \r
464         self.status = 0\r
465         if self.processes:\r
466             for child in self.processes:\r
467                 if child:\r
468                     try:\r
469                         child.execute()\r
470                     except:\r
471                         print("%s : caught exception in child class %s" % (\r
472                             self.__class__, child.__class__))\r
473                         traceback.print_exc()\r
474                         child.status = -1\r
475 \r
476                     if self.blocking and child.status != 0:\r
477                         print("%s : child class %s finished with an error" % (\r
478                             self.__class__, child.__class__))\r
479                         self.status = -1\r
480                         break\r
481 \r
482         self.end = datetime.datetime.now()\r
483         # execute\r
484 \r
485 \r
486 # ProcessList\r
487 \r
488 def main():\r
489     import optparse\r
490 \r
491     p = optparse.OptionParser(description='A process logging script',\r
492                               prog='process',\r
493                               version='process 0.1',\r
494                               usage=('%prog [options] '\r
495                                      '[options for the logged process]'))\r
496     p.add_option('--cmd', '-c', default=None)\r
497     p.add_option('--log', '-l', default=None)\r
498 \r
499     options, arguments = p.parse_args()\r
500 \r
501     #\r
502     # Get options\r
503     # \r
504     cmd = options.cmd\r
505     logFilename = options.log\r
506 \r
507     try:\r
508         argsStart = sys.argv.index('--') + 1\r
509         args = sys.argv[argsStart:]\r
510     except:\r
511         argsStart = len(sys.argv) + 1\r
512         args = []\r
513 \r
514     if cmd == None:\r
515         print("process: No command specified")\r
516 \r
517     #\r
518     # Test regular logging\r
519     #\r
520     process = Process(description="a process", cmd=cmd, args=args)\r
521 \r
522     #\r
523     # Test report generation and writing a log\r
524     #\r
525     processList = ProcessList("a process list")\r
526     processList.processes.append(process)\r
527     processList.echo = True\r
528     processList.execute()\r
529 \r
530     processList.writeLogToDisk(logFilename)\r
531 \r
532 # main\r
533 \r
534 if __name__ == '__main__':\r
535     main()\r