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.111 2004/03/14 01:39:00 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

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

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

# Control and conditional symbols

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
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, isbuiltin, 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 + "   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, 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):

    for sym in Reserved:
        line = line.replace("%s%s%s" % (DELIML, sym, DELIMR), SymTable[sym][SYM_VALUE])

    return line

# 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

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

        #####
        # .if Processing
        #
        # Must be one of the following forms -
        #
        #    .if [var]
        #    .if [var] == string
        #    .if [var] != string
        #
        #####

        if line.startswith(IF):
            
            line = line.split(IF)[1].strip()

        else:
            line = DeRefVar(line, cfgfile, linenum)
            

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