POT.py
Publishing-Oriented Templater by Mark Paschal <markpasc@markpasc.org>
NB: 1.2's .manifest files are incompatible with 1.3. Run with -1 and delete your .manifest before upgrading, then run with --lie afterward to rebuild the .manifest file.
NB: 1.3 uses the Python 2.3 logging module. You'll need Vinay Sajip's compatible logging module to run POT.py with Python 2.2.
POT.py is an engine for publishing HTML files with embedded Python code by FTP. POT.py requires options to tell it what to publish; these are:
-h host- the FTP server to which to upload
-u user- your username on that server
--pass pass- your password on that server (if omitted, POT.py will prompt you for it, so you don't have to type it on the command line)
-r remotedir- the directory on the server that's the "root" of the site
-L localdir- the local director that's the "root" of the site
POT.py will publish every file in and under the specified local root directory to the server, relative to the specified remote root directory.
Dot files
As exception to the above, POT.py does not publish any file the name of which starts with a period. (UNIX uses the period to denote a hidden file.) If you don't want to specify these options on the command line, you can save them in an .ftpData file in the directory where you run POT.py. This file is run as Python code; here's an example:
# Publish my site to myisp.net/~markpasc/
user = 'markpasc'
host = 'ftp.myisp.net'
password = 'steak'
remoteDir = '/home/markpasc/public_html'
localDir = '.'
Since ".ftpData" starts with a period, the ftpData file is not published even if, as in the example, the local root directory is the current directory. POT.py employs two other special dot-files:
.manifest- POT.py's list of what it's published. It is currently not human readable (it's a "pickled" dictionary).
.include- When HTML files are parsed for Python code, POT.py first executes the contents of all the
.includefiles down the directory tree from the root to the parsed file.
POT.py discovers what needs publishing by looking in its manifest, saved in the .manifest file in the root directory. POT.py keeps track of all the files it publishes, by the "mtime," or last modified time, for normal files, and by the MD5 hash for HTML files. (Since Python code in those HTML files is executed, their uploadable results may change without their unparsed content changing.)
Here's a basic example:
<?inherit('.header.html')?>
<p>Hello, world! 2 + 2 = <?2+2?>!</p>
<p>The sum of 1 to 100 is <?
ret = 0
for i in range(1,101):
ret += i
print ret
?></p>
<?inherit('.footer.html')?>
POT.py parses text inside <? and ?>, as highlighted in the example, as Python code. The example shows the difference between expressions and embedded scripts: code that can be stuffed on one line and is, syntactically, a Python expression can be placed inline like <?2+2?>. Code that doesn't count as a Python expression, such as the long script with the for loop, should be written as shown and must use print (or otherwise output to stdout) to "return" a value.
The example also uses POT.py's inherit function. POT.py provides several variables for use in your templates:
dynamic1if the file is being uploaded, or0if it's merely being tested for change. Testdynamicto see if you should include content that always changes; for example, a random quote, or a real timestamp (though you should probably use the file's mtime anyway). Returning a static value when POT.py tests for change will prevent the file from uploading every time.filename- The base filename of the file being rendered. POT.py always evaluates pages from in their directories, so you can use
os.path.abspathoros.getcwdto get the path part. include(filename)- Evaluate the given file as a Python script. Included documents are executed in the same context as the current document, so any variables, functions, &c you've declared already are available in the included document. Note the filename is relative to the parsed file—you probably only want to use this for files in the same directory. (
includeis a very simple wrapper forstr(Document(open(filename, 'r'))).) inherit(filename)- Evaluate the given file as a Python script. Unlike
include,inheritwill look up the directory tree until it finds filename. Use this instead ofincludefor, say, templates that you want to store in your web site's root directory.
An example
At the top of this document (in its unpublished form on my computer) is:
<?
title.append('POT.py')
style += "span.potcode { background-color: #0c9; }\n"
print inherit('.header.html')
?>
.header.html might contain:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">...
<html><head>
<title><?
fulltitle = ''
for segment in title: # which is a list
fulltitle = segment + ": " + fulltitle
if fulltitle: print re.sub(r'<[^>]*>', '', fulltitle) ?>markpasc.org</title>
<style type="text/css"><? style ?></style>
</head><body>
<h1><? fulltitle[:-2] ?></h1>
See how .header.html prints the contents of the style variable inside the <style/> tag, and collapses the title list to make the nice <title/> and leading header on this page. (Also note that the inherit needs printed, since it's part of an embedded script, not a single expression.)
Whither .include? For one, by Python's rules, those variables style and title have to be initialized; .include files are good for that. Also, say a subdirectory has several subpages in it, like this site's code/ section. The sidebar can be set in a sidebar variable in code/.include and referenced in .header.html so all the pages under code/ have the section's sidebar. The sidebar's HTML is only given once—or even, as is the case with this site, generated from a Python dictionary containing the names of subdirectories, project names, and descriptions.
Other options
Lastly, POT.py has three other command line options:
-1- Upload once and stop running. Unless you give this option, POT.py will continue to run, rescanning the directory for changes.
--lie- Only update the manifest; don't actually upload anything. Quits after one scan, like
-1. Useful if you are, say, converting a site over from plain HTML and have a lot of images and other content files that are already on the remote site, or if POT.py's manifest gets out of sync or encounters errors uploading. -q- Run quietly; that is, don't print normal narrative output, only errors. Use this if you're running POT.py from cron, etc.
POT.py is a replacement for publish, an HTML preprocessor I threw together in Perl, so its design is heavily informed by the shortcomings and annoyances I discovered with it, as well as some of the "upstreaming" architecture of Radio UserLand. I hope you find it as useful (or at least as thought-provoking) as I do.
Version history
1.3, 22 August 2003: Made SpyDict class a dict, not a UserDict (NB: this broke .manifest file compatibility). Use standard logging module. Internal stylistic improvements.
1.2, 29 April 2003: Added inherit function, to source.
Leave a comment