#!/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.118 2004/03/16 03:18:19 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 CONDINTRO = '.' # Conditional introducer token INCLUDE = CONDINTRO + "include" ENDIF = CONDINTRO + "endif" IF = CONDINTRO + "if" IFALL = IF + "all" IFANY = IF + "any" IFNONE = IF + "none" Reserved = ["DELIML", "DELIMR", "DOLLAR", "EQUAL", "EQUIV", "NOTEQUIV", "HASH", "INCLUDE", "ENDIF", "IF", "IFALL", "IFANY", "IFNONE"] # Regular Expressions reVARREF = r"\%s.+?\%s" % (DELIML, DELIMR) # Variable reference VarRef = re.compile(reVARREF) ########### # Literals ########### # String Representations Of Booleans sFALSE = "False" sNO = "No" sOFF = "Off" sON = "On" sTRUE = "True" sYES = "Yes" #----------------------------------------------------------# # 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 + " '%s' " + PTR + "%s\n" dNOTINCLUDE = "Current Conditional Block False: Line Not Included" dNUMLINES = "Processing File '%s' Resulted In %d Total Lines Parsed" dPARSEDLINE = FILENUM + " '%s'" + PTR + "'%s'\n" ########### # Error Messages ########### eERROR = "ERROR" eSYNTAX = "Incorrect '%s' Syntax" eBADEXPR = FILENUM + " Bad Expression - %s: '%s'" eCONFOPEN = "Cannot Open The File '%s'" eENDIFEXTRA = FILENUM + " '" + ENDIF + "' Without Matching Condition" eENDIFBAD = eSYNTAX % ENDIF eENDIFMISS = FILENUM + " Missing %d '" + ENDIF + "' Statement(s)" eIFBAD = eSYNTAX % IF eINCLBAD = eSYNTAX % INCLUDE eNOVARREF = "Must Have At Least One Variable Reference" eVARUNDEF = FILENUM + " " + "Attempt To Reference Undefined Variable '%s'" ########### # Warning Messages ########### wWARNING = "WARNING" wEXTRATEXT = FILENUM + " '%s' Statements Only Process Variables. Extra Text Ignored" 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, reporterr=True): # 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: # There are times a reference to an undefined variable # should not produce and error message. For example, # during existential conditional processing, we just # want to check whether variables are defined or not. # Attempts to reference undefined variables are legitimate # in this case and ought not to generate an error message. if reporterr: 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: # Get first token on the line FIRSTTOK = line.split()[0] ##### # .endif Processing - Must be done before state check # because .endif can change parser state ##### if line.startswith(ENDIF): # Check for space after conditional if line.split()[0] != ENDIF: ErrorMsg(eBADEXPR % (cfgfile, linenum, eENDIFBAD, orig)) # This should be the only thing on the line elif 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, dNOTINCLUDE)) return ##### # .include Processing ##### if line.startswith(INCLUDE): # Make sure a space follows the directive if line.split()[0] != INCLUDE: ErrorMsg(eBADEXPR % (cfgfile, linenum, eINCLBAD, orig)) else: ParseFile(line.split(INCLUDE)[1].strip()) ##### # Conditional Processing # # Must be one of the following forms - # # IFALL [var] ... # IFANY [var] ... # IFNONE [var] ... # IF string == string # IF string != string # # where string can be any concatentation of text and variable references # ##### # Is it any of the IF forms? if FIRSTTOK.startswith(IF): IFTYPE = FIRSTTOK ##### # Existential Conditionals ##### if IFTYPE in (IFALL, IFANY, IFNONE): if IFTYPE == IFALL: line = line.split(IFALL)[1].strip() elif IFTYPE == IFANY: line = line.split(IFANY)[1].strip() else: line = line.split(IFNONE)[1].strip() # There must be at least one var reference in the condition vars = VarRef.findall(line) if len(vars) > 0: # Only variable references are significant - warn on other text. # Strip out variable references and see if anything # other than whitespace is left. plain = line for v in vars: plain=plain.replace(v, "") if len(plain.strip()): WarningMsg(wEXTRATEXT % (cfgfile, linenum, IFTYPE)) # Go see how many references actually resolve resolved = 0 for v in vars: v, ref_ok = DerefVar(v, cfgfile, linenum, reporterr=False) if ref_ok: resolved += 1 # And set state accordingly state = sTRUE if IFTYPE == IFALL and len(vars) != resolved: state = sFALSE if IFTYPE == IFANY and not resolved: state = sFALSE if IFTYPE == IFNONE and resolved: state = sFALSE CondStack.append(state) line = state # Bogus conditional syntax else: ErrorMsg(eBADEXPR % (cfgfile, linenum, eNOVARREF, orig)) ##### # (In)Equality Conditionals ##### # Check for IF followed by mandatory whitespace elif line.split()[0] == IF: line = line.split(IF)[1].strip() # Handle Equality Form: ".if string == string" if line.count(EQUIV): pass # Handle InEquality Form: ".if string != string" elif line.count(NOTEQUIV): pass ##### # Line Started With IF, But Was Not In Any Knwon IF Form ##### else: ErrorMsg(eBADEXPR % (cfgfile, linenum, eIFBAD, orig)) ##### # Handle New Variable Declaration/Assignment ##### else: line, ref_ok = DerefVar(line, cfgfile, linenum) ########## # End Of Line Parser ########## if DEBUG: # Note blank lines for debug purposes if not line: DebugMsg(dLINEIGNORE % (cfgfile, linenum, orig, dBLANKLINE)) # All non-blank lines noted here else: DebugMsg(dPARSEDLINE % (cfgfile, linenum, orig, line)) # End of 'ParseLine'