wasm-demo/demo/ermis-f/python_m/cur/0239

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