#!/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.162 2004/04/07 00:10:37 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 -----------------# #----------------------------------------------------------# # Imports # #----------------------------------------------------------# import os import platform import re from sys import platform as sysplat #----------------------------------------------------------# # Aliases & Redefinitions # #----------------------------------------------------------# #----------------------------------------------------------# # Constants & Literals # #----------------------------------------------------------# ########### # Constants ########### # Formatting Constants MSGPROMPT = "%s>" FILENUM = "[File: %s Line: %s] " # Display filename and linenum ATEOF = "EOF" # Use as line number when at EOF 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'$' ENVIRO = DOLLAR # Used to note environment variable EQUAL = r'=' # Used in vbl definition EQUIV = r"==" # Used in conditional tests NAMESPACE = "NAMESPACE" # Key used to get current namespace NSSEP = '.' # Namespace separator character NOTEQUIV = r"!=" # Used in conditional tests # Pre-Defined System Symbols PREDEFINTRO = '.' Predefined = {PREDEFINTRO + "PLATFORM" : sysplat, PREDEFINTRO + "OSDETAILS" : platform.platform(), PREDEFINTRO + "OSNAME" : platform.system(), PREDEFINTRO + "OSRELEASE" : platform.release(), PREDEFINTRO + "PYTHONVERSION" : platform.python_version(), PREDEFINTRO + "MACHINENAME" : platform.node() } # Control and conditional symbols CONDINTRO = '.' # Conditional introducer token INCLUDE = CONDINTRO + "include" IF = CONDINTRO + "if" IFALL = IF + "all" IFANY = IF + "any" IFNONE = IF + "none" ELSE = CONDINTRO + "else" ENDIF = CONDINTRO + "endif" LITERAL = CONDINTRO + "literal" ENDLITERAL = CONDINTRO + "endliteral" Reserved = ["HASH", "DELIML", "DELIMR", "DOLLAR", "EQUAL", "EQUIV", "NOTEQUIV", "NSSEP", "INCLUDE", "IF", "IFALL", "IFANY", "IFNONE", "ELSE", "ENDIF", "LITERAL", "ENDLITERAL"] # Regular Expressions reVARWHITE = r".*\s+.*" reVARILLEGAL = r"\%s(%s)%s" % (DELIML, reVARWHITE, DELIMR) # Variable reference with spaces in it reVARREF = r"\%s.+?\%s" % (DELIML, DELIMR) # Variable reference VarWhite = re.compile(reVARWHITE) VarIllegal = re.compile(reVARILLEGAL) VarRef = re.compile(reVARREF) ########### # Literals ########### # String Representations Of Booleans Booleans = {"1" : True, "0" : False, "True" : True, "False" : False, "Yes" : True, "No" : False, "On" : True, "Off" : False } sFALSE = "False" sTRUE = "True" #----------------------------------------------------------# # Global Variables & Data Structures # #----------------------------------------------------------# ALLOWNEW = True # Allow new variable creation in cfg file DEBUG = False # Control Debug output LITERALVARS = False INLITERAL = False # Indicates we are currently in a literal block DebugMsg = [] # Place to store and return debug info ErrMsgs = [] # Place to store and return errors WarnMsgs = [] # Place to store and return warnings LiteralLines = [] # Place to store and return unprocessed lines CondStack = [["", True,]] # Conditional stack TotalLines = 0 # Total number of lines parsed ##### # Dummy Object Used To Return Parsing Results ##### # This is done so the caller only needs to know the name of # each result set, not its position in the list of returned items class RetObj(object): def __init__(self): self.SymTable = {} self.Errors = [] self.Warnings = [] self.Debug = [] self.Literals = [] # End of 'RetObj' ########## # Symbol Table ########## # Symbol Table is a dictionary in the form: # # {varname : descriptor} # # where the descriptor is an object with the following attributes # # Value, Writeable, Type, Default, LegalVals = [list of legal vals], Min, Max] # # Legal Variable Types TYPE_BOOL = type(True) TYPE_COMPLEX = type(1-1j) TYPE_FLOAT = type(3.14) TYPE_INT = type(1) TYPE_STRING = type('s') # Object to hold full description and options for a given variable class VarDescriptor(object): # Default variable type is a writeable string with no constraints def __init__(self): self.Value = "" self.Writeable = True self.Type = TYPE_STRING self.Default = "" self.LegalVals = [] self.Min = None self.Max = None # End of class 'VarDescriptor' # Initialize the table using the builtin symbols SymTable = {} for sym in Reserved: descript = VarDescriptor() descript.Value = eval(sym) descript.Writeable = False SymTable[sym] = descript # Add the predefined symbols for var in Predefined.keys(): d = VarDescriptor() d.Value = Predefined[var] d.Writeable = False SymTable[var] = d ########## # Error, Warning, And Debug Message Strings Stored In A Global Dictionary ########## Messages = {} #----------------------------------------------------------# # Prompts, & Application Strings # #----------------------------------------------------------# ########## # Debug Literals And Messages ########## # Literals dDEBUG = "DEBUG" dLINEIGNORE = "Line Ignored" dBLANKLINE = "Parsed To Blank Line. %s" % dLINEIGNORE dNOTINCLUDE = "Current Conditional Block False. %s" % dLINEIGNORE # Messages Messages["dLINEIGNORE"] = FILENUM + " '%s' " + PTR + "%s\n" Messages["dNUMLINES"] = "Processing File '%s' Resulted In %d Total Lines Parsed" Messages["dPARSEDLINE"] = FILENUM + " '%s'" + PTR + "'%s'\n" ########### # Error Literals And Messages ########### # Literals eERROR = "ERROR" eBADDEFAULT = "Type Of Default Value Does Not Agree With Type Declared" eBADLEGALVAL = "Type Of One Or More LegalVals Does Not Agree With Type Declared" eBADMINMAX = "Type Of Min Or Max Value Not Appropriate For" eIFBAD = "'%s' Or '%s' Missing" % (EQUIV, NOTEQUIV) eNOTDESCRIPT = "Invalid Descriptor Type" eNOVARREF = "Must Have At Least One Variable Reference" eSTARTUP = "<Program Starting>" # Messages Messages["eBADCOND"] = FILENUM + "Bad '%s' Directive. %s" Messages["eBADREGEX"] = FILENUM + "Bad Regular Expression, '%s', In Legal Values List For Variable '%s'" Messages["eBADSYNTAX"] = FILENUM + "Syntax Error. Statement Not In Known Form" Messages["eCONFOPEN"] = FILENUM + "Cannot Open The File '%s'" Messages["eDESCRIPTBAD"] = "API Error: %s For Variable '%s'" Messages["eELSENOIF"] = FILENUM + "'%s' Without Preceding '%s' Form" % (ELSE, IF) Messages["eENDIFEXTRA"] = FILENUM + "'" + ENDIF + "' Without Matching Condition" Messages["eENDIFMISS"] = FILENUM + "Missing %d" + " '%s' " % ENDIF + " Statement(s)" Messages["eEQUIVEXTRA"] = FILENUM + "Only a single '%s' Or '%s' Operator Permitted" % (EQUIV, NOTEQUIV) Messages["eIFEXTRATXT"] = FILENUM + "Extra Text On Line. '%s' Only Accepts Variable References As Arguments" Messages["eNOTLEGALVAL"] = FILENUM + "'%s' Not Found In List Of Legal Values For Variable '%s'" Messages["eSTRINGLONG"] = FILENUM + "Right-Hand-Side Too Long. '%s' Must Be No More Than %s Characters Long" Messages["eSTRINGSHORT"] = FILENUM + "Right-Hand-Side Too Short. '%s' Must Be At Least %s Characters Long" Messages["eTYPEBAD"] = FILENUM + "Type Mismatch. '%s' Must Be Assigned Values Of Type %s Only" Messages["eVALLARGE"] = FILENUM + "%s Is Larger Than The Maximum Allowed, %s, For Variable '%s'" Messages["eVALSMALL"] = FILENUM + "%s Is Smaller Than The Minimum Allowed, %s, For Variable '%s'" Messages["eVARRESERVED"] = FILENUM + "Variable Name Cannot Begin With The '%s' Symbol" Messages["eVARNAMESPC"] = FILENUM + "Variable Names May Not Contain Whitespace" Messages["eVARNEW"] = FILENUM + "New Variable Creation Not Permitted" Messages["eVARNONAME"] = FILENUM + "Variable Name Evaluates To Null String. Not Permitted" Messages["eVARREADONLY"] = FILENUM + "Variable '%s' Is Read-Only. Cannot Change Its Value" Messages["eVARUNDEF"] = FILENUM + "Attempt To Reference Undefined Variable '%s'" ########### # Warning Literals And Messages ########### # Literals wWARNING = "WARNING" # Messages Messages["wENDLITEXTRA"] = FILENUM + "'%s' Statement Without Preceding '%s'. Statement Ignored" % (ENDLITERAL, LITERAL) Messages["wENDLITMISS"] = FILENUM + "Missing '%s' Statement. All lines treated literally to end-of-file" % ENDLITERAL Messages["wLITEXTRA"] = FILENUM + "Already In A Literal Block. '%s' Statement Ignored" % LITERAL # Determine Length Of Longest Message Type # Needed for formatting later MAXMSG = 0 for msg in Messages: l = len(msg) if l > MAXMSG: MAXMSG = l #--------------------------- Code Begins Here ---------------------------------# #----------------------------------------------------------# # Utility Function Definitions # #----------------------------------------------------------# ########## # Create A Debug Message ########## def DebugMsg(dmsg, args): global DebugMsgs DebugMsgs.append(mkmsg(Messages[dmsg] % args, dmsg)) # End of 'DebugMsg()' ########## # Create An Error Message ########## def ErrorMsg(error, args): global ErrMsgs ErrMsgs.append(mkmsg(Messages[error] % args + "!", error)) # End of 'ErrorMsg()' ########## # Create A Warning Message ########## def WarningMsg(warning, args): global WarnMsgs WarnMsgs.append(mkmsg(Messages[warning] % args + "!", warning)) # End of 'WarningMsg()' ########## # Construct A Standard Application Message String ########## def mkmsg(msg, msgtype): pad = " " * (MAXMSG - len(msgtype) + 2) return "%s %s%s%s" % (PROGINFO, MSGPROMPT % msgtype, pad, msg) # End of 'mkmsg()' #----------------------------------------------------------# # Public API To Module # #----------------------------------------------------------# def ParseConfig(cfgfile, InitialSymTbl={}, AllowNewVars=True, Debug=False, LiteralVars=False): global DebugMsgs, ErrMsgs, WarnMsgs, LiteralLines global CondStack, ALLOWNEW, DEBUG, SymTable, TotalLines, LITERALVARS, INLITERAL # Initialize the globals ALLOWNEW = AllowNewVars DEBUG = Debug LITERALVARS = LiteralVars DebugMsgs = [] ErrMsgs = [] WarnMsgs = [] LiteralLines = [] retobj = RetObj() CondStack = [["", True],] # Always has one entry as a sentinel TotalLines = 0 # Add any passed symbols to the SymbolTable deserror = False for sym in InitialSymTbl: des = InitialSymTbl[sym] # Make sure a valid descriptor was passed for each variable desok = True # Make sure we got a Var Descriptor Object if not isinstance(des, VarDescriptor): desok = False ErrorMsg("eDESCRIPTBAD", (eNOTDESCRIPT, sym)) des = VarDescriptor() # Make a fake one so the following tests don't blow up # Check various entries for type agreement dt = des.Type if des.Default and type(des.Default) != dt: desok = False ErrorMsg("eDESCRIPTBAD", (eBADDEFAULT, sym)) for lv in des.LegalVals: if type(lv) != dt: desok = False ErrorMsg("eDESCRIPTBAD", (eBADLEGALVAL, sym)) for mm in (des.Min, des.Max): # Floats can accept Float or Int boundaries # Ints, & Strings can accept Int boundaries # Boundaries not relevant for Bool and Complex # Anything else is an error if mm and dt == TYPE_FLOAT and type(mm) != TYPE_FLOAT and type(mm) != TYPE_INT: desok = False ErrorMsg("eDESCRIPTBAD", (eBADMINMAX, sym)) if mm and dt in (TYPE_INT, TYPE_STRING) and type(mm) != TYPE_INT: desok = False ErrorMsg("eDESCRIPTBAD", (eBADMINMAX, sym)) # Only load the symbol table with valid entries if desok: SymTable[sym] = des # Indicate that a problem was encountered else: deserror = True # If any of the passed symbols had bogus descriptor contents, we're done if deserror: retobj.SymTable = SymTable retobj.Errors = ErrMsgs retobj.Warnings = WarnMsgs retobj.Debug = DebugMsgs retobj.Literals = LiteralLines return retobj # Symbol Table passed to API was OK, sokeep going # Make sure the symbol table has a valid namespace if NAMESPACE not in SymTable: SymTable[NAMESPACE] = VarDescriptor() SymTable[NAMESPACE].Value = "" SymTable[NAMESPACE].LegalVals.append("") # Parse the file ParseFile(cfgfile, eSTARTUP, 0) # Make sure we had all condition blocks balanced with matching '.endif' finalcond = len(CondStack) if finalcond != 1: ErrorMsg("eENDIFMISS", (cfgfile, ATEOF, finalcond-1)) # Make sure we ended any literal processing properly if INLITERAL: WarningMsg("wENDLITMISS", (cfgfile, ATEOF)) # Return the parsing results if DEBUG: DebugMsg("dNUMLINES", (cfgfile, TotalLines)) retobj.SymTable = SymTable retobj.Errors = ErrMsgs retobj.Warnings = WarnMsgs retobj.Debug = DebugMsgs retobj.Literals = LiteralLines return retobj # 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 # By default, all variable names assumed to be relative to # current namespace context unless escaped with NSSEP # Look for an escape if sym and sym[0] == NSSEP: sym = sym[1:] # Prepend the current namespace for all but the top level elif SymTable[NAMESPACE].Value: sym = "%s%s%s" % (SymTable[NAMESPACE].Value, NSSEP, sym) # Handle environment variables if sym and sym[0] == ENVIRO and sym[1:] in os.environ: line = line.replace(var, os.getenv(sym[1:])) # Handle variables in symbol table elif sym in SymTable: line = line.replace(var, str(SymTable[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, current_cfg, current_linenum): global IgnoreCase, MsgList, SymTable, TotalLines linenum=0 try: cf = open(cfgfile) # 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 IOError: ErrorMsg("eCONFOPEN", (current_cfg, current_linenum, cfgfile)) # Record the error # End of 'ParseFile()' ########## # Parse A Line ########## def ParseLine(line, cfgfile, linenum): global CondStack, MsgList, SymTable, INLITERAL orig = line # May need copy of original for debug output ########## # Beginning Of Line Parser ########## ##### # LITERAL and ENDLITERAL Processing # These get highest precedence because they block everything else. # However, they are ignored within False conditional blocks. ##### if line.strip() in (LITERAL, ENDLITERAL): if not CondStack[-1][1]: if DEBUG: DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dNOTINCLUDE)) return if line.strip() == LITERAL: if INLITERAL: WarningMsg("wLITEXTRA", (cfgfile, linenum)) else: INLITERAL = True # Process ENDLITERAL statements else: if not INLITERAL: WarningMsg("wENDLITEXTRA", (cfgfile, linenum)) else: INLITERAL = False if DEBUG: DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line)) return # We pass lines as-is, with optional variable replacement, in literal blocks if INLITERAL: if LITERALVARS: line, ref_ok = DerefVar(line, cfgfile, linenum) LiteralLines.append(line) if DEBUG: DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line)) return line = ConditionLine(line) # Strip out comments and leading/trailing whitespace condstate = True # Results of conditional tests kept here # Only attempt on non-blank lines for everything else # Line was blank if not line: # Note blank lines for debug purposes if DEBUG: DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dBLANKLINE)) return # Get first token on the line FIRSTTOK = line.split()[0] ##### # ELSE Processing ##### if line == ELSE: # Get the enclosing block type and state btyp, bst = CondStack.pop() # ELSE is only permitted after an immediately preceding IF form if btyp != IF: ErrorMsg("eELSENOIF", (cfgfile, linenum)) CondStack.append(["", False]) # Error makes all that follows False # We *are* in an IF block an ELSE is appropriate. # To determine whether the ELSE should be taken or not we have # to look at the state of that IF block AND the state of # the block that contains the IF. This is because the IF # block state is the AND of the state of its parent block # and its own logical state - i.e. A block can be made # logically False by its containing block even if the condition # tested is True. else: # If the containing block is True, the contained IF state is legitimate. # The ELSE inverts the IF state in that case. if CondStack[-1][1]: CondStack.append([ELSE, not bst]) # The containing block is false, so everything within it is also false else: CondStack.append([ELSE, False]) if DEBUG: DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line)) return ##### # ENDIF Processing ##### if line == ENDIF: # Remove one level of conditional nesting CondStack.pop() # Error, if there are more .endifs than conditionals if not CondStack: ErrorMsg("eENDIFEXTRA", (cfgfile, linenum)) CondStack.append(["", False]) # Restore sentinel & inhibit further parsing if DEBUG: DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line)) return ##### # Namespace Processing ##### if line == DELIML+DELIMR or (len(VarRef.findall(line)) == 1 and \ line[0] == DELIML and line[-1] == DELIMR and\ line.count(DELIML) == line.count(DELIMR) == 1): if not CondStack[-1][1]: if DEBUG: DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dNOTINCLUDE)) return # Set the new namespace ns = line[1:-1] SymTable[NAMESPACE].Value = ns # Save newly seen namespaces in list of legal vals if ns not in SymTable[NAMESPACE].LegalVals: SymTable[NAMESPACE].LegalVals.append(ns) if DEBUG: DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line)) return ##### # INCLUDE Processing ##### elif FIRSTTOK == INCLUDE: if not CondStack[-1][1]: if DEBUG: DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dNOTINCLUDE)) return line, ref_ok = DerefVar(line.split(INCLUDE)[1].strip(), cfgfile, linenum) # Only attempt the include if all the variable dereferencing was successful if ref_ok: ParseFile(line, cfgfile, linenum) if DEBUG: DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line)) return ##### # 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? elif FIRSTTOK in (IF, IFALL, IFANY, IFNONE): ##### # Existential Conditionals ##### if FIRSTTOK in (IFALL, IFANY, IFNONE): if FIRSTTOK == IFALL: line = line.split(IFALL)[1].strip() elif FIRSTTOK == 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) # Only variable references are significant - warn on other text. # Strip out variable references and see if anything # other than whitespace is left. plain = line if vars: for v in vars: plain=plain.replace(v, "") # Only arguments that are allowed are variable references if len(plain.strip()): ErrorMsg("eIFEXTRATXT", (cfgfile, linenum, FIRSTTOK)) condstate = False if vars: # Only do this if the syntax check above was OK if condstate: # 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 the conditional state accordingly if FIRSTTOK == IFALL and len(vars) != resolved: condstate = False if FIRSTTOK == IFANY and not resolved: condstate = False if FIRSTTOK == IFNONE and resolved: condstate = False # Bogus conditional syntax - no variable refs found else: ErrorMsg("eBADCOND", (cfgfile, linenum, FIRSTTOK, eNOVARREF)) # Force parse state to False on an error condstate = False ##### # (In)Equality Conditionals - IF string EQUIV/NOTEQUIV string forms ##### else: line = line.split(IF)[1].strip() if EQUIV in line or NOTEQUIV in line: # Only one operator permitted if (line.count(EQUIV) + line.count(NOTEQUIV)) > 1: ErrorMsg("eEQUIVEXTRA", (cfgfile, linenum)) condstate = False else: # Dereference all variables line, ref_ok = DerefVar(line, cfgfile, linenum) # Reference to undefined variables forces False if not ref_ok: condstate = False # So does a failure of the equality test itself else: invert = False operator = EQUIV condstate = True if operator not in line: # Must be NOTEQUIV invert = True operator = NOTEQUIV line = line.split(operator) if line[0].strip() != line[1].strip(): condstate = False if invert: condstate = not condstate # Conditional Syntax Error else: ErrorMsg("eBADCOND", (cfgfile, linenum, FIRSTTOK, eIFBAD)) # Force parse state to False on an error condstate = False # Set parser state based on a successful conditional test # But it has to be ANDed with the state of the enclosing block enclosing = CondStack[-1][1] CondStack.append([IF, condstate and enclosing]) # Now reflect this in the parsed line line = sTRUE if not condstate: line = sFALSE if DEBUG: DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line)) return ##### # Handle Variable Declaration/Assignment ##### # If we got here it means that none of the conditional forms # were found, so the only thing left might be a variable # definition/assignment. elif EQUAL in line: if not CondStack[-1][1]: if DEBUG: DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dNOTINCLUDE)) return # Catch attempts to use whitespace within variable names or references if VarIllegal.findall(line) or VarWhite.findall(line.split(EQUAL)[0].strip()): ErrorMsg("eVARNAMESPC", (cfgfile, linenum)) return # Catch attempts to dereference without name if line.count(DELIML + DELIMR): ErrorMsg("eVARNONAME", (cfgfile, linenum)) return # Do any necessary variable dereferencing line, ref_ok = DerefVar(line, cfgfile, linenum) # Only do this if all var references were valid if ref_ok: # Get left and right sides of the assignment e = line.index(EQUAL) l = line[:e].strip() r = line[e+1:].strip() # Suppress attempts to set null-named variables if not l: ErrorMsg("eVARNONAME", (cfgfile, linenum)) # Suppress attempts to set variables named starting # with certain reserved symbols because dereferencing # such variable names is impossible elif l[0] in (COMMENT, ENVIRO): ErrorMsg("eVARRESERVED", (cfgfile, linenum, l[0])) # Suppress any attempt to change a RO variable elif l in SymTable and not SymTable[l].Writeable: ErrorMsg("eVARREADONLY", (cfgfile, linenum, l)) # Load variable into the symbol table else: # Munge the variable name to incorporate # the current namespace # The NAMESPACE variable is special - It is presumed to reset # the top level namespace. if l in (NAMESPACE, NSSEP+NAMESPACE): if l == NSSEP + NAMESPACE: l=NAMESPACE # Save the new namespace SymTable[NAMESPACE].Value = r # Add to unique list of namespaces seen if r not in SymTable[NAMESPACE].LegalVals: SymTable[NAMESPACE].LegalVals.append(r) # Handle absolute variable references elif l.startswith(NSSEP): l = l[1:] # In all other cases prepend current namespace else: ns = SymTable[NAMESPACE].Value # Top level namespace variables don't need separator if ns: l = "%s%s%s" % (ns, NSSEP, l) d = VarDescriptor() # If this is a newly defined variable, set its # default to be this first value assigned and # create the new entry if l not in SymTable: # Only do this if new variable creation allowed if ALLOWNEW: d.Default = r d.Value = r SymTable[l] = d # New vars not allowed else: ErrorMsg("eVARNEW", (cfgfile, linenum)) # Otherwise, update an existing entry. # For existing entries we have to first # do the various validation checks specified # in that variable's descriptor else: update = True des = SymTable[l] typ = des.Type lv = des.LegalVals low = des.Min up = des.Max ##### # Type Enforcement ##### # We try to coerce the new value into # the specified type. If this works, we # go on to the rest of the validation tests, # otherwise mark the attempt as invalid. try: # Booleans are a special case - we accept only # a limited number of strings on the RHS if typ == TYPE_BOOL: r = Booleans[r.capitalize()] # For everything else, try an explicit coercion else: r = typ(r) except: update = False ErrorMsg("eTYPEBAD", (cfgfile, linenum, l, str(typ).split()[1][:-1])) ##### # Legal Values Enforcement ##### # These tests valid for everything except booleans. # An empty LegalVals list means this test is skipped. # For all numeric types, the test is to see if the new # value is one of the ones enumerated in LegalVals. # # For strings, LegalVals is presumed to contain a list # of regular expressions. The test is to compile each # regex and see if any match the new value. if update and lv: if typ in (TYPE_COMPLEX, TYPE_FLOAT, TYPE_INT): if r not in lv: ErrorMsg("eNOTLEGALVAL", (cfgfile, linenum, r, l)) update = False elif typ == TYPE_STRING: foundmatch = False for rex in lv: try: rexc=re.compile(rex) if rexc.match(r): foundmatch = True except: ErrorMsg("eBADREGEX", (cfgfile, linenum, rex, l)) update = False if not foundmatch: ErrorMsg("eNOTLEGALVAL", (cfgfile, linenum, r, l)) update = False ##### # Bounds Checks ##### # Check bounds for interger and floats if update and typ in (TYPE_FLOAT, TYPE_INT): if low != None and r < low: ErrorMsg("eVALSMALL", (cfgfile, linenum, r, low, l)) update = False if up != None and r > up: ErrorMsg("eVALLARGE", (cfgfile, linenum, r, up, l)) update = False # Check bounds for strings - these are min/max string lengths, if present if update and typ == TYPE_STRING: if low != None and len(r) < low: ErrorMsg("eSTRINGSHORT", (cfgfile, linenum, l, low)) update = False if up != None and len(r) > up: ErrorMsg("eSTRINGLONG", (cfgfile, linenum, l, up)) update = False # Update variable if all tests passed if update: SymTable[l].Value = r if DEBUG: DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line)) return ##### # Line Format Is Not In One Of The Recognized Forms - Syntax Error ##### else: ErrorMsg("eBADSYNTAX", (cfgfile, linenum)) ########## # End Of Line Parser ########## # End of 'ParseLine' #----------------------------------------------------------# # List Of Public Names Available To Program Importing Us # #----------------------------------------------------------# __all__ = ["ParseConfig", "RetObj", "TYPE_BOOL", "TYPE_COMPLEX", "TYPE_FLOAT", "TYPE_INT", "TYPE_STRING", "VarDescriptor" ] #----------------------------------------------------------# # Entry Point On Direct Invocation # #----------------------------------------------------------# if __name__ == '__main__': # Print program information print BANNER # Print things we know about the environment pk = Predefined.keys() pk.sort() for var in pk: val = Predefined[var] print "%s%s%s" % (var, (20-len(var)) * " ", val)