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.107 2004/03/12 09:10:02 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


#----------------------------------------------------------#
#                 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     = '#'         # Comment introducer character
DELIML      = '['         # Left delimiter for vbl reference
DELIMR      = ']'         # Right delimiter for vbl reference
DOLLAR      = '$'         # 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
                 }



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

eCONFOPEN   = "Cannot Open The File '%s'"
eENDIFEXTRA = FILENUM + " " + ENDIF + " Without Matching Condition"
eERROR      = "ERROR"


###########
# Prompts
###########


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

wENDIFBAD    = FILENUM + " Text After " + ENDIF + " Ignored"
wWARNING     = "WARNING"


#--------------------------- 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
    line = ConditionLine(line)

    ##########
    # Beginning Of Line Parser
    ##########

    # Only attempt on non-blank lines
    if line:

        # Process .endif statements before checking state since
        # these potentially change the 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

        # If we are in a False conditional block, this line
        # is ignored.

        if not CondStack[-1]:
            if DEBUG:
                DebugMsg(dLINEIGNORE %(cfgfile, linenum, orig))
            return

        # Process .include statements

        if line.startswith(INCLUDE):

            ParseFile(line.split(INCLUDE)[1].strip())
            return

        # Substitute references to reserved symbols

        for ref in Reserved.keys():
            line = line.replace("%s%s%s" % (DELIML, ref, DELIMR), Reserved[ref])


    ##########
    # End Of Line Parser
    ##########

    if DEBUG:
        DebugMsg(dPARSEDLINE %(cfgfile, linenum, orig, line))
         
# End of 'ParseLine'