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