Newer
Older
tconfpy / tconfpy.py
#!/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.116 2004/03/14 21:03:36 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"
IFNOT       = CONDINTRO + "ifnot"

Reserved    = ["DELIML", "DELIMR", "DOLLAR", "EQUAL", "EQUIV", "NOTEQUIV",
               "HASH", "INCLUDE", "ENDIF", "IF", "IFNOT"]


# Regular Expressions

reVARREF    = r"\%s.+?\%s" % (DELIML, DELIMR)  # Variable reference
VarRef      = re.compile(reVARREF)


###########
# Literals
###########


TRUE   = str(True)
FALSE  = str(False)


#----------------------------------------------------------#
#          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"

eBADCOND    = FILENUM + " Bad Conditional Expression - %s:  '%s'"
eCONFOPEN   = "Cannot Open The File '%s'"
eENDIFEXTRA = FILENUM + " '" + ENDIF + "' Without Matching Condition"
eENDIFMISS  = FILENUM + " Missing %d '" + ENDIF + "' Statement(s)"
eNOVARREF   = "Must Have At Least One Variable Reference"
eVARUNDEF   = FILENUM + " " + "Attempt To Reference Undefined Variable '%s'"


###########
# Warning Messages
###########

wWARNING     = "WARNING"

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:

        #####
        # .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(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):
            ParseFile(line.split(INCLUDE)[1].strip())

        #####
        # .if Processing
        #
        # Must be one of the following forms -
        #
        #    .if string
        #    .ifnot string
        #    .if string == string
        #    .if string != string
        #
        #    where string can be any concatentation of text and variable references
        #
        #####

        if line.startswith(IFNOT) or line.startswith(IF):

            # NOTE: This next bit of logic will fail if you do not
            #       test the longest of the tokens first!!!!
            
            if line.startswith(IFNOT):
                line = line.split(IFNOT)[1].strip()
                invert = True
            else:
                line = line.split(IF)[1].strip()
                invert = False

            # Handle Equality Form: ".if string == string"
            if line.count(EQUIV):
                pass

            # Handle InEquality Form: ".if string != string"
            elif line.count(NOTEQUIV):
                pass

            # Handle Existential Forms ".if/.ifnot string"
            else:

                # There must be at least one var reference in the condition
                if len(VarRef.findall(line)) > 0:

                    # See if the variable(s) resolve
                    line, ref_ok = DerefVar(line, cfgfile, linenum, reporterr=False)

                    # If we're using '.ifnot', invert the sense of the logic
                    if invert:
                        ref_ok = not ref_ok

                    # Set parser state accordingly
                    if ref_ok:
                        line = TRUE
                        CondStack.append(True)
                    else:
                        line = FALSE
                        CondStack.append(False)                    

                # Bogus conditional syntax
                else:
                    ErrorMsg(eBADCOND % (cfgfile, linenum, eNOVARREF, orig))


        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))

        else:
            DebugMsg(dPARSEDLINE % (cfgfile, linenum, orig, line))



# End of 'ParseLine'