diff --git a/twander.py b/twander.py index 2169822..f079586 100755 --- a/twander.py +++ b/twander.py @@ -4,7 +4,7 @@ PROGNAME = "twander" -RCSID = "$Id: twander.py,v 1.89 2002/12/04 22:48:03 tundra Exp $" +RCSID = "$Id: twander.py,v 1.90 2002/12/06 21:23:18 tundra Exp $" VERSION = RCSID.split()[2] @@ -15,6 +15,7 @@ import getopt import mutex import os +import re from socket import getfqdn from stat import * import sys @@ -201,13 +202,17 @@ # Configuration File Related Literals ##### + +ASSIGN = "=" # Assignment for variable definitions CONF = "" # Config file user selected with -c option -CMDKEY = r'&' # Command key delimiter COMMENT = r"#" # Comment character DIRNAME = r'[DIRECTORY]' # Substitution field in config files +ENVVBL = r'$' # Symbol denoting an environment variable FILE = r'[FILE]' # Substitution field in config files FILES = r'[FILES]' # Ditto -ENVIRO = r'$' # Introduces environment variables +MAXNESTING = 32 # Maximum depth of nested variable definitions +reVAR = r"\[.*?\]" # Regex describing variable notation + #----------------------------------------------------------# @@ -221,14 +226,18 @@ # Errors +eBADCFGLINE = "Bogus Configuration Entry In Line %s: %s" +eBADENVVBL = "Environment Variable %s In Line %s Not Set: %s" eBADROOT = " %s Is Not A Directory" eDIRRD = "Cannot Open Directory : %s --- Check Permissions." -eDUPKEY = "Duplicate Key In Configuration File Found In Entry: \'%s\'" +eDUPKEY = "Found Duplicate Command Key '%s' In Line %s: %s" eERROR = "ERROR" eINITDIRBAD = "Cannot Open Starting Directory : %s - Check Permissions - ABORTING!." -eNOENV = "Configuration File References Undefined Environment Variable: %s" eOPEN = "Cannot Open File: %s" +eREDEFVAR = "Variable %s Redefined In Line %s: %s" eTOOMANY = "You Can Only Specify One Starting Directory." +eUNDEFVBL = "Undefined Variable %s Referenced In Line %s: %s" +eVBLTOODEEP = "Variable Definition Nested Too Deeply At Line %s: %s" # Information @@ -333,64 +342,30 @@ cf = open(CONF) except: WrnMsg(wCONFOPEN % (CONF, wNOCMDS)) - UI.rcfile = {} + UI.CmdTable = {} + UI.Symtable = {} return # Successful open of config file - Begin processing it # Cleanout any old - UI.rcfile = {} - + UI.CmdTable = {} + UI.SymTable = {} + linenum = 0 + # Process and massage the configuration file for line in cf.read().splitlines(): + linenum += 1 - # Lex for comment token and discard until EOL - # A line beginning with the comment token is thus - # turned into a blank line, which is discarded. + # Lex for comment token and discard until EOL. idx = line.find(COMMENT) - if idx > -1: # found a comment character + if idx > -1: line = line[:idx] - # Anything which gets through the next conditional - # must be a non-blank line - i.e., Configuration information - # we care about, so process it and save for future use. - - if line != "": - fields = line.split() - for x in range(1,len(fields)): - - # Process environment variables - if fields[x][0] == ENVIRO: - envval = os.getenv(fields[x][1:]) - if not envval: # Environment variable not defined - ErrMsg(eNOENV % fields[x]) - sys.exit(1) - else: - fields[x] = envval - - # Get command key value and store in dictionary - - keypos = fields[0].find(CMDKEY) # Look for key delimiter - - # No delimiter or delimiter at end of string - if (keypos < 0) or (CMDKEY == fields[0][-1]): - WrnMsg(wCMDKEY % fields[0]) - key = fields[0] # Use whole word as index - - # Found legit delimiter, use it as dictionary index - else: - key = fields[0][keypos+1] - - if key in UI.rcfile: # This is a Python 2.2 or later idiom - ErrMsg(eDUPKEY % fields[0]) # Duplicate key found - cf.close() - sys.exit(1) - - # Save command name and command using key as index - UI.rcfile[key] = ["".join(fields[0].split(CMDKEY)), - " ".join(fields[1:]) - ] + # Parse whatever is left on non-blank lines + if line: + ParseLine(line, linenum) cf.close() @@ -398,6 +373,142 @@ ##### +# Parse A Line From A Configuration File +# Routine Assumes That Comments Previously Removed +##### + + +def ParseLine(line, num): + global UI + revar = re.compile(reVAR) + + # Get rid of trailing newline, if any + if line[-1] == '\n': + line = line[:-1] + + fields = line.split() + + ##### + # Blank Lines - Ignore + ##### + + if len(fields) == 0: + pass + + ##### + # Variable Definitions + ##### + + elif fields[0].find(ASSIGN) > 0: + assign = fields[0].find(ASSIGN) + name = line[:assign] + val=line[assign+1:] + + # Warn on variable redefinitions + if UI.SymTable.has_key(name): + ErrMsg(eREDEFVAR % (name, num, line)) + sys.exit(1) + + UI.SymTable[name] = val + + ##### + # Command Definitions + ##### + + elif len(fields[0]) == 1: + + # Must have at least 3 fields for a valid command definition + if len(fields) < 3: + ErrMsg(eBADCFGLINE % (num, line)) + sys.exit(1) + else: + cmdkey = fields[0] + cmdname = fields[1] + cmd = line.split(fields[1])[1] + + # Evaluate the command line, replacing + # variables as needed + + doeval = TRUE + depth = 0 + + while doeval: + # Get a list of variable references + vbls = revar.findall(cmd) + + # Throw away references to builtins - these are + # processed at runtime and should be left alone here. + + # Note that we iterate over a *copy* of the variables + # list, because we may be changing that list contents + # as we go. i.e., It is bogus to iterate over a list + # which we are changing during the iteration. + + for x in vbls[:]: + vbl = x[1:-1] + if UI.BuiltIns.has_key(vbl): + vbls.remove(x) + + if vbls: + for x in vbls: + vbl = x[1:-1] + + # Process ordinary variables + if UI.SymTable.has_key(vbl): + cmd = cmd.replace(x, UI.SymTable[vbl]) + + # Process environment variables. + # If an environment variable is referenced, + # but not defined, this is a fatal error + + elif vbl[0] == ENVVBL: + envvbl = os.getenv(vbl[1:]) + if envvbl: + cmd = cmd.replace(x, envvbl) + else: + ErrMsg(eBADENVVBL % (x, num, line)) + sys.exit(1) + + # Process references to undefined variables + else: + ErrMsg(eUNDEFVBL % (x, num, line)) + sys.exit(1) + + # No substitutions left to do + else: + doeval = FALSE + + # Bound the number of times we can nest a definition + # to prevent self-references which give infinite nesting depth + + depth += 1 + if depth == MAXNESTING: + doeval = FALSE + + # See if there are still unresolved variable references. + # If so, let the user know + + if revar.findall(cmd): + ErrMsg(eVBLTOODEEP % (num, cmd)) + sys.exit(1) + + # Add the command entry to the command table. + # Prevent duplicate keys from being entered. + + if UI.CmdTable.has_key(cmdkey): + ErrMsg(eDUPKEY % (cmdkey, num, line)) + sys.exit(1) + else: + UI.CmdTable[cmdkey] = [cmdname, cmd] + + else: + ErrMsg(eBADCFGLINE % (num, line)) + sys.exit(1) + +# End of 'ParseLine()' + + +##### # Print Usage Information ##### @@ -1378,6 +1489,9 @@ # Get starting directory into canonical form STARTDIR = os.path.abspath(STARTDIR) +# Setup builtin variables +UI.BuiltIns = {"DIR":"", "SELECTION":"", "SELECTIONS":"", "DSELECTION":"", + "DSELECTIONS":"", "PROMPT:":""} # Parse the and store configuration file, if any ParseConfFile(None)