301 lines
10 KiB
Plaintext
301 lines
10 KiB
Plaintext
From: scott at chronis.pobox.com (scott cotton)
|
|
Date: 15 Apr 1999 00:59:12 GMT
|
|
Subject: reval builtin
|
|
References: <19990331160323.21601.00000471@ng-fi1.aol.com> <7du3ab$3ag@chronicle.concentric.net> <jtaewsfmlj.fsf@wazor.biostat.wisc.edu> <xcxP2.1302$rd2.27684@news14.ispnews.com> <371219E9.9EA91839@home.com> <7f36m6$koq@ds2.acs.ucalgary.ca>
|
|
Message-ID: <slrn7hae7s.ih.scott@chronis.pobox.com>
|
|
Content-Length: 9922
|
|
X-UID: 239
|
|
|
|
here's another config file utility. i know that some
|
|
pythoners won't like this, but it uses shell-like
|
|
'$variable' expansion. i personally like this interpolation
|
|
syntax in config files (but not in programming languages).
|
|
|
|
enjoy,
|
|
|
|
scott
|
|
|
|
"""
|
|
Utilities for reading config files.
|
|
|
|
functions:
|
|
readcf(path) - return a dict containing the vars and vals
|
|
readtxt(txt) - same as above but for text instead of file
|
|
dict2cf(dict, templ=None) - see function doc string
|
|
"""
|
|
|
|
|
|
import string
|
|
|
|
class SyntaxErrorCF(StandardError):
|
|
def __init__(self, *args):
|
|
self.args = args
|
|
|
|
#
|
|
# "eval" for config files
|
|
# given the rhs of an assignment as plain text,
|
|
# figure out what it's python native value is.
|
|
#
|
|
def figger(rhs):
|
|
rhs = string.strip(rhs)
|
|
if rhs == "None":
|
|
return None
|
|
elif rhs == 'yes' or rhs == 'on': # boolean
|
|
return 1
|
|
elif rhs == 'no' or rhs == 'off': # more booleans
|
|
return 0
|
|
elif len(rhs) >= 6 and rhs[:3] in ('"""', "'''") \
|
|
and rhs[:3] == rhs[-3:]:# TQS
|
|
return rhs[3:-3]
|
|
elif rhs[0] in ("'", '"') and rhs[0] == rhs[-1]: # plain string
|
|
return rhs[1:-1]
|
|
elif rhs[0] == '[' and rhs[-1] == ']': # list (of strings only)
|
|
list = []
|
|
inner = rhs[1:-1]
|
|
for s in string.split(inner, ","): # items in the list
|
|
s = string.strip(s)
|
|
if not s:
|
|
continue
|
|
if s == '""' or s == "''": # empty string
|
|
list.append("")
|
|
elif len(s) >= 6 \
|
|
and s[:3] in ('"""', "'''") \
|
|
and s[-3:] == s[:3]: # triple quoted string
|
|
list.append(s[3:-3])
|
|
elif s[0] in ("'", '"') and s[0] == s[-1]: # regular string
|
|
list.append(s[1:-1])
|
|
# since lists can only be lists of strings, we allow bare words
|
|
else:
|
|
list.append(s)
|
|
return list
|
|
else: # the only remaining value is an int.
|
|
try:
|
|
return string.atoi(rhs)
|
|
except ValueError: # oh well, the var get's None.
|
|
return rhs
|
|
|
|
#
|
|
# return a dict containing non variable substituted key-values
|
|
# from the text of a config file.
|
|
#
|
|
def getassignments(text):
|
|
linect = 0
|
|
assgns = {}
|
|
var = None
|
|
check_tqs = 0 # is it a triple quoted string?
|
|
text = string.replace(text, "\\\n", "")
|
|
for line in string.split(text, "\n"):
|
|
stripped = string.strip(line)
|
|
linect = linect + 1
|
|
if check_tqs: # check for the end of a triple quoted string
|
|
rhs = rhs + "\n" + line
|
|
if len(stripped) >= 3 and stripped[-3:] == check_tqs:
|
|
# the variable check_tqs contains either ''' or """ as
|
|
# a string literal.
|
|
assgns[var] = figger(rhs)
|
|
var, rhs = None, None
|
|
check_tqs = 0
|
|
elif not stripped or stripped[0] == '#':
|
|
# we try to wrap everything we know up from a previous assignment
|
|
if var is not None and rhs is not None:
|
|
if not string.strip(rhs):
|
|
raise SyntaxErrorCF(linect -1, "%s = " % (var))
|
|
assgns[var] = figger(rhs)
|
|
# reinitialize the variables.
|
|
rhs = None
|
|
var = None
|
|
continue
|
|
elif line[0] not in " \t":
|
|
if var is not None and rhs is not None:
|
|
if not string.strip(rhs):
|
|
raise SyntaxErrorCF(linect -1, "%s = " % (var))
|
|
assgns[var] = figger(rhs)
|
|
var, rhs = None, None
|
|
if string.find(line, "=") == -1: # not an assignment
|
|
raise SyntaxErrorCF(linect, line)
|
|
lhs, rhs = string.split(line, "=", 1)
|
|
var = string.strip(lhs)
|
|
if len(string.strip(rhs)) >= 3 \
|
|
and string.strip(rhs)[:3] in ('"""', "'''"):
|
|
check_tqs = string.strip(rhs)[:3]
|
|
var = string.strip(lhs)
|
|
else:
|
|
if var is None:
|
|
raise SyntaxErrorCF(linect, line)
|
|
rhs = rhs + " " + line
|
|
# here we just wrap up what we found out iterating over the
|
|
# lines in the text one last time.
|
|
if var is not None and rhs is not None:
|
|
if not string.strip(rhs):
|
|
raise SyntaxErrorCF(linect -1, "%s = " % (var))
|
|
assgns[var] = figger(rhs)
|
|
return assgns
|
|
|
|
#
|
|
# globally replace variables with values (eg $prefix)
|
|
#
|
|
def global_replace(text, dict):
|
|
for k, v in dict.items():
|
|
text = string.replace(text, '$' + k, v)
|
|
return text
|
|
|
|
#
|
|
# replace variables defined earlier in a config file
|
|
#
|
|
def internal_replace(dict, replacements):
|
|
for var, rvars in replacements.items():
|
|
for rvar in rvars:
|
|
cval = dict[var]
|
|
if type(cval) is type([]):
|
|
l = []
|
|
for s in cval:
|
|
l.append(string.replace(s, '$' + rvar, dict[rvar]))
|
|
dict[var] = l
|
|
elif type(cval) is type(""):
|
|
dict[var] = string.replace(cval, '$' + rvar, dict[rvar])
|
|
return dict
|
|
|
|
|
|
#
|
|
# top level function
|
|
#
|
|
def readcf(file, global_repl=None, int_repl=None):
|
|
"""
|
|
given a path to a file, and any replacements that need to
|
|
be made, return a dictionary containing the variables
|
|
in the config file as keys and the values as values.
|
|
"""
|
|
text = open(file).read()
|
|
text = string.replace(text, "\\\n", "") # get rid of \ cont'd lines
|
|
if global_repl is not None:
|
|
text = global_replace(text, global_repl)
|
|
d = getassignments(text)
|
|
if int_repl is not None:
|
|
d = internal_replace(d, int_repl)
|
|
return d
|
|
|
|
|
|
def readtxt(txt, global_repl=None, int_repl=None):
|
|
"""
|
|
reads the text of a cf file into a dict and return the dict.
|
|
"""
|
|
txt = string.replace(txt, "\\\n", "")
|
|
if global_repl is not None:
|
|
txt = global_replace(txt, global_repl)
|
|
d = getassignments(text)
|
|
if int_repl is not None:
|
|
d = internal_replace(d, int_repl)
|
|
return d
|
|
|
|
|
|
def dict2cf(dict, templ=None):
|
|
"""
|
|
given a dictionary keyed by the configuration variables
|
|
and whose values are a 3-tuple of (var-value, descr, vtype)
|
|
where vtype is one of 'str', 'int', 'bool' or 'list', return
|
|
text suitable for writing a config file. If optional second arg
|
|
'templ' is passed, then transpose the values to the template,
|
|
which should be a string with 1 %(<varname>)s entry per variable
|
|
"""
|
|
txtdict = {}
|
|
for k, (val, vtype, descr) in dict.items():
|
|
valstr = ""
|
|
descr = string.rstrip(descr)
|
|
entry = descr + "\n#\n" + k + " = "
|
|
if vtype == "str":
|
|
if string.find(val, "\n") != -1:
|
|
valstr = '"""\\\n' + val + '"""'
|
|
elif string.find(val, '"') != -1:
|
|
valstr = "'" + val + "'"
|
|
else:
|
|
valstr = '"' + val + '"'
|
|
elif vtype == "bool":
|
|
if val:
|
|
valstr = "yes"
|
|
else:
|
|
valstr = "no"
|
|
elif vtype == "int":
|
|
valstr = "%d" % (val)
|
|
elif vtype == "list":
|
|
if not val:
|
|
valstr = "[]"
|
|
else:
|
|
spaces = " " * (len(k) + 4)
|
|
valstr = "["
|
|
for item in val:
|
|
if string.find(item, "\n") != -1:
|
|
valstr = "%s%s%s%s,\n%s" % (valstr,
|
|
'"""\\\n',
|
|
item,
|
|
'"""',
|
|
spaces)
|
|
elif string.find(item, '"') != -1:
|
|
valstr = "%s%s%s%s,\n%s" % (valstr,
|
|
"'",
|
|
item,
|
|
"'",
|
|
spaces)
|
|
else:
|
|
valstr = "%s%s%s%s,\n%s" % (valstr,
|
|
'"',
|
|
item,
|
|
'"',
|
|
spaces)
|
|
valstr = valstr[:-2 - len(spaces)] + "]"
|
|
else:
|
|
print "weird:", k, val, descr, vtype
|
|
valstr = valstr + "\n\n"
|
|
txtdict[k] = entry + valstr
|
|
if templ:
|
|
return templ % txtdict
|
|
else:
|
|
vars = txtdict.keys()
|
|
vars.sort()
|
|
res = ""
|
|
for v in vars:
|
|
res = res + txtdict[v]
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
On 14 Apr 1999 23:00:22 GMT, <nascheme at m67.enme.ucalgary.ca> wrote:
|
|
>On Mon, 12 Apr 1999 15:59:05 GMT, Jim Meier <fatjim at home.com> wrote:
|
|
>>This introduces some major security problems, and is a little difficult
|
|
>>to edit, but there is very little parsing needed to make it usable. Does
|
|
>>anyone know of a way to limit the damage a user can do to such a file?
|
|
>
|
|
>It would be nice to have an "reval" builtin that would only evaluate
|
|
>literals. That would make building things like config files safe and
|
|
>easy. I have two ideas on how to accomplish this:
|
|
>
|
|
> 1. Create a new start symbol "reval_input" in the Grammar/Grammar
|
|
> and add a "builtin_reval" function in Python/bltinmodule.c. Sound
|
|
> easy? Well, the connection between these two changes is long and
|
|
> twisted.
|
|
>
|
|
> 2. Use something like lex and yacc to create an extension module
|
|
> that does the Right Thing(TM). I think the problem with this
|
|
> approach is making it conform to the real Python grammar. If I
|
|
> get time I will try it.
|
|
>
|
|
>Perhaps some guru can explain an easy way to accomplish this and same me
|
|
>some time.
|
|
>
|
|
>
|
|
> Neil
|
|
|
|
|
|
|
|
|