#!/usr/bin/env python # tconfpy.py # Copyright (c) 2003-2004 TundraWare Inc. All Rights Reserved. # For Updates See: http://www.tundraware.com/Software/tconfpy # Program Information PROGNAME = "tconfpy" RCSID = "$Id: tconfpy.py,v 1.114 2004/03/14 09:57:13 tundra Exp $" VERSION = RCSID.split()[2] # Copyright Information CPRT = chr(169) DATE = "2003-2004" OWNER = "TundraWare Inc." RIGHTS = "All Rights Reserved" COPYRIGHT = "Copyright %s %s %s, %s." % (CPRT, DATE, OWNER, RIGHTS) PROGINFO = PROGNAME + " " + VERSION BANNER = "%s\n%s\n%s\n" % (PROGINFO, COPYRIGHT, RCSID) #----------------------------------------------------------# # Variables User Might Change # #----------------------------------------------------------# #------------------- Nothing Below Here Should Need Changing -----------------# #----------------------------------------------------------# # Public Features Of This Module # #----------------------------------------------------------# __all__ = ["ParseConfig"] #----------------------------------------------------------# # Imports # #----------------------------------------------------------# import os.path import re #----------------------------------------------------------# # Aliases & Redefinitions # #----------------------------------------------------------# #----------------------------------------------------------# # Constants & Literals # #----------------------------------------------------------# ########### # Constants ########### # Formatting Constants MSGPOS = 10 # Where to start message output FILENUM = "[File: %s Line: %s]" # Display filename and linenum PTR = " ---> " # Textual pointer for debug output # Special And Reserved Symbols HASH = r'#' COMMENT = HASH # Comment introducer character DELIML = r'[' # Left delimiter for vbl reference DELIMR = r']' # Right delimiter for vbl reference DOLLAR = r'$' # Used to note enviro vbl EQUAL = r'=' # Used in vbl definition EQUIV = r"==" # Used in conditional tests NOTEQUIV = r"!=" # Used in conditional tests # Control and conditional symbols INCLUDE = ".include" ENDIF = ".endif" IF = ".if" IFNOT = ".ifnot" Reserved = ["DELIML", "DELIMR", "DOLLAR", "EQUAL", "EQUIV", "NOTEQUIV", "HASH", "INCLUDE", "ENDIF", "IF", "IFNOT"] # Regular Expressions reVARREF = r"\%s.+?\%s" % (DELIML, DELIMR) # Variable reference VarRef = re.compile(reVARREF) ########### # Literals ########### TRUE = str(True) FALSE = str(False) #----------------------------------------------------------# # Global Variables & Data Structures # #----------------------------------------------------------# DEBUG = False # Control Debug output IGNORECASE = False # Case observed by default DebugMsg = [] # Place to store and return debug info ErrMsgs = [] # Place to store and return errors WarnMsgs = [] # Place to store and return warnings CondStack = [True,] # Conditional stack TotalLines = 0 # Total number of lines parsed ########## # Symbol Table ########## # Symbol Table is a dictionary in the form: # # {varname : descriptor} # # where the descriptor is a list: # # [value, iswriteable, type, default value, [list of legal vals], min, max] # Indexes Into Symbol Table Variable Descriptor SYM_VALUE = 0 SYM_WRITE = 1 SYM_TYPE = 2 SYM_DEFAULT = 3 SYM_VALUES = 4 SYM_MIN = 5 SYM_MAX = 6 # Legal Variable Types TYP_BOOL = 'b' TYPE_CMPLX = 'x' TYP_FLOAT = 'f' TYP_INT = 'i' TYP_STRING = 's' # Boolean Flags SYM_WRITEABLE = True # Initialize the table using the builtin symbols SymTable = {} for sym in Reserved: SymTable[sym] = [eval(sym), not SYM_WRITEABLE, TYP_STRING, eval(sym), None, None, None ] #----------------------------------------------------------# # Prompts, & Application Strings # #----------------------------------------------------------# ########## # Debug Messages ########## dDEBUG = "DEBUG" dBLANKLINE = "(Parsed To Blank Line - Ignored)" dLINEIGNORE = FILENUM + " Line Ignored/Not Included" + PTR + "'%s'\n" dNUMLINES = "Processing File '%s' Resulted In %d Total Lines Parsed" dPARSEDLINE = FILENUM + " '%s'" + PTR + "'%s'\n" ########### # Error Messages ########### eERROR = "ERROR" eBADCOND = FILENUM + " Bad Conditional Expression - %s: '%s'" eCONFOPEN = "Cannot Open The File '%s'" eENDIFEXTRA = FILENUM + " '" + ENDIF + "' Without Matching Condition" eENDIFMISS = FILENUM + " Missing %d '" + ENDIF + "' Statement(s)" eNOVARREF = "Must Have At Least One Variable Reference" eVARUNDEF = FILENUM + " " + "Attempt To Reference Undefined Variable '%s'" ########### # Warning Messages ########### wWARNING = "WARNING" wTRAILING = FILENUM + " Trailing Text After '%s' Statement Ignored" #--------------------------- Code Begins Here ---------------------------------# #----------------------------------------------------------# # Object Base Class Definitions # #----------------------------------------------------------# #----------------------------------------------------------# # Utility Function Definitions # #----------------------------------------------------------# ########## # Create A Debug Message ########## def DebugMsg(dmsg): global DebugMsgs DebugMsgs.append(mkmsg(dmsg, dDEBUG)) # End of 'DebugMsg()' ########## # Create An Error Message ########## def ErrorMsg(error): global ErrMsgs ErrMsgs.append(mkmsg(error + "!", eERROR)) # End of 'ErrorMsg()' ########## # Create A Warning Message ########## def WarningMsg(warning): global WarnMsgs WarnMsgs.append(mkmsg(warning + "!", wWARNING)) # End of 'WarningMsg()' ########## # Construct A Standard Application Message String ########## def mkmsg(msg, msgtype=""): if msgtype: msgtype += ">" pad = " " * (MSGPOS - len(msgtype)) return "%s %s%s%s" % (PROGINFO, msgtype, pad, msg) # End of 'mkmsg()' #----------------------------------------------------------# # Entry Point On Direct Invocation # #----------------------------------------------------------# if __name__ == '__main__': print BANNER #----------------------------------------------------------# # Public API To Module # #----------------------------------------------------------# def ParseConfig(cfgfile, symtbl={}, IgnoreCase=False, debug=False): global DebugMsgs, ErrMsgs, WarnMsgs global CondStack, DEBUG, IGNORECASE, SymTable, TotalLines # Initialize the globals DEBUG = debug IGNORECASE = IgnoreCase DebugMsgs = [] ErrMsgs = [] WarnMsgs = [] CondStack = [True,] TotalLines = 0 # Add any passed symbols to the SymbolTable for sym in symtbl: SymTable[sym] = symtbl[sym] # Parse the file ParseFile(cfgfile) # Return the parsing results if DEBUG: DebugMsg(dNUMLINES %(cfgfile, TotalLines)) return (SymTable, ErrMsgs, WarnMsgs, DebugMsgs) # End of 'ParseConfig()' #----------------------------------------------------------# # Parser Support Functions # #----------------------------------------------------------# ########## # Condition A Line - Remove Comments & Leading/Trailing Whitespace ########## def ConditionLine(line): return line.split(COMMENT)[0].strip() # End of 'ConditionLine()' ########## # Dereference Variables ########## def DerefVar(line, cfgfile, linenum): # Find all symbols refrences and replace w/sym table entry if present ref_ok = True for var in VarRef.findall(line): sym = var[1:-1] # Strip delimiters if sym in SymTable: line = line.replace(var, str(SymTable[sym][SYM_VALUE])) # Reference to undefined variable else: ErrorMsg(eVARUNDEF % (cfgfile, linenum, sym)) ref_ok = False return line, ref_ok # End of 'DerefVar()' ########## # Parse A File ########## def ParseFile(cfgfile): global IgnoreCase, MsgList, SymTable, TotalLines try: cf = open(cfgfile) # Successful open of config file - Begin processing it linenum=0 # Process and massage the configuration file for line in cf.read().splitlines(): linenum += 1 TotalLines += 1 # Parse this line ParseLine(line, cfgfile, linenum) # Close the config file cf.close() # File open failed for some reason except: ErrorMsg(eCONFOPEN % cfgfile) # Record the error # Make sure we had all condition blocks balanced with matching '.endif' finalcond = len(CondStack) if finalcond != 1: ErrorMsg(eENDIFMISS % (cfgfile, linenum, finalcond-1)) # End of 'ParseFile()' ########## # Parse A Line ########## def ParseLine(line, cfgfile, linenum): global CondStack, MsgList, SymTable orig = line # May need copy of original for debug output line = ConditionLine(line) ########## # Beginning Of Line Parser ########## # Only attempt on non-blank lines if line: ##### # .endif Processing - Must be done before state check # because .endif can change state ##### if line.startswith(ENDIF): # This should be the only thing on the line if line != ENDIF: WarningMsg(wTRAILING % (cfgfile, linenum, ENDIF)) # Remove one level of nesting CondStack.pop() # Error, if there are more .endifs than conditionals if not CondStack: ErrorMsg(eENDIFEXTRA % (cfgfile, linenum)) CondStack.append(False) # Inhibit further parsing ##### # Check State Of Parser ##### if not CondStack[-1]: if DEBUG: DebugMsg(dLINEIGNORE % (cfgfile, linenum, orig)) return ##### # .include Processing ##### if line.startswith(INCLUDE): ParseFile(line.split(INCLUDE)[1].strip()) ##### # .if Processing # # Must be one of the following forms - # # .if string # .ifnot string # .if string == string # .if string != string # # where string can be any concatentation of text and variable references # ##### if line.startswith(IF) or line.startswith(IFNOT): # NOTE: This next bit of logic will fail if you do not # test the longer of the two tokens first!!!! if line.startswith(IFNOT): line = line.split(IFNOT)[1].strip() invert = True else: line = line.split(IF)[1].strip() invert = False # Handle Equality Form: ".if string == string" if line.count(EQUIV): pass # Handle Existential Forms ".if/.ifnot string" else: # There must be at least one var reference in the condition if len(VarRef.findall(line)) > 0: # See if the variable(s) resolve line, ref_ok = DerefVar(line, cfgfile, linenum) # If we're using '.ifnot', invert the sense of the logic if invert: ref_ok = not ref_ok # Set parser state accordingly if ref_ok: line = TRUE CondStack.append(True) else: line = FALSE CondStack.append(False) # Bogus conditional syntax else: ErrorMsg(eBADCOND % (cfgfile, linenum, eNOVARREF, orig)) line, ref_ok = DerefVar(line, cfgfile, linenum) ########## # End Of Line Parser ########## if DEBUG: # Note blank lines for debug purposes if not line: line = dBLANKLINE DebugMsg(dPARSEDLINE % (cfgfile, linenum, orig, line)) # End of 'ParseLine'