################################################################################
# an application class hierarchy, for handling top-level components;
# App is the root class of the App hierarchy, extended in other files;
################################################################################

import sys, os, traceback
AppError = 'App class error'                              # errors raised here

class App:                                                # the root class
    def __init__(self, name=None):
        self.name    = name or self.__class__.__name__    # the lowest class
        self.args    = sys.argv[1:] 
        self.env     = os.environ
        self.verbose = self.getopt('-v') or self.getenv('VERBOSE')   
        self.input   = sys.stdin
        self.output  = sys.stdout 
        self.error   = sys.stderr                     # stdout may be piped
    def closeApp(self):                               # not __del__: ref's?
        pass                                          # nothing at this level
    def help(self):
        print self.name, 'command-line arguments:'    # extend in subclass
        print '-v (verbose)'

    ##############################
    # script environment services
    ##############################

    def getopt(self, tag):
        try:                                    # test "-x" command arg
            self.args.remove(tag)               # not real argv: > 1 App?
            return 1                   
        except:
            return 0
    def getarg(self, tag, default=None):
        try:                                    # get "-x val" command arg
            pos = self.args.index(tag)
            val = self.args[pos+1]
            self.args[pos:pos+2] = []
            return val
        except:
            return default                      # None: missing, no default
    def getenv(self, name, default=''):
        try:                                    # get "$x" environment var
            return self.env[name]
        except KeyError:
            return default
    def endargs(self):
        if self.args:
            self.message('extra arguments ignored: ' + `self.args`)
            self.args = []
    def restargs(self):
        res, self.args = self.args, []          # no more args/options
        return res
    def message(self, text):
        self.error.write(text + '\n')           # stdout may be redirected
    def exception(self):
        return (sys.exc_type, sys.exc_value)    # the last exception
    def exit(self, message='', status=1):
        if message: 
            self.message(message)
        sys.exit(status)
    def shell(self, command, fork=0, inp=''):
        if self.verbose:
            self.message(command)                         # how about ipc?
        if not fork:
            os.system(command)                            # run a shell cmd
        elif fork == 1:
            return os.popen(command, 'r').read()          # get its output
        else:                                             # readlines too?
            pipe = os.popen(command, 'w')      
            pipe.write(inp)                               # send it input
            pipe.close()

    #################################################
    # input/output-stream methods for the app itself; 
    # redefine in subclasses if not using files, or 
    # set self.input/output to file-like objects;
    #################################################

    def read(self, *size):       
        return apply(self.input.read, size)
    def readline(self):          
        return self.input.readline()
    def readlines(self):         
        return self.input.readlines()
    def write(self, text):       
        self.output.write(text)
    def writelines(self, text):  
        self.output.writelines(text)

    ###################################################
    # to run the app
    # main() is the start/run/stop execution protocol;
    ###################################################

    def main(self):
        res = None
        try:
            self.start()
            self.run()
            res = self.stop()               # optional return val
        except SystemExit:                  # ignore if from exit()
            pass
        except:
            self.message('uncaught: ' + `self.exception()`)
            traceback.print_exc()
        self.closeApp()
        return res

    def start(self): 
        if self.verbose: self.message(self.name + ' start.')
    def stop(self): 
        if self.verbose: self.message(self.name + ' done.')
    def run(self):  
        raise AppError, 'run must be redefined!'
