diff --git a/tconfpy.py b/tconfpy.py index 670cf13..2475143 100755 --- a/tconfpy.py +++ b/tconfpy.py @@ -6,7 +6,7 @@ # Program Information PROGNAME = "tconfpy" -RCSID = "$Id: tconfpy.py,v 1.146 2004/03/25 07:03:20 tundra Exp $" +RCSID = "$Id: tconfpy.py,v 1.147 2004/03/25 10:43:45 tundra Exp $" VERSION = RCSID.split()[2] # Copyright Information @@ -55,6 +55,7 @@ MSGPROMPT = "%s>" FILENUM = "[File: %s Line: %s] " # Display filename and linenum +NOLINENUM = "---" # Use as line number when none is needed PTR = " ---> " # Textual pointer for debug output @@ -90,7 +91,7 @@ # Regular Expressions -reVARREF = r"\%s.*\%s" % (DELIML, DELIMR) # Variable reference +reVARREF = r"\%s.+?\%s" % (DELIML, DELIMR) # Variable reference VarRef = re.compile(reVARREF) @@ -177,6 +178,7 @@ descript = VarDescriptor() descript.Value = eval(sym) + descript.Writeable = False SymTable[sym] = descript @@ -242,7 +244,6 @@ 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["eRESERVED"] = FILENUM + "Cannot Modify Value Of Reserved Symbol '%s'" Messages["eSTRINGLONG"] = FILENUM + "Right-Hand-Side, '%s' Longer Than Max Allowed Length, %s, For Variable '%s'" Messages["eSTRINGSHORT"] = FILENUM + "Right-Hand-Side, '%s' Shorter Than Min Allowed Length, %s, For Variable '%s'" Messages["eTYPEBAD"] = FILENUM + "Type Mismatch. '%s' Must Be Assigned Values Of Type %s Only" @@ -444,6 +445,16 @@ ParseFile(cfgfile, eSTARTUP, 0) + # Make sure we had all condition blocks balanced with matching '.endif' + + finalcond = len(CondStack) + if finalcond != 1: + ErrorMsg("eENDIFMISS", (cfgfile, NOLINENUM, finalcond-1)) + + # Make sure we ended any literal processing properly + if INLITERAL: + WarningMsg("wENDLITMISS", (cfgfile, NOLINENUM)) + # Return the parsing results if DEBUG: @@ -479,8 +490,8 @@ # 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 @@ -534,12 +545,11 @@ global IgnoreCase, MsgList, SymTable, TotalLines - try: - - cf = open(cfgfile) - # Successful open of config file - Begin processing it - linenum=0 + linenum=0 + + try: + cf = open(cfgfile) # Process and massage the configuration file for line in cf.read().splitlines(): @@ -552,21 +562,10 @@ # Close the config file cf.close() - # File open failed for some reason except: ErrorMsg("eCONFOPEN", (current_cfg, current_linenum, 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)) - - # Make sure we ended any literal processing properly - if INLITERAL: - WarningMsg("wENDLITMISS", (cfgfile, linenum)) - # End of 'ParseFile()' @@ -591,7 +590,12 @@ # However, they are ignored within False conditional blocks. ##### - if line.strip() in (LITERAL, ENDLITERAL) and CondStack[-1]: + if line.strip() in (LITERAL, ENDLITERAL): + + if not CondStack[-1]: + if DEBUG: + DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dNOTINCLUDE)) + return if line.strip() == LITERAL: if INLITERAL: @@ -631,406 +635,443 @@ 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 - if line: + # Line was blank + if not line: - # Get first token on the line - FIRSTTOK = line.split()[0] + # Note blank lines for debug purposes + if DEBUG: + DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dBLANKLINE)) - ##### - # ENDIF Processing - Must be done before state check - # because ENDIF can change parser state - ##### + return - if line == ENDIF: - # Remove one level of conditional nesting - CondStack.pop() + # Get first token on the line + FIRSTTOK = line.split()[0] - # Error, if there are more .endifs than conditionals - if not CondStack: - ErrorMsg("eENDIFEXTRA", (cfgfile, linenum)) - CondStack.append(False) # Restore sentinel & inhibit further parsing + ##### + # ENDIF Processing + ##### - ##### - # Check State Of Parser - ##### + 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): if not CondStack[-1]: if DEBUG: DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dNOTINCLUDE)) return - ##### - # Namespace Processing - ##### - if (len(VarRef.findall(line)) == 1 and line[0] == DELIML and line[-1] == DELIMR): + # Set the new namespace + ns = line[1:-1] - # Set the new namespace - ns = line[1:-1] - SymTable[NAMESPACE].Value = ns + 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) + # 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]: + 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): ##### - # INCLUDE Processing + # Existential Conditionals ##### - elif FIRSTTOK == INCLUDE: - line, ref_ok = DerefVar(line.split(INCLUDE)[1].strip(), cfgfile, linenum) + if FIRSTTOK in (IFALL, IFANY, IFNONE): - # Only attempt the include if all the variable dereferencing was successful - if ref_ok: - ParseFile(line, cfgfile, linenum) + if FIRSTTOK == IFALL: + line = line.split(IFALL)[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 - # - ##### + elif FIRSTTOK == IFANY: + line = line.split(IFANY)[1].strip() - # 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() + line = line.split(IFNONE)[1].strip() - if EQUIV in line or NOTEQUIV in line: + # There must be at least one var reference in the condition - # Only one operator permitted - if (line.count(EQUIV) + line.count(NOTEQUIV)) > 1: - ErrorMsg("eEQUIVEXTRA", (cfgfile, linenum)) + 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 - else: - - # Dereference all variables - line, ref_ok = DerefVar(line, cfgfile, linenum) + if FIRSTTOK == IFANY and not resolved: + condstate = False - # Reference to undefined variables forces False - if not ref_ok: - condstate = False + if FIRSTTOK == IFNONE and resolved: + condstate = False - # So does a failure of the equality test itself - else: - invert = False - operator = EQUIV - condstate = True + # Bogus conditional syntax - no variable refs found + else: + ErrorMsg("eBADCOND", (cfgfile, linenum, FIRSTTOK, eNOVARREF)) - 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 + # Force parse state to False on an error + condstate = False - if invert: - condstate = not condstate + ##### + # (In)Equality Conditionals - IF string EQUIV/NOTEQUIV string forms + ##### + else: + line = line.split(IF)[1].strip() - # Conditional Syntax Error - else: - ErrorMsg("eBADCOND", (cfgfile, linenum, FIRSTTOK, eIFBAD)) + if EQUIV in line or NOTEQUIV in line: - # Force parse state to False on an error + # Only one operator permitted + if (line.count(EQUIV) + line.count(NOTEQUIV)) > 1: + ErrorMsg("eEQUIVEXTRA", (cfgfile, linenum)) condstate = False - # Set parser state based on a successful conditional test - - CondStack.append(condstate) - - # Now reflect this in the parsed line - line = sTRUE - if not condstate: - line = sFALSE - - - ##### - # Handle New 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: - - # 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 any attempt to change a RO variable - - if l in SymTable and not SymTable[l].Writeable: - - ErrorMsg("eVARREADONLY", (cfgfile, linenum, l)) - - # Suppress any attempt to change a Reserved symbol - - elif l in Reserved: - ErrorMsg("eRESERVED", (cfgfile, linenum, l)) - - # Load variable into the symbol table else: - # Munge the variable name to incorporate - # the current namespace + # Dereference all variables + line, ref_ok = DerefVar(line, cfgfile, linenum) - # The NAMESPACE variable is special - It is presumed to reset - # the top level namespace. - - if l in (NAMESPACE, NSSEP+NAMESPACE): + # Reference to undefined variables forces False + if not ref_ok: + condstate = False - 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 + # So does a failure of the equality test itself else: - ns = SymTable[NAMESPACE].Value + invert = False + operator = EQUIV + condstate = True - # Top level namespace variables don't need separator - if ns: - l = "%s%s%s" % (ns, NSSEP, l) - - d = VarDescriptor() + if operator not in line: # Must be NOTEQUIV + invert = True + operator = NOTEQUIV - # If this is a newly defined variable, set its - # default to be this first value assigned and - # create the new entry + line = line.split(operator) - if l not in SymTable: - d.Default = r - d.Value = r - SymTable[l] = d + if line[0].strip() != line[1].strip(): + condstate = False - # 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 + if invert: + condstate = not condstate - ##### - # 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. + # Conditional Syntax Error + else: + ErrorMsg("eBADCOND", (cfgfile, linenum, FIRSTTOK, eIFBAD)) - try: - # Booleans are a special case - we accept only - # a limited number of strings on the RHS + # Force parse state to False on an error + condstate = False - if typ == TYPE_BOOL: - r = Booleans[r.capitalize()] + # Set parser state based on a successful conditional test + # But it has to be ANDed with the state of the enclosing block - # For everything else, try an explicit coercion - else: - r = typ(r) - - except: + enclosing = CondStack[-1] + CondStack.append(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 New 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]: + if DEBUG: + DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dNOTINCLUDE)) + 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 any attempt to change a RO variable + + if 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: + d.Default = r + d.Value = r + SymTable[l] = d + + # 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 - ErrorMsg("eTYPEBAD", (cfgfile, linenum, l, str(typ).split()[1][:-1])) + + 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, r, low, l)) + update = False + + if up != None and len(r) > up: + ErrorMsg("eSTRINGLONG", (cfgfile, linenum, r, up, l)) + update = False - ##### - # Legal Values Enforcement - ##### + # Update variable if all tests passed + if update: + SymTable[l].Value = r - # 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 DEBUG: + DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line)) - 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 + return + ##### + # Line Format Is Not In One Of The Recognized Forms - Syntax Error + ##### - ##### - # 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, r, low, l)) - update = False - - if up != None and len(r) > up: - ErrorMsg("eSTRINGLONG", (cfgfile, linenum, r, up, l)) - update = False - - - # Update variable if all tests passed - if update: - SymTable[l].Value = r - - ##### - # Line Format Is Not In One Of The Recognized Forms - Syntax Error - ##### - - # To keep the code structure clean, the ENDIF and INCLUDE - # processing above falls through to here as well. We ignore - # it because any problems with these directives have already been - # handled. - - elif line != ENDIF and FIRSTTOK != INCLUDE: - ErrorMsg("eBADSYNTAX", (cfgfile, linenum)) - + else: + ErrorMsg("eBADSYNTAX", (cfgfile, linenum)) ########## @@ -1038,21 +1079,5 @@ ########## - ########## - # Write Fully Parsed Line To Debug Log If Requested - ########## - - 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'