diff --git a/tconfpy.py b/tconfpy.py index 682c10c..b2b9ed2 100755 --- a/tconfpy.py +++ b/tconfpy.py @@ -6,7 +6,7 @@ # Program Information PROGNAME = "tconfpy" -RCSID = "$Id: tconfpy.py,v 1.175 2004/04/14 23:33:12 tundra Exp $" +RCSID = "$Id: tconfpy.py,v 1.176 2004/04/16 05:48:53 tundra Exp $" VERSION = RCSID.split()[2] # Copyright Information @@ -57,7 +57,7 @@ # Formatting Constants ATEOF = "EOF" # Use as line number when at EOF -FILENUM = "[File: %s Line: %s] " # Display filename and linenum +FILENUM = "[File: %s Line: %s] " # Display filename and linenum MSGPROMPT = "%s>" PTR = " ---> " # Textual pointer for debug output STARTUP = "STARTUP" # Indicates message before any lines processed @@ -157,7 +157,6 @@ #----------------------------------------------------------# -ALLOWNEWNS = True # Allow new namespace creation in cfg file ALLOWNEWVAR = True # Allow new variable creation in cfg file DEBUG = False # Control Debug output LITERALVARS = False @@ -272,10 +271,11 @@ # Debug Messages -Messages["dLINEIGNORE"] = FILENUM + " '%s'" + PTR + "%s\n" +Messages["dLINEIGNORE"] = FILENUM + "'%s'" + PTR + "%s" Messages["dNAMESPACE"] = FILENUM + "Setting Current Namespace To: '%s'" Messages["dNUMLINES"] = "Processing File '%s' Resulted In %d Total Lines Parsed" -Messages["dPARSEDLINE"] = FILENUM + " '%s'" + PTR + "'%s'\n" +Messages["dPARSEDLINE"] = FILENUM + "'%s'" + PTR + "'%s'" +Messages["dREGEXMATCH"] = FILENUM + "Value '%s' Matched Regex '%s' For Variable '%s'" Messages["dVARREF"] = FILENUM + "Variable Dereference: '%s'" + PTR + "'%s'" @@ -308,7 +308,6 @@ 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["eNAMESPACENEW"] = FILENUM + "New Namespace Creation Not Permitted. Current Namespace Unchanged: '%s'" 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" @@ -340,8 +339,7 @@ Messages["wLITEXTRA"] = FILENUM + "Already In A Literal Block. '%s' Statement Ignored" % LITERAL -# Determine Length Of Longest Message Type -# Needed for formatting later +# Determine Length Of Longest Message Name MAXMSG = 0 for msg in Messages: @@ -415,15 +413,14 @@ # Public API To Module # #----------------------------------------------------------# -def ParseConfig(cfgfile, InitialSymTable={}, AllowNewVars=True, AllowNewNamespaces=True, Debug=False, LiteralVars=False): +def ParseConfig(cfgfile, InitialSymTable={}, AllowNewVars=True, Debug=False, LiteralVars=False): global DebugMsgs, ErrMsgs, WarnMsgs, LiteralLines - global CondStack, ALLOWNEWVAR, ALLOWNEWNS, DEBUG, SymTable, TotalLines, LITERALVARS, INLITERAL + global CondStack, ALLOWNEWVAR, DEBUG, SymTable, TotalLines, LITERALVARS, INLITERAL # Initialize the globals ALLOWNEWVAR = AllowNewVars - ALLOWNEWNS = AllowNewNamespaces DEBUG = Debug LITERALVARS = LiteralVars @@ -522,21 +519,13 @@ if NAMESPACE not in SymTable: SymTable[NAMESPACE] = VarDescriptor() SymTable[NAMESPACE].Value = "" - SymTable[NAMESPACE].LegalVals.append("") - # Ensure that the initial namespace is properly formed. + # Otherwise, ensure that the initial namespace is properly formed. # If not, revert to root namespace. - if not ValidateSymbolName(SymTable[NAMESPACE].Value, STARTUP, STARTUP, AllowEmpty=True): + elif not ValidateSymbolName(SymTable[NAMESPACE].Value, STARTUP, STARTUP, AllowEmpty=True): SymTable[NAMESPACE].Value = "" - # Make sure initial namespace is in LegalVals - - initialns = SymTable[NAMESPACE].Value - - if initialns not in SymTable[NAMESPACE].LegalVals: - SymTable[NAMESPACE].LegalVals.append(initialns) - # Report namespace to debug output if DEBUG: @@ -855,48 +844,23 @@ # Namespace Processing ##### + # All we do here is detect the "[...]" form of the statement and + # convert it into a "NAMESPACE = ..." form. That way, the normal + # variable setting routines below can handle it as usual. + 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 - + # Get the new namespace - ns = line[1:-1] - - # Flag attempts to create new namespaces if this is disabled - if not ALLOWNEWNS and ns != SymTable[NAMESPACE].Value and ns not in SymTable[NAMESPACE].LegalVals: - ErrorMsg("eNAMESPACENEW", (cfgfile, linenum, SymTable[NAMESPACE].Value)) - - # Make sure the namespace is properly formed - - elif not ValidateSymbolName(ns, cfgfile, linenum, AllowEmpty=True): - pass # Validation function issues relevant error messages - - - # Install the new namespace - else: - - 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("dNAMESPACE", (cfgfile, linenum, SymTable[NAMESPACE].Value)) - DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line)) - return + line = "%s%s%s" % (NAMESPACE, EQUAL, line[1:-1]) ##### # INCLUDE Processing ##### - elif FIRSTTOK == INCLUDE: + if FIRSTTOK == INCLUDE: if not CondStack[-1][1]: if DEBUG: @@ -1075,9 +1039,6 @@ elif EQUAL in line: - # Flag to set whether existing variable should be updated - update = True - if not CondStack[-1][1]: if DEBUG: DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dNOTINCLUDE)) @@ -1133,37 +1094,14 @@ # Munge the variable name to incorporate # the current namespace - # The NAMESPACE variable is special - It is presumed to reset - # the current namespace. + # The NAMESPACE variable is special, it is always + # relative to the root namespace. if l in (NAMESPACE, NSSEP+NAMESPACE): - # Flag attempts to create new namespaces if this is disabled - if not ALLOWNEWNS and r != SymTable[NAMESPACE].Value and r not in SymTable[NAMESPACE].LegalVals: - ErrorMsg("eNAMESPACENEW", (cfgfile, linenum, SymTable[NAMESPACE].Value)) - update = False - - # Make sure the new namespace is properly formed + # Make sure it is in absolute format + l = NAMESPACE - elif not ValidateSymbolName(r, cfgfile, linenum, AllowEmpty=True): - update = False # Validation function issues relevant error messages - - # Install the new namespace - else: - 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) - - if DEBUG: - DebugMsg("dNAMESPACE", (cfgfile, linenum, SymTable[NAMESPACE].Value)) - - # Handle absolute variable references elif l.startswith(NSSEP): @@ -1179,8 +1117,6 @@ 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 @@ -1189,6 +1125,10 @@ # Only do this if new variable creation allowed if ALLOWNEWVAR: + + # Build a new variable descriptor + + d = VarDescriptor() d.Default = r d.Value = r SymTable[l] = d @@ -1197,115 +1137,34 @@ 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 + # Otherwise, update an existing entry. For existing + # entries, the update only takes place if all the + # validation tests specified in the descriptor are + # passed successfully. else: - des = SymTable[l] - typ = des.Type - lv = des.LegalVals - low = des.Min - up = des.Max - ##### - # Type Enforcement - ##### + # If all the validation testing is OK, update the + # entry. The validation function produces any + # relevant errors. + + if ValidateValue(l, r, cfgfile, linenum): - # 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. + # Write the new value for this variable - try: - # Booleans are a special case - we accept only - # a limited number of strings on the RHS + # Translate various strings to their + # equivalent logical value for boolean + # variables first - if typ == TYPE_BOOL: + if SymTable[l].Type == 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 + # Produce debug output when we change namespaces + if DEBUG and l == NAMESPACE: + DebugMsg("dNAMESPACE", (cfgfile, linenum, r)) + if DEBUG: DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line)) @@ -1369,6 +1228,141 @@ # End Of 'ValidateSymbolName()' +##### +# Validate Value +##### + +def ValidateValue(l, r, cfgfile, linenum): + + update = True + + # Do some special preprocessing for the NAMESPACE variable + + if l == NAMESPACE: + + # The root namespace is always permitted regardless of other + # restrictions the calling program may have put in place. + + if not r: + return True + + # Other NAMESPACE values must conform to the same rules + # as variable names + + if not ValidateSymbolName(r, cfgfile, linenum, AllowEmpty=True): + update = False # Validation function issues appropriate errors + + + 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: + ErrorMsg("eTYPEBAD", (cfgfile, linenum, l, str(typ).split()[1][:-1])) + update = False + + + ##### + # 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 + + # This can be kind of complex, so produce + # debug output here to help the poor user + + if DEBUG: + DebugMsg("dREGEXMATCH", (cfgfile, linenum, r, rex, l)) + + 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 + + return update + + +# End of 'ValidateValue()' + #----------------------------------------------------------# # List Of Public Names Available To Program Importing Us #