Add top level "aces_ocio" python package and preliminary unit tests.
[OpenColorIO-Configs.git] / aces_1.0.0 / python / aces_ocio / process.py
diff --git a/aces_1.0.0/python/aces_ocio/process.py b/aces_1.0.0/python/aces_ocio/process.py
new file mode 100755 (executable)
index 0000000..17347ee
--- /dev/null
@@ -0,0 +1,474 @@
+#!/usr/bin/python2.6\r
+\r
+'''A process wrapper class that maintains the text output and execution status of a process\r
+or a list of other process wrappers which carry such data.'''\r
+\r
+import os\r
+import sys\r
+import traceback\r
+\r
+def readText(textFile):\r
+    if( textFile != "" ):\r
+        fp = open(textFile, 'rb')\r
+        # Create a text/plain message\r
+        text = (fp.read())\r
+        fp.close()\r
+    return text\r
+# readText\r
+\r
+def writeText(text, textFile):\r
+    if( textFile != "" ):\r
+        fp = open(textFile, 'wb')\r
+        # Create a text/plain message\r
+        fp.write(text)\r
+        fp.close()\r
+    return text\r
+# readText\r
+\r
+class Process:\r
+    "A process with logged output"\r
+\r
+    def __init__(self, description=None, cmd=None, args=[], cwd=None, env=None, batchWrapper=False):\r
+        "Initialize the standard class variables"\r
+        self.cmd = cmd\r
+        if not description:\r
+            self.description = cmd\r
+        else:\r
+            self.description = description\r
+        self.status = None\r
+        self.args = args\r
+        self.start = None\r
+        self.end = None\r
+        self.log = []\r
+        self.echo = True\r
+        self.cwd = cwd\r
+        self.env = env\r
+        self.batchWrapper = batchWrapper\r
+        self.processKeys = []\r
+    # __init__\r
+\r
+    def getElapsedSeconds(self):\r
+        import math\r
+        if self.end and self.start:\r
+            delta = (self.end - self.start)\r
+            formatted = "%s.%s" % (delta.days * 86400 + delta.seconds, int(math.floor(delta.microseconds/1e3)))\r
+        else:\r
+            formatted = None\r
+        return formatted\r
+    # getElapsedtime\r
+\r
+    def writeKey(self, writeDict, key=None, value=None, startStop=None):\r
+        "Write a key, value pair in a supported format"\r
+        if key != None and (value != None or startStop != None):\r
+            indent = '\t'*writeDict['indentationLevel']\r
+            if writeDict['format'] == 'xml':\r
+                if startStop == 'start':\r
+                    writeDict['logHandle'].write( "%s<%s>\n" % (indent, key) )\r
+                elif startStop == 'stop':\r
+                    writeDict['logHandle'].write( "%s</%s>\n" % (indent, key) )\r
+                else:\r
+                    writeDict['logHandle'].write( "%s<%s>%s</%s>\n" % (indent, key, value, key) )\r
+            else: # writeDict['format'] == 'txt':\r
+                writeDict['logHandle'].write( "%s%40s : %s\n" % (indent, key, value) )\r
+\r
+    def writeLogHeader(self, writeDict):\r
+        import platform\r
+\r
+        # Retrieve operating environment information\r
+        user = None\r
+        try:\r
+            user = os.getlogin()\r
+        except:\r
+            try:\r
+                user = os.getenv("USERNAME")\r
+                if user == None:\r
+                    user = os.getenv("USER")\r
+            except:\r
+                user = "unknown_user"\r
+        try:\r
+            (sysname, nodename, release, version, machine, processor) = platform.uname()\r
+        except:\r
+            (sysname, nodename, release, version, machine, processor) = ("unknown_sysname", "unknown_nodename", "unknown_release", "unknown_version", "unknown_machine", "unknown_processor")\r
+        try:\r
+            hostname = platform.node()\r
+        except:\r
+            hostname = "unknown_hostname"\r
+\r
+        self.writeKey(writeDict, 'process', None, 'start' )\r
+        writeDict['indentationLevel'] += 1\r
+\r
+        self.writeKey(writeDict, 'description', self.description )\r
+        self.writeKey(writeDict, 'cmd', self.cmd )\r
+        if self.args: self.writeKey(writeDict, 'args', ' '.join(self.args) )\r
+        self.writeKey(writeDict, 'start', self.start )\r
+        self.writeKey(writeDict, 'end', self.end )\r
+        self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds() )\r
+\r
+        self.writeKey(writeDict, 'user', user )\r
+        self.writeKey(writeDict, 'sysname', sysname )\r
+        self.writeKey(writeDict, 'nodename', nodename )\r
+        self.writeKey(writeDict, 'release', release )\r
+        self.writeKey(writeDict, 'version', version )\r
+        self.writeKey(writeDict, 'machine', machine )\r
+        self.writeKey(writeDict, 'processor', processor )\r
+\r
+        if len(self.processKeys) > 0:\r
+            self.writeKey(writeDict, 'processKeys', None, 'start' )\r
+            for pair in self.processKeys:\r
+                (key, value) = pair\r
+                writeDict['indentationLevel'] += 1\r
+                self.writeKey(writeDict, key, value )\r
+                writeDict['indentationLevel'] -= 1\r
+            self.writeKey(writeDict, 'processKeys', None, 'stop' )\r
+\r
+        self.writeKey(writeDict, 'status', self.status )\r
+    # writeLogHeader\r
+\r
+    def writeLogFooter(self, writeDict):\r
+        writeDict['indentationLevel'] -= 1\r
+        self.writeKey(writeDict, 'process', None, 'stop' )\r
+    # writeLogFooter\r
+\r
+    def writeLog(self, logHandle=sys.stdout, indentationLevel=0,format='xml'):\r
+        "Write logging information to the specified handle"\r
+        \r
+        writeDict = {}\r
+        writeDict['logHandle'] = logHandle\r
+        writeDict['indentationLevel'] = indentationLevel\r
+        writeDict['format'] = format\r
+        \r
+        if logHandle:\r
+            self.writeLogHeader(writeDict)\r
+            \r
+            if self.log:\r
+                self.writeKey(writeDict, 'output', None, 'start' )\r
+                if format == 'xml':\r
+                    logHandle.write( "<![CDATA[\n" )\r
+                for line in self.log:\r
+                    logHandle.write( '%s%s\n' % ("", line) )\r
+                if format == 'xml':\r
+                    logHandle.write( "]]>\n" )\r
+                self.writeKey(writeDict, 'output', None, 'stop' )\r
+\r
+            self.writeLogFooter(writeDict)\r
+    # writeLog\r
+\r
+    def writeLogToDisk(self, logFilename=None, format='xml', header=None):\r
+        if logFilename: \r
+            try:\r
+                # This also doesn't seem like the best structure...\r
+                # 3.1\r
+                try:\r
+                    logHandle = open( logFilename, mode='wt', encoding="utf-8")\r
+                # 2.6\r
+                except:\r
+                    logHandle = open( logFilename, mode='wt')\r
+            except:\r
+                print( "Couldn't open log : %s" % logFilename )\r
+                logHandle = None\r
+\r
+        if logHandle:\r
+            if header:\r
+                if format == 'xml':\r
+                    logHandle.write( "<![CDATA[\n" )\r
+                logHandle.write( header )\r
+                if format == 'xml':\r
+                    logHandle.write( "]]>\n" )\r
+            self.writeLog(logHandle)\r
+            logHandle.close()\r
+    # writeLogToDisk\r
+\r
+    def logLine(self, line):\r
+        "Add a line of text to the log"\r
+        self.log.append( line.rstrip() )\r
+        if self.echo:\r
+            print( "%s" % line.rstrip() )\r
+    # logLine\r
+\r
+    def execute(self):\r
+        "Execute this process"\r
+        import re\r
+        import datetime\r
+        import traceback\r
+        \r
+        try:\r
+            import subprocess as sp\r
+        except:\r
+            sp = None\r
+\r
+        self.start = datetime.datetime.now()\r
+\r
+        cmdargs = [self.cmd]\r
+        cmdargs.extend(self.args)\r
+        \r
+        if self.echo:\r
+            if sp:\r
+                print( "\n%s : %s\n" % (self.__class__, sp.list2cmdline(cmdargs)) )\r
+            else:\r
+                print( "\n%s : %s\n" % (self.__class__, " ".join(cmdargs)) )\r
+\r
+        # intialize a few variables that may or may not be set later\r
+        process = None\r
+        tmpWrapper = None\r
+        stdout = None\r
+        stdin = None\r
+        parentenv = os.environ\r
+        parentcwd = os.getcwd()\r
+\r
+        try:\r
+            # Using subprocess\r
+            if sp:\r
+                if self.batchWrapper:\r
+                    cmd = " ".join(cmdargs)\r
+                    tmpWrapper = os.path.join(self.cwd, "process.bat")\r
+                    writeText(cmd, tmpWrapper)\r
+                    print( "%s : Running process through wrapper %s\n" % (self.__class__, tmpWrapper) )\r
+                    process = sp.Popen([tmpWrapper], stdout=sp.PIPE, stderr=sp.STDOUT, \r
+                        cwd=self.cwd, env=self.env)\r
+                else:\r
+                    process = sp.Popen(cmdargs, stdout=sp.PIPE, stderr=sp.STDOUT, \r
+                        cwd=self.cwd, env=self.env)\r
+\r
+            # using os.popen4\r
+            else:\r
+                if self.env:\r
+                    os.environ = self.env\r
+                if self.cwd:\r
+                    os.chdir( self.cwd )\r
+                \r
+                stdin, stdout = os.popen4( cmdargs, 'r')\r
+        except:\r
+            print( "Couldn't execute command : %s" % cmdargs[0] )\r
+            traceback.print_exc()\r
+\r
+        # Using subprocess\r
+        if sp:\r
+            if process != None:\r
+                #pid = process.pid\r
+                #log.logLine( "process id %s\n" % pid )\r
+\r
+                try:\r
+                    # This is more proper python, and resolves some issues with a process ending before all\r
+                    #  of its output has been processed, but it also seems to stall when the read buffer\r
+                    #  is near or over it's limit. this happens relatively frequently with processes\r
+                    #  that generate lots of print statements.\r
+                    #\r
+                    for line in process.stdout:\r
+                        self.logLine(line)\r
+                    #\r
+                    # So we go with the, um, uglier  option below\r
+\r
+                    # This is now used to ensure that the process has finished\r
+                    line = ""\r
+                    while line != None and process.poll() == None:\r
+                        try:\r
+                            line = process.stdout.readline()\r
+                        except:\r
+                            break\r
+                        # 3.1\r
+                        try:\r
+                            self.logLine( str(line, encoding="utf-8") )\r
+                        # 2.6\r
+                        except:\r
+                            self.logLine( line )\r
+                except:\r
+                    self.logLine( "Logging error : %s" % sys.exc_info()[0] )\r
+\r
+                self.status = process.returncode\r
+                \r
+                if self.batchWrapper and tmpWrapper:\r
+                    try:\r
+                        os.remove(tmpWrapper)\r
+                    except:\r
+                        print( "Couldn't remove temp wrapper : %s" % tmpWrapper )\r
+                        traceback.print_exc()\r
+\r
+        # Using os.popen4\r
+        else:\r
+            exitCode = -1\r
+            try:\r
+                #print( "reading stdout lines" )\r
+                stdoutLines = stdout.readlines()\r
+                exitCode = stdout.close()\r
+\r
+                stdout.close()\r
+                stdin.close()\r
+\r
+                if self.env:\r
+                    os.environ = parentenv\r
+                if self.cwd:\r
+                    os.chdir( parentcwd )\r
+                \r
+                if len( stdoutLines ) > 0:\r
+                    for line in stdoutLines:\r
+                        self.logLine(line)\r
+\r
+                if not exitCode:\r
+                    exitCode = 0\r
+            except:\r
+                self.logLine( "Logging error : %s" % sys.exc_info()[0] )\r
+\r
+            self.status = exitCode\r
+            \r
+        self.end = datetime.datetime.now()\r
+    #execute\r
+# Process\r
+\r
+class ProcessList(Process):\r
+    "A list of processes with logged output"\r
+\r
+    def __init__(self, description, blocking=True, cwd=None, env=None):\r
+        Process.__init__(self, description, None, None, cwd, env)\r
+        "Initialize the standard class variables"\r
+        self.processes = []\r
+        self.blocking = blocking\r
+    # __init__\r
+\r
+    def generateReport(self, writeDict):\r
+        "Generate a log based on the success of the child processes"\r
+        if self.processes:\r
+            _status = True\r
+            indent = '\t'*(writeDict['indentationLevel']+1)\r
+            \r
+            self.log = []\r
+            \r
+            for child in self.processes:\r
+                if isinstance(child, ProcessList):\r
+                    child.generateReport(writeDict)\r
+                \r
+                childResult = ""\r
+                key = child.description\r
+                value = child.status\r
+                if writeDict['format'] == 'xml':\r
+                    childResult = ( "%s<result description=\"%s\">%s</result>" % (indent, key, value) )\r
+                else: # writeDict['format'] == 'txt':\r
+                    childResult = ( "%s%40s : %s" % (indent, key, value) )\r
+                self.log.append( childResult )\r
+                \r
+                if child.status != 0:\r
+                    _status = False\r
+            if not _status:\r
+                self.status = -1\r
+            else:\r
+                self.status = 0\r
+        else:\r
+            self.log = ["No child processes available to generate a report"]\r
+            self.status = -1\r
+\r
+    def writeLogHeader(self, writeDict):\r
+        self.writeKey(writeDict, 'processList', None, 'start' )\r
+        writeDict['indentationLevel'] += 1\r
+\r
+        self.writeKey(writeDict, 'description', self.description )\r
+        self.writeKey(writeDict, 'start', self.start )\r
+        self.writeKey(writeDict, 'end', self.end )\r
+        self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds() )\r
+\r
+        self.generateReport(writeDict)\r
+\r
+        self.writeKey(writeDict, 'status', self.status )\r
+    # writeLogHeader\r
+\r
+    def writeLogFooter(self, writeDict):\r
+        writeDict['indentationLevel'] -= 1\r
+        self.writeKey(writeDict, 'processList', None, 'stop' )\r
+    # writeLogFooter\r
+\r
+    def writeLog(self, logHandle=sys.stdout, indentationLevel=0,format='xml'):\r
+        "Write logging information to the specified handle"\r
+        \r
+        writeDict = {}\r
+        writeDict['logHandle'] = logHandle\r
+        writeDict['indentationLevel'] = indentationLevel\r
+        writeDict['format'] = format\r
+        \r
+        if logHandle:\r
+            self.writeLogHeader(writeDict)\r
+            \r
+            if self.log:\r
+                self.writeKey(writeDict, 'output', None, 'start' )\r
+                for line in self.log:\r
+                    logHandle.write( '%s%s\n' % ("", line) )\r
+                self.writeKey(writeDict, 'output', None, 'stop' )\r
+\r
+            if self.processes:\r
+                self.writeKey(writeDict, 'processes', None, 'start' )\r
+                for child in self.processes:\r
+                    child.writeLog( logHandle, indentationLevel + 1, format )\r
+                self.writeKey(writeDict, 'processes', None, 'stop' )\r
+\r
+            self.writeLogFooter(writeDict)\r
+    # writeLog\r
+\r
+    def execute(self):\r
+        "Execute this list of processes"\r
+        import datetime\r
+\r
+        self.start = datetime.datetime.now()\r
+\r
+        self.status = 0\r
+        if self.processes:\r
+            for child in self.processes:\r
+                if child:\r
+                    try:\r
+                        child.execute()\r
+                    except:\r
+                        print( "%s : caught exception in child class %s" % (self.__class__, child.__class__) )\r
+                        traceback.print_exc()\r
+                        child.status = -1\r
+\r
+                    if self.blocking and child.status != 0:\r
+                        print( "%s : child class %s finished with an error" % (self.__class__, child.__class__) )\r
+                        self.status = -1\r
+                        break\r
+\r
+        self.end = datetime.datetime.now()\r
+    # execute\r
+# ProcessList\r
+\r
+def main():\r
+    import optparse\r
+\r
+    p = optparse.OptionParser(description='A process logging script',\r
+                                prog='process',\r
+                                version='process 0.1',\r
+                                usage='%prog [options] [options for the logged process]')\r
+    p.add_option('--cmd', '-c', default=None)\r
+    p.add_option('--log', '-l', default=None)\r
+\r
+    options, arguments = p.parse_args()\r
+\r
+    #\r
+    # Get options\r
+    # \r
+    cmd = options.cmd\r
+    logFilename = options.log\r
+\r
+    try:\r
+        argsStart = sys.argv.index('--') + 1\r
+        args = sys.argv[argsStart:]\r
+    except:\r
+        argsStart = len(sys.argv)+1\r
+        args = []\r
+\r
+    if cmd == None:\r
+        print( "process: No command specified" )\r
\r
+    #\r
+    # Test regular logging\r
+    #\r
+    process = Process(description="a process",cmd=cmd, args=args)\r
+\r
+    #\r
+    # Test report generation and writing a log\r
+    #\r
+    processList = ProcessList("a process list")\r
+    processList.processes.append( process )\r
+    processList.echo = True\r
+    processList.execute()\r
+    \r
+    processList.writeLogToDisk(logFilename)\r
+# main\r
+\r
+if __name__ == '__main__':\r
+    main()\r