#!/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.110 2004/03/13 00:19:04 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 # Reserved Symbols COMMENT = r'#' # Comment introducer character DELIML = r'[' # Left delimiter for vbl reference DELIMR = r']' # Right delimiter for vbl reference DOLLAR = r'$' # Used to note enviro vbl # Control and conditional symbols INCLUDE = ".include" ENDIF = ".endif" IF = ".if" # Table of reserved symbols used by parser. User is able # to include this in the right side of a variable definition # via [reserved sym]. Reserved = {"DELIML" : DELIML, "DELIMR" : DELIMR, "DOLLAR" : DOLLAR, "HASH" : COMMENT, "INCLUDE" : INCLUDE, "ENDIF" : ENDIF, "IF" : IF } # Regular Expressions reVARREF = r"\%s.+?\%s" % (DELIML, DELIMR) # Variable reference VARREF = re.compile(reVARREF) ########### # Literals ########### #----------------------------------------------------------# # 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 SymTable = {} # Results of the parsing stored here TotalLines = 0 # Total number of lines parsed #----------------------------------------------------------# # 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" eCONFOPEN = "Cannot Open The File '%s'" eENDIFEXTRA = FILENUM + " " + ENDIF + " Without Matching Condition" ########### # Prompts ########### ########### # Warning Messages ########### wWARNING = "WARNING" wENDIFBAD = FILENUM + " Text After " + ENDIF + " 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, Options={}, 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,] SymTable = Options TotalLines = 0 # 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()' ########## # 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 # 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(wENDIFBAD % (cfgfile, linenum)) # 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()) ##### # Replace Explicit References To Reserved Symbols ##### else: for ref in Reserved.keys(): line = line.replace("%s%s%s" % (DELIML, ref, DELIMR), Reserved[ref]) ########## # 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'