POT.py

| | Comments (0)

Publishing-Oriented Templater by Mark Paschal <markpasc@markpasc.org>

POT.py 1.3 (zip archive)

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 .include files 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:

dynamic
1 if the file is being uploaded, or 0 if it's merely being tested for change. Test dynamic to 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.abspath or os.getcwd to 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. (include is a very simple wrapper for str(Document(open(filename, 'r'))).)
inherit(filename)
Evaluate the given file as a Python script. Unlike include, inherit will look up the directory tree until it finds filename. Use this instead of include for, 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

About this Entry

This page contains a single entry by markpasc published on February 22, 2006 10:02 PM.

AtomAppCallback was the previous entry in this blog.

TBPY is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Categories

Tag Cloud