Entry
Preprocessor (with Python as macro language)
Jul 5th, 2000 10:00
Nathan Wallace, Hans Nowak, Snippet 113, Glyn Webster
"""
Packages: miscellaneous;text
"""
""" A preprocessor in Python that uses Python as it's macro language.
Python source code fragments between "<* *>" brackets in the text
will be removed, evaluated and replaced with their output.
When the source code fragment is an expression its 'output' is the
value it returns. When the source code fragment is a sequence of
statements its 'output' is everything it tries to print to the
standard output with "print" statements.
All code fragments are executed in the same namespace, So you can
define variables or import Python modules in one code fragment,
then use them in subsequent code fragments.
Only matching sets of <* *> brackets are processed. And you can't
use the strings '<*' '*>' in the text or the code. That isn't too
big a problem because they're not valid code in either Python or
HTML. You can use the code fragment "<* '<' + '*' *>" to
represent "<*" if you need to.
Here's a little example of Pymacro in use as a Web page preprocessor
from the command line. To create and HTML file from the file below
you'd run the command "python pymacro.py <gourds.pyml >gourds.html":
<html>
<*
Gourds = 'http://daphne.palomar.edu/wayne/ww0503.htm'
BodyTags = 'background="board.gif"'
def Url(s): return '<a href="%s">%s</a>' % (s, s)
from time import ctime, time
*>
<head>
<title>Links Page</title>
<meta name="DESCRIPTION" content="Link to Wayne's Gourds page">
</head>
<body <*BodyTags*> >
<b>Here's a link:</b><br>
<ul>
<li> Go to The World of Gourds <*Url(Gourds)*>
</ul>
</body>
<small>Last updated: <*ctime(time())*> </small>
</html>
Glyn Webster <gdw@cs.waikato.ac.nz> 2020-10-20
"""
import sys, string, cStringIO
def process_to_string(text, namespace = {}):
""" Replaces all the Python code fragments in the string 'text'
with their output, as described above, and returns the resulting
string.
All the code fragments are executed in 'namespace' (see
the 'execute' function below). The namespace is empty by
default. You can supply a preloaded one if you feel like it:
e.g.
rough_result = process(heavy_math_stuff, {'pi': 3.14})
my_namespace = {}
exec "from math import *" in my_namespace
precise_result = process(heavy_math_stuff, my_namespace)
"""
output = cStringIO.StringIO()
process_to_file(text, output, namespace)
return ouput.getvalue()
def process_to_file(text, output_file, namespace = {}):
""" Replaces all the Python code fragments in the string 'text'
with their output, as described above, writing processed
text to the file `output' as it goes.
(This one is faster if you are writing to a file.)
"""
here = 0
while 1:
start = string.find(text, '<*', here)
end = string.find(text, '*>', start)
if start == -1 or end == -1 :
break
output_file.write(text[here:start])
output_file.write(execute(text[start+2:end], namespace))
here = end + 2
output_file.write(text[here:])
def execute( code, namespace ):
""" This function executes some Python code in a namespace and
returns the code's output as a string.
If the code is an expression its 'output' is the value it returns.
'Execute' attempts to execute the code as an exppression first,
and if that causes a syntax error, tries to execute it as a
sequence of statements. (I can't see any more graceful way to
tell the difference, sorry.)
The code may modify the namespace, but that's good, you want
that to happen, for example you may want to define variables or
import Python modules into the namespace for subsequent code
fragments to use.
"""
try:
value = eval(code, namespace)
if value is None:
return ""
else:
return str(value)
except SyntaxError:
code = string.strip(code)
exec code in namespace
return ""
# If pymacro.py is called from the command line,
# process the standard input to the standard output:
#
if __name__ == "__main__":
process_to_file(sys.stdin.read(), sys.stdout)