shithub: werc

Download patch

ref: 864073948ed33ab4f2aadc2e1caf54beafe42304
parent: 2a1b90b3575a1918fce472d3f7a2161aff1b3452
author: uriel <uriel@engel.se.cat-v.org>
date: Sun Oct 26 00:29:50 EDT 2008

Big cleanup of bin/, move external programs to contrib/, move minor useful scripts to aux/, and most important of all move controller.rc to werc.rc

--- a/bin/TODO
+++ /dev/null
@@ -1,7 +1,0 @@
-02:50 < devn> <META name="description" content="This is the Google Summer of Code blog for Plan9 and Inferno projects.">
-02:50 < devn> <META name="keywords" content="google, summer of code, inferno, plan9, programming">
-
-Should add werc to certain wikipedia lists:
-* http://en.wikipedia.org/wiki/List_of_content_management_systems
-* http://en.wikipedia.org/wiki/Comparison_of_wiki_software
-
--- /dev/null
+++ b/bin/aux/bpst.rc
@@ -1,0 +1,55 @@
+#!/usr/bin/env rc
+
+path=( $PLAN9/bin  $path )
+base=.
+
+fn die {
+	echo error: $* >[1=2]
+	exit 1
+}
+
+if (~ $#* 0)
+    die 'Missing title'
+
+if(~ $#user 0)
+	user = `{whoami}
+
+file = (); title = ();
+bloguser = $user
+while(! ~ $#* 0) {
+	switch($1) {
+	case -u
+        base=/gsoc/www/people/$user/blog/
+		shift
+	case *
+		title = $"*
+	}
+    shift
+}
+
+date=`{/bin/date +%F}
+
+n = 1
+for(f in $base/$date-*) {
+	i = `{echo $f | sed -n 's|^.*/'$date'-([0-9]+)_.*|\1|p'}
+	if(! ~ $#i 0 && test $i -ge $n)
+		n = `{hoc -e $i'+1'}
+}
+
+if(~ $EDITOR '')
+	EDITOR = vi
+
+file = $home/.blogtmp.$pid
+rm $file >[2]/dev/null
+touch $file
+$EDITOR $file
+ispell $file
+rm $file.bak >[2]/dev/null
+
+
+title = `{ echo $"title | sed 's/ /_/g' }
+
+title = `{echo $"title | sed 's/[ 	]+/_/g; 1q'}
+
+mv $file $base/$"date^'-'^$"n^_$"title.md
+
--- a/bin/bpst.rc
+++ /dev/null
@@ -1,55 +1,0 @@
-#!/usr/bin/env rc
-
-path=( $PLAN9/bin  $path )
-base=.
-
-fn die {
-	echo error: $* >[1=2]
-	exit 1
-}
-
-if (~ $#* 0)
-    die 'Missing title'
-
-if(~ $#user 0)
-	user = `{whoami}
-
-file = (); title = ();
-bloguser = $user
-while(! ~ $#* 0) {
-	switch($1) {
-	case -u
-        base=/gsoc/www/people/$user/blog/
-		shift
-	case *
-		title = $"*
-	}
-    shift
-}
-
-date=`{/bin/date +%F}
-
-n = 1
-for(f in $base/$date-*) {
-	i = `{echo $f | sed -n 's|^.*/'$date'-([0-9]+)_.*|\1|p'}
-	if(! ~ $#i 0 && test $i -ge $n)
-		n = `{hoc -e $i'+1'}
-}
-
-if(~ $EDITOR '')
-	EDITOR = vi
-
-file = $home/.blogtmp.$pid
-rm $file >[2]/dev/null
-touch $file
-$EDITOR $file
-ispell $file
-rm $file.bak >[2]/dev/null
-
-
-title = `{ echo $"title | sed 's/ /_/g' }
-
-title = `{echo $"title | sed 's/[ 	]+/_/g; 1q'}
-
-mv $file $base/$"date^'-'^$"n^_$"title.md
-
--- /dev/null
+++ b/bin/contrib/hgweb.config
@@ -1,0 +1,12 @@
+[web]
+style = gitweb
+allow_archive = bz2
+
+#[paths]
+#w9 = /gsoc/hg/w9/
+
+[collections]
+#allow_archive = bz2 zip
+/gsoc/hg = /gsoc/hg/
+#/var/hg = /var/hg/
+
--- /dev/null
+++ b/bin/contrib/hgwebdir.cgi
@@ -1,0 +1,47 @@
+#!/usr/bin/env python
+#
+# An example CGI script to export multiple hgweb repos, edit as necessary
+
+# send python tracebacks to the browser if an error occurs:
+import cgitb
+cgitb.enable()
+
+# adjust python path if not a system-wide install:
+#import sys
+#sys.path.insert(0, "/path/to/python/lib")
+
+# If you'd like to serve pages with UTF-8 instead of your default
+# locale charset, you can do so by uncommenting the following lines.
+# Note that this will cause your .hgrc files to be interpreted in
+# UTF-8 and all your repo files to be displayed using UTF-8.
+#
+#import os
+#os.environ["HGENCODING"] = "UTF-8"
+
+from mercurial.hgweb.hgwebdir_mod import hgwebdir
+from mercurial.hgweb.request import wsgiapplication
+import mercurial.hgweb.wsgicgi as wsgicgi
+
+# The config file looks like this.  You can have paths to individual
+# repos, collections of repos in a directory tree, or both.
+#
+# [paths]
+# virtual/path = /real/path
+# virtual/path = /real/path
+#
+# [collections]
+# /prefix/to/strip/off = /root/of/tree/full/of/repos
+#
+# collections example: say directory tree /foo contains repos /foo/bar,
+# /foo/quux/baz.  Give this config section:
+#   [collections]
+#   /foo = /foo
+# Then repos will list as bar and quux/baz.
+#
+# Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
+# or use a dictionary with entries like 'virtual/path': '/real/path'
+
+def make_web_app():
+    return hgwebdir("hgweb.config")
+
+wsgicgi.launch(wsgiapplication(make_web_app))
--- /dev/null
+++ b/bin/contrib/markdown.pl
@@ -1,0 +1,1447 @@
+#!/usr/bin/env perl
+#
+# Markdown -- A text-to-HTML conversion tool for web writers
+#
+# Copyright (c) 2004 John Gruber
+# <http://daringfireball.net/projects/markdown/>
+#
+package Markdown;
+require 5.006_000;
+use strict;
+use warnings;
+
+use Digest::MD5 qw(md5_hex);
+use vars qw($VERSION);
+$VERSION = '1.0.1';
+# Tue 14 Dec 2004
+
+## Disabled; causes problems under Perl 5.6.1:
+# use utf8;
+# binmode( STDOUT, ":utf8" );  # c.f.: http://acis.openlib.org/dev/perl-unicode-struggle.html
+
+
+#
+# Global default settings:
+#
+my $g_empty_element_suffix = " />";     # Change to ">" for HTML output
+my $g_tab_width = 4;
+
+
+#
+# Globals:
+#
+
+# Regex to match balanced [brackets]. See Friedl's
+# "Mastering Regular Expressions", 2nd Ed., pp. 328-331.
+my $g_nested_brackets;
+$g_nested_brackets = qr{
+	(?> 								# Atomic matching
+	   [^\[\]]+							# Anything other than brackets
+	 | 
+	   \[
+		 (??{ $g_nested_brackets })		# Recursive set of nested brackets
+	   \]
+	)*
+}x;
+
+
+# Table of hash values for escaped characters:
+my %g_escape_table;
+foreach my $char (split //, '\\`*_{}[]()>#+-.!') {
+	$g_escape_table{$char} = md5_hex($char);
+}
+
+
+# Global hashes, used by various utility routines
+my %g_urls;
+my %g_titles;
+my %g_html_blocks;
+
+# Used to track when we're inside an ordered or unordered list
+# (see _ProcessListItems() for details):
+my $g_list_level = 0;
+
+
+#### Blosxom plug-in interface ##########################################
+
+# Set $g_blosxom_use_meta to 1 to use Blosxom's meta plug-in to determine
+# which posts Markdown should process, using a "meta-markup: markdown"
+# header. If it's set to 0 (the default), Markdown will process all
+# entries.
+my $g_blosxom_use_meta = 0;
+
+sub start { 1; }
+sub story {
+	my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
+
+	if ( (! $g_blosxom_use_meta) or
+	     (defined($meta::markup) and ($meta::markup =~ /^\s*markdown\s*$/i))
+	     ){
+			$$body_ref  = Markdown($$body_ref);
+     }
+     1;
+}
+
+
+#### Movable Type plug-in interface #####################################
+eval {require MT};  # Test to see if we're running in MT.
+unless ($@) {
+    require MT;
+    import  MT;
+    require MT::Template::Context;
+    import  MT::Template::Context;
+
+	eval {require MT::Plugin};  # Test to see if we're running >= MT 3.0.
+	unless ($@) {
+		require MT::Plugin;
+		import  MT::Plugin;
+		my $plugin = new MT::Plugin({
+			name => "Markdown",
+			description => "A plain-text-to-HTML formatting plugin. (Version: $VERSION)",
+			doc_link => 'http://daringfireball.net/projects/markdown/'
+		});
+		MT->add_plugin( $plugin );
+	}
+
+	MT::Template::Context->add_container_tag(MarkdownOptions => sub {
+		my $ctx	 = shift;
+		my $args = shift;
+		my $builder = $ctx->stash('builder');
+		my $tokens = $ctx->stash('tokens');
+
+		if (defined ($args->{'output'}) ) {
+			$ctx->stash('markdown_output', lc $args->{'output'});
+		}
+
+		defined (my $str = $builder->build($ctx, $tokens) )
+			or return $ctx->error($builder->errstr);
+		$str;		# return value
+	});
+
+	MT->add_text_filter('markdown' => {
+		label     => 'Markdown',
+		docs      => 'http://daringfireball.net/projects/markdown/',
+		on_format => sub {
+			my $text = shift;
+			my $ctx  = shift;
+			my $raw  = 0;
+		    if (defined $ctx) {
+		    	my $output = $ctx->stash('markdown_output'); 
+				if (defined $output  &&  $output =~ m/^html/i) {
+					$g_empty_element_suffix = ">";
+					$ctx->stash('markdown_output', '');
+				}
+				elsif (defined $output  &&  $output eq 'raw') {
+					$raw = 1;
+					$ctx->stash('markdown_output', '');
+				}
+				else {
+					$raw = 0;
+					$g_empty_element_suffix = " />";
+				}
+			}
+			$text = $raw ? $text : Markdown($text);
+			$text;
+		},
+	});
+
+	# If SmartyPants is loaded, add a combo Markdown/SmartyPants text filter:
+	my $smartypants;
+
+	{
+		no warnings "once";
+		$smartypants = $MT::Template::Context::Global_filters{'smarty_pants'};
+	}
+
+	if ($smartypants) {
+		MT->add_text_filter('markdown_with_smartypants' => {
+			label     => 'Markdown With SmartyPants',
+			docs      => 'http://daringfireball.net/projects/markdown/',
+			on_format => sub {
+				my $text = shift;
+				my $ctx  = shift;
+				if (defined $ctx) {
+					my $output = $ctx->stash('markdown_output'); 
+					if (defined $output  &&  $output eq 'html') {
+						$g_empty_element_suffix = ">";
+					}
+					else {
+						$g_empty_element_suffix = " />";
+					}
+				}
+				$text = Markdown($text);
+				$text = $smartypants->($text, '1');
+			},
+		});
+	}
+}
+else {
+#### BBEdit/command-line text filter interface ##########################
+# Needs to be hidden from MT (and Blosxom when running in static mode).
+
+    # We're only using $blosxom::version once; tell Perl not to warn us:
+	no warnings 'once';
+    unless ( defined($blosxom::version) ) {
+		use warnings;
+
+		#### Check for command-line switches: #################
+		my %cli_opts;
+		use Getopt::Long;
+		Getopt::Long::Configure('pass_through');
+		GetOptions(\%cli_opts,
+			'version',
+			'shortversion',
+			'html4tags',
+		);
+		if ($cli_opts{'version'}) {		# Version info
+			print "\nThis is Markdown, version $VERSION.\n";
+			print "Copyright 2004 John Gruber\n";
+			print "http://daringfireball.net/projects/markdown/\n\n";
+			exit 0;
+		}
+		if ($cli_opts{'shortversion'}) {		# Just the version number string.
+			print $VERSION;
+			exit 0;
+		}
+		if ($cli_opts{'html4tags'}) {			# Use HTML tag style instead of XHTML
+			$g_empty_element_suffix = ">";
+		}
+
+
+		#### Process incoming text: ###########################
+		my $text;
+		{
+			local $/;               # Slurp the whole file
+			$text = <>;
+		}
+        print Markdown($text);
+    }
+}
+
+
+
+sub Markdown {
+#
+# Main function. The order in which other subs are called here is
+# essential. Link and image substitutions need to happen before
+# _EscapeSpecialChars(), so that any *'s or _'s in the <a>
+# and <img> tags get encoded.
+#
+	my $text = shift;
+
+	# Clear the global hashes. If we don't clear these, you get conflicts
+	# from other articles when generating a page which contains more than
+	# one article (e.g. an index page that shows the N most recent
+	# articles):
+	%g_urls = ();
+	%g_titles = ();
+	%g_html_blocks = ();
+
+
+	# Standardize line endings:
+	$text =~ s{\r\n}{\n}g; 	# DOS to Unix
+	$text =~ s{\r}{\n}g; 	# Mac to Unix
+
+	# Make sure $text ends with a couple of newlines:
+	$text .= "\n\n";
+
+	# Convert all tabs to spaces.
+	$text = _Detab($text);
+
+	# Strip any lines consisting only of spaces and tabs.
+	# This makes subsequent regexen easier to write, because we can
+	# match consecutive blank lines with /\n+/ instead of something
+	# contorted like /[ \t]*\n+/ .
+	$text =~ s/^[ \t]+$//mg;
+
+	# Turn block-level HTML blocks into hash entries
+	$text = _HashHTMLBlocks($text);
+
+	# Strip link definitions, store in hashes.
+	$text = _StripLinkDefinitions($text);
+
+	$text = _RunBlockGamut($text);
+
+	$text = _UnescapeSpecialChars($text);
+
+	return $text . "\n";
+}
+
+
+sub _StripLinkDefinitions {
+#
+# Strips link definitions from text, stores the URLs and titles in
+# hash references.
+#
+	my $text = shift;
+	my $less_than_tab = $g_tab_width - 1;
+
+	# Link defs are in the form: ^[id]: url "optional title"
+	while ($text =~ s{
+						^[ ]{0,$less_than_tab}\[(.+)\]:	# id = $1
+						  [ \t]*
+						  \n?				# maybe *one* newline
+						  [ \t]*
+						<?(\S+?)>?			# url = $2
+						  [ \t]*
+						  \n?				# maybe one newline
+						  [ \t]*
+						(?:
+							(?<=\s)			# lookbehind for whitespace
+							["(]
+							(.+?)			# title = $3
+							[")]
+							[ \t]*
+						)?	# title is optional
+						(?:\n+|\Z)
+					}
+					{}mx) {
+		$g_urls{lc $1} = _EncodeAmpsAndAngles( $2 );	# Link IDs are case-insensitive
+		if ($3) {
+			$g_titles{lc $1} = $3;
+			$g_titles{lc $1} =~ s/"/&quot;/g;
+		}
+	}
+
+	return $text;
+}
+
+
+sub _HashHTMLBlocks {
+	my $text = shift;
+	my $less_than_tab = $g_tab_width - 1;
+
+	# Hashify HTML blocks:
+	# We only want to do this for block-level HTML tags, such as headers,
+	# lists, and tables. That's because we still want to wrap <p>s around
+	# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+	# phrase emphasis, and spans. The list of tags we're looking for is
+	# hard-coded:
+	my $block_tags_a = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del/;
+	my $block_tags_b = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math/;
+
+	# First, look for nested blocks, e.g.:
+	# 	<div>
+	# 		<div>
+	# 		tags for inner block must be indented.
+	# 		</div>
+	# 	</div>
+	#
+	# The outermost tags must start at the left margin for this to match, and
+	# the inner nested divs must be indented.
+	# We need to do this before the next, more liberal match, because the next
+	# match will start at the first `<div>` and stop at the first `</div>`.
+	$text =~ s{
+				(						# save in $1
+					^					# start of line  (with /m)
+					<($block_tags_a)	# start tag = $2
+					\b					# word break
+					(.*\n)*?			# any number of lines, minimally matching
+					</\2>				# the matching end tag
+					[ \t]*				# trailing spaces/tabs
+					(?=\n+|\Z)	# followed by a newline or end of document
+				)
+			}{
+				my $key = md5_hex($1);
+				$g_html_blocks{$key} = $1;
+				"\n\n" . $key . "\n\n";
+			}egmx;
+
+
+	#
+	# Now match more liberally, simply from `\n<tag>` to `</tag>\n`
+	#
+	$text =~ s{
+				(						# save in $1
+					^					# start of line  (with /m)
+					<($block_tags_b)	# start tag = $2
+					\b					# word break
+					(.*\n)*?			# any number of lines, minimally matching
+					.*</\2>				# the matching end tag
+					[ \t]*				# trailing spaces/tabs
+					(?=\n+|\Z)	# followed by a newline or end of document
+				)
+			}{
+				my $key = md5_hex($1);
+				$g_html_blocks{$key} = $1;
+				"\n\n" . $key . "\n\n";
+			}egmx;
+	# Special case just for <hr />. It was easier to make a special case than
+	# to make the other regex more complicated.	
+	$text =~ s{
+				(?:
+					(?<=\n\n)		# Starting after a blank line
+					|				# or
+					\A\n?			# the beginning of the doc
+				)
+				(						# save in $1
+					[ ]{0,$less_than_tab}
+					<(hr)				# start tag = $2
+					\b					# word break
+					([^<>])*?			# 
+					/?>					# the matching end tag
+					[ \t]*
+					(?=\n{2,}|\Z)		# followed by a blank line or end of document
+				)
+			}{
+				my $key = md5_hex($1);
+				$g_html_blocks{$key} = $1;
+				"\n\n" . $key . "\n\n";
+			}egx;
+
+	# Special case for standalone HTML comments:
+	$text =~ s{
+				(?:
+					(?<=\n\n)		# Starting after a blank line
+					|				# or
+					\A\n?			# the beginning of the doc
+				)
+				(						# save in $1
+					[ ]{0,$less_than_tab}
+					(?s:
+						<!
+						(--.*?--\s*)+
+						>
+					)
+					[ \t]*
+					(?=\n{2,}|\Z)		# followed by a blank line or end of document
+				)
+			}{
+				my $key = md5_hex($1);
+				$g_html_blocks{$key} = $1;
+				"\n\n" . $key . "\n\n";
+			}egx;
+
+
+	return $text;
+}
+
+
+sub _RunBlockGamut {
+#
+# These are all the transformations that form block-level
+# tags like paragraphs, headers, and list items.
+#
+	my $text = shift;
+
+	$text = _DoHeaders($text);
+
+	# Do Horizontal Rules:
+	$text =~ s{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
+	$text =~ s{^[ ]{0,2}([ ]? -[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
+	$text =~ s{^[ ]{0,2}([ ]? _[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
+
+	$text = _DoLists($text);
+
+	$text = _DoCodeBlocks($text);
+
+	$text = _DoBlockQuotes($text);
+
+	# We already ran _HashHTMLBlocks() before, in Markdown(), but that
+	# was to escape raw HTML in the original Markdown source. This time,
+	# we're escaping the markup we've just created, so that we don't wrap
+	# <p> tags around block-level tags.
+	$text = _HashHTMLBlocks($text);
+
+	$text = _FormParagraphs($text);
+
+	return $text;
+}
+
+
+sub _RunSpanGamut {
+#
+# These are all the transformations that occur *within* block-level
+# tags like paragraphs, headers, and list items.
+#
+	my $text = shift;
+
+	$text = _DoCodeSpans($text);
+
+	$text = _EscapeSpecialChars($text);
+
+	# Process anchor and image tags. Images must come first,
+	# because ![foo][f] looks like an anchor.
+	$text = _DoImages($text);
+	$text = _DoAnchors($text);
+
+	# Make links out of things like `<http://example.com/>`
+	# Must come after _DoAnchors(), because you can use < and >
+	# delimiters in inline links like [this](<url>).
+	$text = _DoAutoLinks($text);
+
+	$text = _EncodeAmpsAndAngles($text);
+
+	$text = _DoItalicsAndBold($text);
+
+	# Do hard breaks:
+	$text =~ s/ {2,}\n/ <br$g_empty_element_suffix\n/g;
+
+	return $text;
+}
+
+
+sub _EscapeSpecialChars {
+	my $text = shift;
+	my $tokens ||= _TokenizeHTML($text);
+
+	$text = '';   # rebuild $text from the tokens
+# 	my $in_pre = 0;	 # Keep track of when we're inside <pre> or <code> tags.
+# 	my $tags_to_skip = qr!<(/?)(?:pre|code|kbd|script|math)[\s>]!;
+
+	foreach my $cur_token (@$tokens) {
+		if ($cur_token->[0] eq "tag") {
+			# Within tags, encode * and _ so they don't conflict
+			# with their use in Markdown for italics and strong.
+			# We're replacing each such character with its
+			# corresponding MD5 checksum value; this is likely
+			# overkill, but it should prevent us from colliding
+			# with the escape values by accident.
+			$cur_token->[1] =~  s! \* !$g_escape_table{'*'}!gx;
+			$cur_token->[1] =~  s! _  !$g_escape_table{'_'}!gx;
+			$text .= $cur_token->[1];
+		} else {
+			my $t = $cur_token->[1];
+			$t = _EncodeBackslashEscapes($t);
+			$text .= $t;
+		}
+	}
+	return $text;
+}
+
+
+sub _DoAnchors {
+#
+# Turn Markdown link shortcuts into XHTML <a> tags.
+#
+	my $text = shift;
+
+	#
+	# First, handle reference-style links: [link text] [id]
+	#
+	$text =~ s{
+		(					# wrap whole match in $1
+		  \[
+		    ($g_nested_brackets)	# link text = $2
+		  \]
+
+		  [ ]?				# one optional space
+		  (?:\n[ ]*)?		# one optional newline followed by spaces
+
+		  \[
+		    (.*?)		# id = $3
+		  \]
+		)
+	}{
+		my $result;
+		my $whole_match = $1;
+		my $link_text   = $2;
+		my $link_id     = lc $3;
+
+		if ($link_id eq "") {
+			$link_id = lc $link_text;     # for shortcut links like [this][].
+		}
+
+		if (defined $g_urls{$link_id}) {
+			my $url = $g_urls{$link_id};
+			$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
+			$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
+			$result = "<a href=\"$url\"";
+			if ( defined $g_titles{$link_id} ) {
+				my $title = $g_titles{$link_id};
+				$title =~ s! \* !$g_escape_table{'*'}!gx;
+				$title =~ s!  _ !$g_escape_table{'_'}!gx;
+				$result .=  " title=\"$title\"";
+			}
+			$result .= ">$link_text</a>";
+		}
+		else {
+			$result = $whole_match;
+		}
+		$result;
+	}xsge;
+
+	#
+	# Next, inline-style links: [link text](url "optional title")
+	#
+	$text =~ s{
+		(				# wrap whole match in $1
+		  \[
+		    ($g_nested_brackets)	# link text = $2
+		  \]
+		  \(			# literal paren
+		  	[ \t]*
+			<?(.*?)>?	# href = $3
+		  	[ \t]*
+			(			# $4
+			  (['"])	# quote char = $5
+			  (.*?)		# Title = $6
+			  \5		# matching quote
+			)?			# title is optional
+		  \)
+		)
+	}{
+		my $result;
+		my $whole_match = $1;
+		my $link_text   = $2;
+		my $url	  		= $3;
+		my $title		= $6;
+
+		$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
+		$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
+		$result = "<a href=\"$url\"";
+
+		if (defined $title) {
+			$title =~ s/"/&quot;/g;
+			$title =~ s! \* !$g_escape_table{'*'}!gx;
+			$title =~ s!  _ !$g_escape_table{'_'}!gx;
+			$result .=  " title=\"$title\"";
+		}
+
+		$result .= ">$link_text</a>";
+
+		$result;
+	}xsge;
+
+	return $text;
+}
+
+
+sub _DoImages {
+#
+# Turn Markdown image shortcuts into <img> tags.
+#
+	my $text = shift;
+
+	#
+	# First, handle reference-style labeled images: ![alt text][id]
+	#
+	$text =~ s{
+		(				# wrap whole match in $1
+		  !\[
+		    (.*?)		# alt text = $2
+		  \]
+
+		  [ ]?				# one optional space
+		  (?:\n[ ]*)?		# one optional newline followed by spaces
+
+		  \[
+		    (.*?)		# id = $3
+		  \]
+
+		)
+	}{
+		my $result;
+		my $whole_match = $1;
+		my $alt_text    = $2;
+		my $link_id     = lc $3;
+
+		if ($link_id eq "") {
+			$link_id = lc $alt_text;     # for shortcut links like ![this][].
+		}
+
+		$alt_text =~ s/"/&quot;/g;
+		if (defined $g_urls{$link_id}) {
+			my $url = $g_urls{$link_id};
+			$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
+			$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
+			$result = "<img src=\"$url\" alt=\"$alt_text\"";
+			if (defined $g_titles{$link_id}) {
+				my $title = $g_titles{$link_id};
+				$title =~ s! \* !$g_escape_table{'*'}!gx;
+				$title =~ s!  _ !$g_escape_table{'_'}!gx;
+				$result .=  " title=\"$title\"";
+			}
+			$result .= $g_empty_element_suffix;
+		}
+		else {
+			# If there's no such link ID, leave intact:
+			$result = $whole_match;
+		}
+
+		$result;
+	}xsge;
+
+	#
+	# Next, handle inline images:  ![alt text](url "optional title")
+	# Don't forget: encode * and _
+
+	$text =~ s{
+		(				# wrap whole match in $1
+		  !\[
+		    (.*?)		# alt text = $2
+		  \]
+		  \(			# literal paren
+		  	[ \t]*
+			<?(\S+?)>?	# src url = $3
+		  	[ \t]*
+			(			# $4
+			  (['"])	# quote char = $5
+			  (.*?)		# title = $6
+			  \5		# matching quote
+			  [ \t]*
+			)?			# title is optional
+		  \)
+		)
+	}{
+		my $result;
+		my $whole_match = $1;
+		my $alt_text    = $2;
+		my $url	  		= $3;
+		my $title		= '';
+		if (defined($6)) {
+			$title		= $6;
+		}
+
+		$alt_text =~ s/"/&quot;/g;
+		$title    =~ s/"/&quot;/g;
+		$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
+		$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
+		$result = "<img src=\"$url\" alt=\"$alt_text\"";
+		if (defined $title) {
+			$title =~ s! \* !$g_escape_table{'*'}!gx;
+			$title =~ s!  _ !$g_escape_table{'_'}!gx;
+			$result .=  " title=\"$title\"";
+		}
+		$result .= $g_empty_element_suffix;
+
+		$result;
+	}xsge;
+
+	return $text;
+}
+
+
+sub _DoHeaders {
+	my $text = shift;
+
+	# Setext-style headers:
+	#	  Header 1
+	#	  ========
+	#  
+	#	  Header 2
+	#	  --------
+	#
+	$text =~ s{ ^(.+)[ \t]*\n=+[ \t]*\n+ }{
+		"<h1>"  .  _RunSpanGamut($1)  .  "</h1>\n\n";
+	}egmx;
+
+	$text =~ s{ ^(.+)[ \t]*\n-+[ \t]*\n+ }{
+		"<h2>"  .  _RunSpanGamut($1)  .  "</h2>\n\n";
+	}egmx;
+
+
+	# atx-style headers:
+	#	# Header 1
+	#	## Header 2
+	#	## Header 2 with closing hashes ##
+	#	...
+	#	###### Header 6
+	#
+	$text =~ s{
+			^(\#{1,6})	# $1 = string of #'s
+			[ \t]*
+			(.+?)		# $2 = Header text
+			[ \t]*
+			\#*			# optional closing #'s (not counted)
+			\n+
+		}{
+			my $h_level = length($1);
+			"<h$h_level>"  .  _RunSpanGamut($2)  .  "</h$h_level>\n\n";
+		}egmx;
+
+	return $text;
+}
+
+
+sub _DoLists {
+#
+# Form HTML ordered (numbered) and unordered (bulleted) lists.
+#
+	my $text = shift;
+	my $less_than_tab = $g_tab_width - 1;
+
+	# Re-usable patterns to match list item bullets and number markers:
+	my $marker_ul  = qr/[*+-]/;
+	my $marker_ol  = qr/\d+[.]/;
+	my $marker_any = qr/(?:$marker_ul|$marker_ol)/;
+
+	# Re-usable pattern to match any entirel ul or ol list:
+	my $whole_list = qr{
+		(								# $1 = whole list
+		  (								# $2
+			[ ]{0,$less_than_tab}
+			(${marker_any})				# $3 = first list item marker
+			[ \t]+
+		  )
+		  (?s:.+?)
+		  (								# $4
+			  \z
+			|
+			  \n{2,}
+			  (?=\S)
+			  (?!						# Negative lookahead for another list item marker
+				[ \t]*
+				${marker_any}[ \t]+
+			  )
+		  )
+		)
+	}mx;
+
+	# We use a different prefix before nested lists than top-level lists.
+	# See extended comment in _ProcessListItems().
+	#
+	# Note: There's a bit of duplication here. My original implementation
+	# created a scalar regex pattern as the conditional result of the test on
+	# $g_list_level, and then only ran the $text =~ s{...}{...}egmx
+	# substitution once, using the scalar as the pattern. This worked,
+	# everywhere except when running under MT on my hosting account at Pair
+	# Networks. There, this caused all rebuilds to be killed by the reaper (or
+	# perhaps they crashed, but that seems incredibly unlikely given that the
+	# same script on the same server ran fine *except* under MT. I've spent
+	# more time trying to figure out why this is happening than I'd like to
+	# admit. My only guess, backed up by the fact that this workaround works,
+	# is that Perl optimizes the substition when it can figure out that the
+	# pattern will never change, and when this optimization isn't on, we run
+	# afoul of the reaper. Thus, the slightly redundant code to that uses two
+	# static s/// patterns rather than one conditional pattern.
+
+	if ($g_list_level) {
+		$text =~ s{
+				^
+				$whole_list
+			}{
+				my $list = $1;
+				my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol";
+				# Turn double returns into triple returns, so that we can make a
+				# paragraph for the last item in a list, if necessary:
+				$list =~ s/\n{2,}/\n\n\n/g;
+				my $result = _ProcessListItems($list, $marker_any);
+				$result = "<$list_type>\n" . $result . "</$list_type>\n";
+				$result;
+			}egmx;
+	}
+	else {
+		$text =~ s{
+				(?:(?<=\n\n)|\A\n?)
+				$whole_list
+			}{
+				my $list = $1;
+				my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol";
+				# Turn double returns into triple returns, so that we can make a
+				# paragraph for the last item in a list, if necessary:
+				$list =~ s/\n{2,}/\n\n\n/g;
+				my $result = _ProcessListItems($list, $marker_any);
+				$result = "<$list_type>\n" . $result . "</$list_type>\n";
+				$result;
+			}egmx;
+	}
+
+
+	return $text;
+}
+
+
+sub _ProcessListItems {
+#
+#	Process the contents of a single ordered or unordered list, splitting it
+#	into individual list items.
+#
+
+	my $list_str = shift;
+	my $marker_any = shift;
+
+
+	# The $g_list_level global keeps track of when we're inside a list.
+	# Each time we enter a list, we increment it; when we leave a list,
+	# we decrement. If it's zero, we're not in a list anymore.
+	#
+	# We do this because when we're not inside a list, we want to treat
+	# something like this:
+	#
+	#		I recommend upgrading to version
+	#		8. Oops, now this line is treated
+	#		as a sub-list.
+	#
+	# As a single paragraph, despite the fact that the second line starts
+	# with a digit-period-space sequence.
+	#
+	# Whereas when we're inside a list (or sub-list), that line will be
+	# treated as the start of a sub-list. What a kludge, huh? This is
+	# an aspect of Markdown's syntax that's hard to parse perfectly
+	# without resorting to mind-reading. Perhaps the solution is to
+	# change the syntax rules such that sub-lists must start with a
+	# starting cardinal number; e.g. "1." or "a.".
+
+	$g_list_level++;
+
+	# trim trailing blank lines:
+	$list_str =~ s/\n{2,}\z/\n/;
+
+
+	$list_str =~ s{
+		(\n)?							# leading line = $1
+		(^[ \t]*)						# leading whitespace = $2
+		($marker_any) [ \t]+			# list marker = $3
+		((?s:.+?)						# list item text   = $4
+		(\n{1,2}))
+		(?= \n* (\z | \2 ($marker_any) [ \t]+))
+	}{
+		my $item = $4;
+		my $leading_line = $1;
+		my $leading_space = $2;
+
+		if ($leading_line or ($item =~ m/\n{2,}/)) {
+			$item = _RunBlockGamut(_Outdent($item));
+		}
+		else {
+			# Recursion for sub-lists:
+			$item = _DoLists(_Outdent($item));
+			chomp $item;
+			$item = _RunSpanGamut($item);
+		}
+
+		"<li>" . $item . "</li>\n";
+	}egmx;
+
+	$g_list_level--;
+	return $list_str;
+}
+
+
+
+sub _DoCodeBlocks {
+#
+#	Process Markdown `<pre><code>` blocks.
+#	
+
+	my $text = shift;
+
+	$text =~ s{
+			(?:\n\n|\A)
+			(	            # $1 = the code block -- one or more lines, starting with a space/tab
+			  (?:
+			    (?:[ ]{$g_tab_width} | \t)  # Lines must start with a tab or a tab-width of spaces
+			    .*\n+
+			  )+
+			)
+			((?=^[ ]{0,$g_tab_width}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
+		}{
+			my $codeblock = $1;
+			my $result; # return value
+
+			$codeblock = _EncodeCode(_Outdent($codeblock));
+			$codeblock = _Detab($codeblock);
+			$codeblock =~ s/\A\n+//; # trim leading newlines
+			$codeblock =~ s/\s+\z//; # trim trailing whitespace
+
+			$result = "\n\n<pre><code>" . $codeblock . "\n</code></pre>\n\n";
+
+			$result;
+		}egmx;
+
+	return $text;
+}
+
+
+sub _DoCodeSpans {
+#
+# 	*	Backtick quotes are used for <code></code> spans.
+# 
+# 	*	You can use multiple backticks as the delimiters if you want to
+# 		include literal backticks in the code span. So, this input:
+#     
+#         Just type ``foo `bar` baz`` at the prompt.
+#     
+#     	Will translate to:
+#     
+#         <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
+#     
+#		There's no arbitrary limit to the number of backticks you
+#		can use as delimters. If you need three consecutive backticks
+#		in your code, use four for delimiters, etc.
+#
+#	*	You can use spaces to get literal backticks at the edges:
+#     
+#         ... type `` `bar` `` ...
+#     
+#     	Turns to:
+#     
+#         ... type <code>`bar`</code> ...
+#
+
+	my $text = shift;
+
+	$text =~ s@
+			(`+)		# $1 = Opening run of `
+			(.+?)		# $2 = The code block
+			(?<!`)
+			\1			# Matching closer
+			(?!`)
+		@
+ 			my $c = "$2";
+ 			$c =~ s/^[ \t]*//g; # leading whitespace
+ 			$c =~ s/[ \t]*$//g; # trailing whitespace
+ 			$c = _EncodeCode($c);
+			"<code>$c</code>";
+		@egsx;
+
+	return $text;
+}
+
+
+sub _EncodeCode {
+#
+# Encode/escape certain characters inside Markdown code runs.
+# The point is that in code, these characters are literals,
+# and lose their special Markdown meanings.
+#
+    local $_ = shift;
+
+	# Encode all ampersands; HTML entities are not
+	# entities within a Markdown code span.
+	s/&/&amp;/g;
+
+	# Encode $'s, but only if we're running under Blosxom.
+	# (Blosxom interpolates Perl variables in article bodies.)
+	{
+		no warnings 'once';
+    	if (defined($blosxom::version)) {
+    		s/\$/&#036;/g;	
+    	}
+    }
+
+
+	# Do the angle bracket song and dance:
+	s! <  !&lt;!gx;
+	s! >  !&gt;!gx;
+
+	# Now, escape characters that are magic in Markdown:
+	s! \* !$g_escape_table{'*'}!gx;
+	s! _  !$g_escape_table{'_'}!gx;
+	s! {  !$g_escape_table{'{'}!gx;
+	s! }  !$g_escape_table{'}'}!gx;
+	s! \[ !$g_escape_table{'['}!gx;
+	s! \] !$g_escape_table{']'}!gx;
+	s! \\ !$g_escape_table{'\\'}!gx;
+
+	return $_;
+}
+
+
+sub _DoItalicsAndBold {
+	my $text = shift;
+
+	# <strong> must go first:
+	$text =~ s{ (\*\*|__) (?=\S) (.+?[*_]*) (?<=\S) \1 }
+		{<strong>$2</strong>}gsx;
+
+	$text =~ s{ (\*|_) (?=\S) (.+?) (?<=\S) \1 }
+		{<em>$2</em>}gsx;
+
+	return $text;
+}
+
+
+sub _DoBlockQuotes {
+	my $text = shift;
+
+	$text =~ s{
+		  (								# Wrap whole match in $1
+			(
+			  ^[ \t]*>[ \t]?			# '>' at the start of a line
+			    .+\n					# rest of the first line
+			  (.+\n)*					# subsequent consecutive lines
+			  \n*						# blanks
+			)+
+		  )
+		}{
+			my $bq = $1;
+			$bq =~ s/^[ \t]*>[ \t]?//gm;	# trim one level of quoting
+			$bq =~ s/^[ \t]+$//mg;			# trim whitespace-only lines
+			$bq = _RunBlockGamut($bq);		# recurse
+
+			$bq =~ s/^/  /g;
+			# These leading spaces screw with <pre> content, so we need to fix that:
+			$bq =~ s{
+					(\s*<pre>.+?</pre>)
+				}{
+					my $pre = $1;
+					$pre =~ s/^  //mg;
+					$pre;
+				}egsx;
+
+			"<blockquote>\n$bq\n</blockquote>\n\n";
+		}egmx;
+
+
+	return $text;
+}
+
+
+sub _FormParagraphs {
+#
+#	Params:
+#		$text - string to process with html <p> tags
+#
+	my $text = shift;
+
+	# Strip leading and trailing lines:
+	$text =~ s/\A\n+//;
+	$text =~ s/\n+\z//;
+
+	my @grafs = split(/\n{2,}/, $text);
+
+	#
+	# Wrap <p> tags.
+	#
+	foreach (@grafs) {
+		unless (defined( $g_html_blocks{$_} )) {
+			$_ = _RunSpanGamut($_);
+			s/^([ \t]*)/<p>/;
+			$_ .= "</p>";
+		}
+	}
+
+	#
+	# Unhashify HTML blocks
+	#
+	foreach (@grafs) {
+		if (defined( $g_html_blocks{$_} )) {
+			$_ = $g_html_blocks{$_};
+		}
+	}
+
+	return join "\n\n", @grafs;
+}
+
+
+sub _EncodeAmpsAndAngles {
+# Smart processing for ampersands and angle brackets that need to be encoded.
+
+	my $text = shift;
+
+	# Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
+	#   http://bumppo.net/projects/amputator/
+ 	$text =~ s/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/&amp;/g;
+
+	# Encode naked <'s
+ 	$text =~ s{<(?![a-z/?\$!])}{&lt;}gi;
+
+	return $text;
+}
+
+
+sub _EncodeBackslashEscapes {
+#
+#   Parameter:  String.
+#   Returns:    The string, with after processing the following backslash
+#               escape sequences.
+#
+    local $_ = shift;
+
+    s! \\\\  !$g_escape_table{'\\'}!gx;		# Must process escaped backslashes first.
+    s! \\`   !$g_escape_table{'`'}!gx;
+    s! \\\*  !$g_escape_table{'*'}!gx;
+    s! \\_   !$g_escape_table{'_'}!gx;
+    s! \\\{  !$g_escape_table{'{'}!gx;
+    s! \\\}  !$g_escape_table{'}'}!gx;
+    s! \\\[  !$g_escape_table{'['}!gx;
+    s! \\\]  !$g_escape_table{']'}!gx;
+    s! \\\(  !$g_escape_table{'('}!gx;
+    s! \\\)  !$g_escape_table{')'}!gx;
+    s! \\>   !$g_escape_table{'>'}!gx;
+    s! \\\#  !$g_escape_table{'#'}!gx;
+    s! \\\+  !$g_escape_table{'+'}!gx;
+    s! \\\-  !$g_escape_table{'-'}!gx;
+    s! \\\.  !$g_escape_table{'.'}!gx;
+    s{ \\!  }{$g_escape_table{'!'}}gx;
+
+    return $_;
+}
+
+
+sub _DoAutoLinks {
+	my $text = shift;
+
+	$text =~ s{<((https?|ftp):[^'">\s]+)>}{<a href="$1">$1</a>}gi;
+
+	# Email addresses: <address@domain.foo>
+	$text =~ s{
+		<
+        (?:mailto:)?
+		(
+			[-.\w]+
+			\@
+			[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
+		)
+		>
+	}{
+		_EncodeEmailAddress( _UnescapeSpecialChars($1) );
+	}egix;
+
+	return $text;
+}
+
+
+sub _EncodeEmailAddress {
+#
+#	Input: an email address, e.g. "foo@example.com"
+#
+#	Output: the email address as a mailto link, with each character
+#		of the address encoded as either a decimal or hex entity, in
+#		the hopes of foiling most address harvesting spam bots. E.g.:
+#
+#	  <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
+#       x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
+#       &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
+#
+#	Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
+#	mailing list: <http://tinyurl.com/yu7ue>
+#
+
+	my $addr = shift;
+
+	srand;
+	my @encode = (
+		sub { '&#' .                 ord(shift)   . ';' },
+		sub { '&#x' . sprintf( "%X", ord(shift) ) . ';' },
+		sub {                            shift          },
+	);
+
+	$addr = "mailto:" . $addr;
+
+	$addr =~ s{(.)}{
+		my $char = $1;
+		if ( $char eq '@' ) {
+			# this *must* be encoded. I insist.
+			$char = $encode[int rand 1]->($char);
+		} elsif ( $char ne ':' ) {
+			# leave ':' alone (to spot mailto: later)
+			my $r = rand;
+			# roughly 10% raw, 45% hex, 45% dec
+			$char = (
+				$r > .9   ?  $encode[2]->($char)  :
+				$r < .45  ?  $encode[1]->($char)  :
+							 $encode[0]->($char)
+			);
+		}
+		$char;
+	}gex;
+
+	$addr = qq{<a href="$addr">$addr</a>};
+	$addr =~ s{">.+?:}{">}; # strip the mailto: from the visible part
+
+	return $addr;
+}
+
+
+sub _UnescapeSpecialChars {
+#
+# Swap back in all the special characters we've hidden.
+#
+	my $text = shift;
+
+	while( my($char, $hash) = each(%g_escape_table) ) {
+		$text =~ s/$hash/$char/g;
+	}
+    return $text;
+}
+
+
+sub _TokenizeHTML {
+#
+#   Parameter:  String containing HTML markup.
+#   Returns:    Reference to an array of the tokens comprising the input
+#               string. Each token is either a tag (possibly with nested,
+#               tags contained therein, such as <a href="<MTFoo>">, or a
+#               run of text between tags. Each element of the array is a
+#               two-element array; the first is either 'tag' or 'text';
+#               the second is the actual value.
+#
+#
+#   Derived from the _tokenize() subroutine from Brad Choate's MTRegex plugin.
+#       <http://www.bradchoate.com/past/mtregex.php>
+#
+
+    my $str = shift;
+    my $pos = 0;
+    my $len = length $str;
+    my @tokens;
+
+    my $depth = 6;
+    my $nested_tags = join('|', ('(?:<[a-z/!$](?:[^<>]') x $depth) . (')*>)' x  $depth);
+    my $match = qr/(?s: <! ( -- .*? -- \s* )+ > ) |  # comment
+                   (?s: <\? .*? \?> ) |              # processing instruction
+                   $nested_tags/ix;                   # nested tags
+
+    while ($str =~ m/($match)/g) {
+        my $whole_tag = $1;
+        my $sec_start = pos $str;
+        my $tag_start = $sec_start - length $whole_tag;
+        if ($pos < $tag_start) {
+            push @tokens, ['text', substr($str, $pos, $tag_start - $pos)];
+        }
+        push @tokens, ['tag', $whole_tag];
+        $pos = pos $str;
+    }
+    push @tokens, ['text', substr($str, $pos, $len - $pos)] if $pos < $len;
+    \@tokens;
+}
+
+
+sub _Outdent {
+#
+# Remove one level of line-leading tabs or spaces
+#
+	my $text = shift;
+
+	$text =~ s/^(\t|[ ]{1,$g_tab_width})//gm;
+	return $text;
+}
+
+
+sub _Detab {
+#
+# Cribbed from a post by Bart Lateur:
+# <http://www.nntp.perl.org/group/perl.macperl.anyperl/154>
+#
+	my $text = shift;
+
+	$text =~ s{(.*?)\t}{$1.(' ' x ($g_tab_width - length($1) % $g_tab_width))}ge;
+	return $text;
+}
+
+
+1;
+
+__END__
+
+
+=pod
+
+=head1 NAME
+
+B<Markdown>
+
+
+=head1 SYNOPSIS
+
+B<Markdown.pl> [ B<--html4tags> ] [ B<--version> ] [ B<-shortversion> ]
+    [ I<file> ... ]
+
+
+=head1 DESCRIPTION
+
+Markdown is a text-to-HTML filter; it translates an easy-to-read /
+easy-to-write structured text format into HTML. Markdown's text format
+is most similar to that of plain text email, and supports features such
+as headers, *emphasis*, code blocks, blockquotes, and links.
+
+Markdown's syntax is designed not as a generic markup language, but
+specifically to serve as a front-end to (X)HTML. You can  use span-level
+HTML tags anywhere in a Markdown document, and you can use block level
+HTML tags (like <div> and <table> as well).
+
+For more information about Markdown's syntax, see:
+
+    http://daringfireball.net/projects/markdown/
+
+
+=head1 OPTIONS
+
+Use "--" to end switch parsing. For example, to open a file named "-z", use:
+
+	Markdown.pl -- -z
+
+=over 4
+
+
+=item B<--html4tags>
+
+Use HTML 4 style for empty element tags, e.g.:
+
+    <br>
+
+instead of Markdown's default XHTML style tags, e.g.:
+
+    <br />
+
+
+=item B<-v>, B<--version>
+
+Display Markdown's version number and copyright information.
+
+
+=item B<-s>, B<--shortversion>
+
+Display the short-form version number.
+
+
+=back
+
+
+
+=head1 BUGS
+
+To file bug reports or feature requests (other than topics listed in the
+Caveats section above) please send email to:
+
+    support@daringfireball.net
+
+Please include with your report: (1) the example input; (2) the output
+you expected; (3) the output Markdown actually produced.
+
+
+=head1 VERSION HISTORY
+
+See the readme file for detailed release notes for this version.
+
+1.0.1 - 14 Dec 2004
+
+1.0 - 28 Aug 2004
+
+
+=head1 AUTHOR
+
+    John Gruber
+    http://daringfireball.net
+
+    PHP port and other contributions by Michel Fortin
+    http://michelf.com
+
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (c) 2003-2004 John Gruber   
+<http://daringfireball.net/>   
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+* Neither the name "Markdown" nor the names of its contributors may
+  be used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+This software is provided by the copyright holders and contributors "as
+is" and any express or implied warranties, including, but not limited
+to, the implied warranties of merchantability and fitness for a
+particular purpose are disclaimed. In no event shall the copyright owner
+or contributors be liable for any direct, indirect, incidental, special,
+exemplary, or consequential damages (including, but not limited to,
+procurement of substitute goods or services; loss of use, data, or
+profits; or business interruption) however caused and on any theory of
+liability, whether in contract, strict liability, or tort (including
+negligence or otherwise) arising in any way out of the use of this
+software, even if advised of the possibility of such damage.
+
+=cut
--- /dev/null
+++ b/bin/contrib/urldecode.awk
@@ -1,0 +1,39 @@
+#!/usr/bin/awk -f
+BEGIN {
+	hextab ["0"] = 0; hextab ["8"] = 8;
+	hextab ["1"] = 1; hextab ["9"] = 9;
+	hextab ["2"] = 2; hextab ["A"] = hextab ["a"] = 10
+	hextab ["3"] = 3; hextab ["B"] = hextab ["b"] = 11;
+	hextab ["4"] = 4; hextab ["C"] = hextab ["c"] = 12;
+	hextab ["5"] = 5; hextab ["D"] = hextab ["d"] = 13;
+	hextab ["6"] = 6; hextab ["E"] = hextab ["e"] = 14;
+	hextab ["7"] = 7; hextab ["F"] = hextab ["f"] = 15;
+}
+{
+	decoded = ""
+	i   = 1
+	len = length ($0)
+	while ( i <= len ) {
+		c = substr ($0, i, 1)
+		if ( c == "%" ) {
+			if ( i+2 <= len ) {
+				c1 = substr ($0, i+1, 1)
+				c2 = substr ($0, i+2, 1)
+				if ( hextab [c1] == "" || hextab [c2] == "" ) {
+					print "WARNING: invalid hex encoding: %" c1 c2 | "cat >&2"
+				} else {
+					code = 0 + hextab [c1] * 16 + hextab [c2] + 0
+					c = sprintf ("%c", code)
+					i = i + 2
+				}
+			} else {
+				print "WARNING: invalid % encoding: " substr ($0, i, len - i)
+			}
+		} else if ( c == "+" ) {
+			c = " "
+		}
+		decoded = decoded c
+		++i
+	}
+	print decoded
+}
--- a/bin/controller.rc
+++ /dev/null
@@ -1,324 +1,0 @@
-#!/usr/local/plan9/bin/rc
-. ./cgilib.rc
-cd ..
-
-forbidden_uri_chars='[^a-zA-Z0-9_+\-\/\.]'
-response_format=html
-
-fn get_lib_file {
-    wantedfile = $1
-    if (test -f $sitedir/_werc/lib/$wantedfile)
-    	echo -n $sitedir/_werc/lib/$wantedfile
-    if not if(! ~ $#masterSite 0 && test -f sites/$masterSite/_werc/lib/$wantedfile)
-    	echo -n       sites/$masterSite/_werc/lib/$wantedfile
-    if not if (test -f lib/$wantedfile)
-    	echo -n        lib/$wantedfile
-}
-
-# Title
-fn gentitle {
-    echo '<h1 class="headerTitle"><a href="/">' ^ $"siteTitle ^ ' <span id="headerSubTitle">' ^ $"siteSubTitle ^ '</span></a></h1>'
-}
-
-# Don't change var name or trailing ';', some dirs change the filter!
-dirfilter = '/\/[._]/d; /'^$forbidden_uri_chars^'/d; /\/robots.\txt$/d; /\/sitemap\.txt$/d; s,^\./,,; s,\.md$,,; s,\.html,,; s,\.txt,,; '
-
-# To be used from config files
-fn hide_paths {
-    for(i in $*)
-       dirfilter = $dirfilter^'/^'$i'$/d; '
-}
-
-# Sidebar 
-fn menu {
-    ls -F $1 | sed $dirfilter | awk -F/ '
-    BEGIN { print "<ul class=\"side-bar\">" }
-    END { print "</ul>" }
-    # Should add \. to the list of allowed chars in file names, but need to find a way to filter out .txt and so on
-    /^([a-zA-Z0-9+_\-]+[\/*]?)+$/ && $NF != "index" {
-        isdir = match($0, "/$")
-        sub("[*/]$", "")
-
-        path = bname = $0
-        sub("^(.*/)?([0-9]+_)?", "", bname)
-        gsub("_", " ", bname)
-
-        if(isdir) {
-            bname = bname "/"
-            path = $0 "/"
-        }
-
-        if(index(ENVIRON["REQUEST_URI"] "/", "/" path) == 1) {
-            if(isdir) {
-                print "<li><a href=\"/" path "\" class=\"thisPage\">&raquo;<i> " bname "</i></a>"
-                system("rc -c ''menu " path "''")
-            } else {
-                print "<li><a href=\"/" path "\" class=\"thisPage\">&raquo;<i> " bname "</i></a>"
-            }
-        } else 
-            print "<li><a href=\"/" path "\">&rsaquo; " bname "</a>"
-
-        print "</li>"
-    }'
-}
-
-fn gensidebar {
-    @{
-        cd $sitedir
-        menu .
-    }
-}
-
-fn sortedBlogPostList {
-    # the /./ is added so we can sort -t. and order only the file name
-    if (! ~ $#* 0)
-        ls $*^'/./' | grep '/[0-9]+.*\.md$'| sort -r -t. +1
-}
-
-fn gen_blog_post_title {
-    title=`{basename $1 | sed 's/^[0-9\-]*_(.*)\.md$/\1/; s/_/ /g' }
-    permlink= `{echo $1 | sed 's,^/[a-z/]*www/,/,; s,^sites/[^/]*/*/,/,; s/\.md$//' }
-    du=`{ls -l $1}
-    by = ''
-    if (! ~ $#blogAuthor 0)
-        by='By '$"blogAuthor
-    if not if (~ $#blogDirs 1)
-        by='By '$du(4)
-    echo '##<a href="'^$"permlink^'">' $"title^'</a> *( '$by Last mod: $du(7 8 9) ' )*'
-}
-
-
-
-# Handlers
-fn set_handler {
-    handler = $1
-    shift
-    handler_args = $*
-}
-
-fn md_handler { cat $* | $formatter }
-
-fn tpl_handler { template $1 }
-
-fn html_handler {
-    cat $1 | /bin/sed '0,/<[Bb][Oo][Dd][Yy][^>]*>/d; /<\/[Bb][Oo][Dd][Yy]>/,$d' 
-}
-
-fn txt_handler {
-    echo '<pre>'
-    # XXX Insering a blank line between lines in input is good for fortunes.txt, but maybe not for other .txt files
-    # XXX Words are not broken, even if they are way beyond 82 chars long
-    cat $1 |sed 's/$/\n/g; s/</\&lt;/g; s/>/\&gt;/g' |fmt -l 82 -j
-    echo '</pre>'
-}
-
-fn dir_listing_handler {
-    d = `{basename -d $1}
-    echo '<h1 style="text-transform: capitalize;">' `{echo $d|sed -e 's,.*//,,g' -e 's,/$,,' -e 's,/, / ,g' } '</h1>'
-    echo '<ul style="text-transform: capitalize;">'
-    ls -F $d | sed -e $dirfilter' s,^'$sitedir'/.*/([^$].*),<li><a href="\1">\1</a></li>,' # XXX I'm not sure what the [^$] does there
-    echo '</ul>'
-}
-
-fn 404_handler { template `{ get_lib_file 404.tpl } }
-
-fn blog_dir_handler {
-    blogDirs = $*
-    tpl_handler lib/feeds/html.tpl
-}
-
-fn blog_post_handler {
-    gen_blog_post_title $1 | $formatter
-    $formatter < $1
-}
-
-
-fn select_handler {
-
-    if (test -f $body.md) {
-        if (! ~ $#inBlog 0)
-            set_handler blog_post_handler $body.md
-        if not
-            set_handler md_handler $body.md
-        if (! ~ $#allowComments 0 && ~ $REQUEST_METHOD POST) {
-            get_post_args comment_text
-            d = `{date -n} # FIXME Obvious race
-            d = $body.md_werc/comments/$d/
-
-            u = $logged_user
-            if (~ $#logged_user 0) {
-                get_post_args comment_user_name comment_user_password
-                # TODO Should take this path too if the user can login but doesn't
-                # belong to required group
-                if (! login_user $comment_user_name $comment_user_password) {
-                    u = $comment_user_name ':' $comment_user_password
-                    d = $d^'_pending'
-                }
-                if not
-                    u = $logged_user
-            }
-
-            umask 002
-            mkdir -m 775 -p $d
-            echo $u > $d/user
-            echo $comment_text > $d/body
-        }
-    }
-    if not if (test -f $body.tpl)
-        set_handler tpl_handler $body.tpl
-
-    if not if (test -f $body.html)
-        set_handler html_handler $body.html
-
-    # Explicit .html urls, unfortunately usually the web server will handle this as static files
-    if not if (~ $body *.html && test -f $body)
-        perm_redirect `{ echo $REQUEST_URI|sed 's/.html$//' }
-
-    # Rss feeds. TODO: we should check that the request is for a real blog dir
-    if not if (~ $REQUEST_URI */index.rss) {
-        uri = `{echo $uri | sed 's/index.rss$//'}
-        if (~ $#blogDirs 0)
-            blogDirs = $sitedir^$uri
-        uri=$baseuri$"uri
-        master_template=feeds/rss20.tpl
-        response_format=raw
-    }
-
-    # Blog handler
-    if not if (~ $body */[bB]log/index */[bB]log//index || ! ~ $#blogDirs 0) {
-        if (~ $#blogDirs 0)
-            blogDirs = `{basename -d $body}
-
-        rssuri=`{basename -d $uri}
-        rssuri=$baseuri`{cleanname $"rssuri^/index.rss}
-        extraHeaders=$"extraHeaders ^ \
-    	'<link rel="alternate" type="application/rss+xml" title="RSS" href="'$rssuri'" />
-'
-        set_handler tpl_handler lib/feeds/html.tpl
-    }
-
-    # Global tpl (eg sitemap.tpl)
-    if not if (test -f lib/^$uri^.tpl)
-        set_handler tpl_handler lib/^$uri^.tpl
-
-    if not if (test -f $body.txt)
-        set_handler txt_handler $body.txt
-
-    # Dir listing
-    if not if(~ $body */index)
-        set_handler dir_listing_handler $body
-
-    # File not found
-    if not {
-        set_handler 404_handler $body
-        dprint 'NOT FOUND: '$SERVER_NAME^$REQUEST_URI^' - '^$"HTTP_REFERER^' - '^$"HTTP_USER_AGENT
-        echo 'Status: 404 Not Found'
-    }
-
-}
-
-
-fn genbody {
-    $handler $handler_args
-}
-
-
-# Careful, the proper p9p path might not be set until initrc.local is sourced
-path=(. $PLAN9/bin ./bin/ /bin/ /usr/bin) 
-
-site=$SERVER_NAME
-headers=lib/headers.tpl
-master_template=default_master.tpl
-sidebar=sidebar
-baseuri=http://$site/
-for(i in siteTitle siteSubTitle pageTitle extraHeaders)
-    $i = ''
-
-. etc/initrc
-
-if(test -f etc/initrc.local)
-    . etc/initrc.local
-
-
-# Parse request URL
-uri = `{echo -n $REQUEST_URI | sed -e 's/\?.*//; s/'^$forbidden_uri_chars^'//g; s/\.\.*/./g;' -e '1q'}
-ifs='/' { args = `{echo -n $uri} }
-
-
-if(! ~ $#debug 0)
-    dprint '  ' $SERVER_NAME^$REQUEST_URI^' - '^$"HTTP_USER_AGENT
-
-# Hack: preload post data so we can access it from templates where cgi's stdin is not accesible
-if(~ $REQUEST_METHOD POST) {
-    get_post_args 
-    login_user
-}
-
-if (! ~ $args '') {
-    if (~ $args($#args) 'index')
-        perm_redirect `{ echo $REQUEST_URI | sed 's,/index$,/,' }
-        
-    pageTitle=`{echo $args | sed -e 's/ / - /g' -e 's/_/ /g'}
-    req_path=$uri
-}
-if not {
-    req_path='/'
-}
-
-fpath=$sitedir
-for (i in ('' $args)) {
-    fpath = $fpath/$i
-    # We don't want blog settings to cascade into posts, note that we are inBlog instead
-    if (! ~ $#blogDirs 0 && ! ~ $req_path */index.rss */[bB]log */[bB]log/) {
-        inBlog = $blogDirs
-        blogDirs = () 
-    }
-
-    if (test -f $fpath/_config)
-        . $fpath/_config # DEPRECATED
-    if (test -f $fpath/_werc/config)
-        . $fpath/_werc/config
-
-    if (~ $#blogDirs 0 && ~ $#inBlog 0 && ~ $i [Bb]log)
-        inBlog = 'yes'
-}
-
-# Redirections and other preprocessing
-if (~ $#redirectPermanent 1)
-    perm_redirect $"redirectPermanent
-
-if not if (~ $#redirectPermanent 2 && {echo $SERVER_NAME^$REQUEST_URI|grep -s $redirectPermanent(1) }) {
-    # Experimental regexp sub-based redirect, probably should find a nicer interface
-    # For now only used at sites/harmful.cat-v.org/software/OO_programming/_config
-    to=`{echo $SERVER_NAME^$REQUEST_URI|sed 's@'^$redirectPermanent(1)^'@'^$redirectPermanent(2)^'@'}
-    if(! ~ $to $REQUEST_URI)
-        perm_redirect $to
-}
-
-# Set Page title
-if(~ $pageTitle '')
-	pageTitle=$siteTitle^' '^$siteSubTitle
-if not
-	pageTitle=$"pageTitle^' | '^$"siteTitle^' '^$"siteSubTitle
-
-
-body=$sitedir/$req_path
-if (test -d $body) {
-    if(! ~ $body */)
-    	perm_redirect $REQUEST_URI^'/'
-    body=$body/index
-}
-
-select_handler
-
-# Template/body selection
-master_template= `{get_lib_file $master_template}
-
-if(~ $response_format html) {
-    # Is awk_buffer really useful?
-	template $headers $master_template | awk_buffer
-	echo '</body></html>'
-}
-if not if (~ $response_format raw) {
-	template < $master_template 
-}
--- a/bin/gensitemaptxt.rc
+++ /dev/null
@@ -1,13 +1,0 @@
-#!/usr/bin/env rc
-
-for ( d in sites/*/ ) {
-echo $d
-9 du -a $d | awk '/\.(md|html)$/ { print $2 }; {}' | 9 sed -e 's/\.(md|html)$//' -e 's,/index$,/,' -e 's,^sites/,http://,' > $d/sitemap.txt
-
-if(! test -f $d/robots.txt) {
-	echo generating missing robots.txt for $d
-	echo $d|sed 's,sites/,Sitemap: http://,; s/$/sitemap.txt/;' > $d/robots.txt
-	cat $d/robots.txt
-}
-
-}
--- a/bin/hgweb.config
+++ /dev/null
@@ -1,12 +1,0 @@
-[web]
-style = gitweb
-allow_archive = bz2
-
-#[paths]
-#w9 = /gsoc/hg/w9/
-
-[collections]
-#allow_archive = bz2 zip
-/gsoc/hg = /gsoc/hg/
-#/var/hg = /var/hg/
-
--- a/bin/hgwebdir.cgi
+++ /dev/null
@@ -1,47 +1,0 @@
-#!/usr/bin/env python
-#
-# An example CGI script to export multiple hgweb repos, edit as necessary
-
-# send python tracebacks to the browser if an error occurs:
-import cgitb
-cgitb.enable()
-
-# adjust python path if not a system-wide install:
-#import sys
-#sys.path.insert(0, "/path/to/python/lib")
-
-# If you'd like to serve pages with UTF-8 instead of your default
-# locale charset, you can do so by uncommenting the following lines.
-# Note that this will cause your .hgrc files to be interpreted in
-# UTF-8 and all your repo files to be displayed using UTF-8.
-#
-#import os
-#os.environ["HGENCODING"] = "UTF-8"
-
-from mercurial.hgweb.hgwebdir_mod import hgwebdir
-from mercurial.hgweb.request import wsgiapplication
-import mercurial.hgweb.wsgicgi as wsgicgi
-
-# The config file looks like this.  You can have paths to individual
-# repos, collections of repos in a directory tree, or both.
-#
-# [paths]
-# virtual/path = /real/path
-# virtual/path = /real/path
-#
-# [collections]
-# /prefix/to/strip/off = /root/of/tree/full/of/repos
-#
-# collections example: say directory tree /foo contains repos /foo/bar,
-# /foo/quux/baz.  Give this config section:
-#   [collections]
-#   /foo = /foo
-# Then repos will list as bar and quux/baz.
-#
-# Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
-# or use a dictionary with entries like 'virtual/path': '/real/path'
-
-def make_web_app():
-    return hgwebdir("hgweb.config")
-
-wsgicgi.launch(wsgiapplication(make_web_app))
--- a/bin/markdown.pl
+++ /dev/null
@@ -1,1447 +1,0 @@
-#!/usr/bin/env perl
-#
-# Markdown -- A text-to-HTML conversion tool for web writers
-#
-# Copyright (c) 2004 John Gruber
-# <http://daringfireball.net/projects/markdown/>
-#
-package Markdown;
-require 5.006_000;
-use strict;
-use warnings;
-
-use Digest::MD5 qw(md5_hex);
-use vars qw($VERSION);
-$VERSION = '1.0.1';
-# Tue 14 Dec 2004
-
-## Disabled; causes problems under Perl 5.6.1:
-# use utf8;
-# binmode( STDOUT, ":utf8" );  # c.f.: http://acis.openlib.org/dev/perl-unicode-struggle.html
-
-
-#
-# Global default settings:
-#
-my $g_empty_element_suffix = " />";     # Change to ">" for HTML output
-my $g_tab_width = 4;
-
-
-#
-# Globals:
-#
-
-# Regex to match balanced [brackets]. See Friedl's
-# "Mastering Regular Expressions", 2nd Ed., pp. 328-331.
-my $g_nested_brackets;
-$g_nested_brackets = qr{
-	(?> 								# Atomic matching
-	   [^\[\]]+							# Anything other than brackets
-	 | 
-	   \[
-		 (??{ $g_nested_brackets })		# Recursive set of nested brackets
-	   \]
-	)*
-}x;
-
-
-# Table of hash values for escaped characters:
-my %g_escape_table;
-foreach my $char (split //, '\\`*_{}[]()>#+-.!') {
-	$g_escape_table{$char} = md5_hex($char);
-}
-
-
-# Global hashes, used by various utility routines
-my %g_urls;
-my %g_titles;
-my %g_html_blocks;
-
-# Used to track when we're inside an ordered or unordered list
-# (see _ProcessListItems() for details):
-my $g_list_level = 0;
-
-
-#### Blosxom plug-in interface ##########################################
-
-# Set $g_blosxom_use_meta to 1 to use Blosxom's meta plug-in to determine
-# which posts Markdown should process, using a "meta-markup: markdown"
-# header. If it's set to 0 (the default), Markdown will process all
-# entries.
-my $g_blosxom_use_meta = 0;
-
-sub start { 1; }
-sub story {
-	my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
-
-	if ( (! $g_blosxom_use_meta) or
-	     (defined($meta::markup) and ($meta::markup =~ /^\s*markdown\s*$/i))
-	     ){
-			$$body_ref  = Markdown($$body_ref);
-     }
-     1;
-}
-
-
-#### Movable Type plug-in interface #####################################
-eval {require MT};  # Test to see if we're running in MT.
-unless ($@) {
-    require MT;
-    import  MT;
-    require MT::Template::Context;
-    import  MT::Template::Context;
-
-	eval {require MT::Plugin};  # Test to see if we're running >= MT 3.0.
-	unless ($@) {
-		require MT::Plugin;
-		import  MT::Plugin;
-		my $plugin = new MT::Plugin({
-			name => "Markdown",
-			description => "A plain-text-to-HTML formatting plugin. (Version: $VERSION)",
-			doc_link => 'http://daringfireball.net/projects/markdown/'
-		});
-		MT->add_plugin( $plugin );
-	}
-
-	MT::Template::Context->add_container_tag(MarkdownOptions => sub {
-		my $ctx	 = shift;
-		my $args = shift;
-		my $builder = $ctx->stash('builder');
-		my $tokens = $ctx->stash('tokens');
-
-		if (defined ($args->{'output'}) ) {
-			$ctx->stash('markdown_output', lc $args->{'output'});
-		}
-
-		defined (my $str = $builder->build($ctx, $tokens) )
-			or return $ctx->error($builder->errstr);
-		$str;		# return value
-	});
-
-	MT->add_text_filter('markdown' => {
-		label     => 'Markdown',
-		docs      => 'http://daringfireball.net/projects/markdown/',
-		on_format => sub {
-			my $text = shift;
-			my $ctx  = shift;
-			my $raw  = 0;
-		    if (defined $ctx) {
-		    	my $output = $ctx->stash('markdown_output'); 
-				if (defined $output  &&  $output =~ m/^html/i) {
-					$g_empty_element_suffix = ">";
-					$ctx->stash('markdown_output', '');
-				}
-				elsif (defined $output  &&  $output eq 'raw') {
-					$raw = 1;
-					$ctx->stash('markdown_output', '');
-				}
-				else {
-					$raw = 0;
-					$g_empty_element_suffix = " />";
-				}
-			}
-			$text = $raw ? $text : Markdown($text);
-			$text;
-		},
-	});
-
-	# If SmartyPants is loaded, add a combo Markdown/SmartyPants text filter:
-	my $smartypants;
-
-	{
-		no warnings "once";
-		$smartypants = $MT::Template::Context::Global_filters{'smarty_pants'};
-	}
-
-	if ($smartypants) {
-		MT->add_text_filter('markdown_with_smartypants' => {
-			label     => 'Markdown With SmartyPants',
-			docs      => 'http://daringfireball.net/projects/markdown/',
-			on_format => sub {
-				my $text = shift;
-				my $ctx  = shift;
-				if (defined $ctx) {
-					my $output = $ctx->stash('markdown_output'); 
-					if (defined $output  &&  $output eq 'html') {
-						$g_empty_element_suffix = ">";
-					}
-					else {
-						$g_empty_element_suffix = " />";
-					}
-				}
-				$text = Markdown($text);
-				$text = $smartypants->($text, '1');
-			},
-		});
-	}
-}
-else {
-#### BBEdit/command-line text filter interface ##########################
-# Needs to be hidden from MT (and Blosxom when running in static mode).
-
-    # We're only using $blosxom::version once; tell Perl not to warn us:
-	no warnings 'once';
-    unless ( defined($blosxom::version) ) {
-		use warnings;
-
-		#### Check for command-line switches: #################
-		my %cli_opts;
-		use Getopt::Long;
-		Getopt::Long::Configure('pass_through');
-		GetOptions(\%cli_opts,
-			'version',
-			'shortversion',
-			'html4tags',
-		);
-		if ($cli_opts{'version'}) {		# Version info
-			print "\nThis is Markdown, version $VERSION.\n";
-			print "Copyright 2004 John Gruber\n";
-			print "http://daringfireball.net/projects/markdown/\n\n";
-			exit 0;
-		}
-		if ($cli_opts{'shortversion'}) {		# Just the version number string.
-			print $VERSION;
-			exit 0;
-		}
-		if ($cli_opts{'html4tags'}) {			# Use HTML tag style instead of XHTML
-			$g_empty_element_suffix = ">";
-		}
-
-
-		#### Process incoming text: ###########################
-		my $text;
-		{
-			local $/;               # Slurp the whole file
-			$text = <>;
-		}
-        print Markdown($text);
-    }
-}
-
-
-
-sub Markdown {
-#
-# Main function. The order in which other subs are called here is
-# essential. Link and image substitutions need to happen before
-# _EscapeSpecialChars(), so that any *'s or _'s in the <a>
-# and <img> tags get encoded.
-#
-	my $text = shift;
-
-	# Clear the global hashes. If we don't clear these, you get conflicts
-	# from other articles when generating a page which contains more than
-	# one article (e.g. an index page that shows the N most recent
-	# articles):
-	%g_urls = ();
-	%g_titles = ();
-	%g_html_blocks = ();
-
-
-	# Standardize line endings:
-	$text =~ s{\r\n}{\n}g; 	# DOS to Unix
-	$text =~ s{\r}{\n}g; 	# Mac to Unix
-
-	# Make sure $text ends with a couple of newlines:
-	$text .= "\n\n";
-
-	# Convert all tabs to spaces.
-	$text = _Detab($text);
-
-	# Strip any lines consisting only of spaces and tabs.
-	# This makes subsequent regexen easier to write, because we can
-	# match consecutive blank lines with /\n+/ instead of something
-	# contorted like /[ \t]*\n+/ .
-	$text =~ s/^[ \t]+$//mg;
-
-	# Turn block-level HTML blocks into hash entries
-	$text = _HashHTMLBlocks($text);
-
-	# Strip link definitions, store in hashes.
-	$text = _StripLinkDefinitions($text);
-
-	$text = _RunBlockGamut($text);
-
-	$text = _UnescapeSpecialChars($text);
-
-	return $text . "\n";
-}
-
-
-sub _StripLinkDefinitions {
-#
-# Strips link definitions from text, stores the URLs and titles in
-# hash references.
-#
-	my $text = shift;
-	my $less_than_tab = $g_tab_width - 1;
-
-	# Link defs are in the form: ^[id]: url "optional title"
-	while ($text =~ s{
-						^[ ]{0,$less_than_tab}\[(.+)\]:	# id = $1
-						  [ \t]*
-						  \n?				# maybe *one* newline
-						  [ \t]*
-						<?(\S+?)>?			# url = $2
-						  [ \t]*
-						  \n?				# maybe one newline
-						  [ \t]*
-						(?:
-							(?<=\s)			# lookbehind for whitespace
-							["(]
-							(.+?)			# title = $3
-							[")]
-							[ \t]*
-						)?	# title is optional
-						(?:\n+|\Z)
-					}
-					{}mx) {
-		$g_urls{lc $1} = _EncodeAmpsAndAngles( $2 );	# Link IDs are case-insensitive
-		if ($3) {
-			$g_titles{lc $1} = $3;
-			$g_titles{lc $1} =~ s/"/&quot;/g;
-		}
-	}
-
-	return $text;
-}
-
-
-sub _HashHTMLBlocks {
-	my $text = shift;
-	my $less_than_tab = $g_tab_width - 1;
-
-	# Hashify HTML blocks:
-	# We only want to do this for block-level HTML tags, such as headers,
-	# lists, and tables. That's because we still want to wrap <p>s around
-	# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
-	# phrase emphasis, and spans. The list of tags we're looking for is
-	# hard-coded:
-	my $block_tags_a = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del/;
-	my $block_tags_b = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math/;
-
-	# First, look for nested blocks, e.g.:
-	# 	<div>
-	# 		<div>
-	# 		tags for inner block must be indented.
-	# 		</div>
-	# 	</div>
-	#
-	# The outermost tags must start at the left margin for this to match, and
-	# the inner nested divs must be indented.
-	# We need to do this before the next, more liberal match, because the next
-	# match will start at the first `<div>` and stop at the first `</div>`.
-	$text =~ s{
-				(						# save in $1
-					^					# start of line  (with /m)
-					<($block_tags_a)	# start tag = $2
-					\b					# word break
-					(.*\n)*?			# any number of lines, minimally matching
-					</\2>				# the matching end tag
-					[ \t]*				# trailing spaces/tabs
-					(?=\n+|\Z)	# followed by a newline or end of document
-				)
-			}{
-				my $key = md5_hex($1);
-				$g_html_blocks{$key} = $1;
-				"\n\n" . $key . "\n\n";
-			}egmx;
-
-
-	#
-	# Now match more liberally, simply from `\n<tag>` to `</tag>\n`
-	#
-	$text =~ s{
-				(						# save in $1
-					^					# start of line  (with /m)
-					<($block_tags_b)	# start tag = $2
-					\b					# word break
-					(.*\n)*?			# any number of lines, minimally matching
-					.*</\2>				# the matching end tag
-					[ \t]*				# trailing spaces/tabs
-					(?=\n+|\Z)	# followed by a newline or end of document
-				)
-			}{
-				my $key = md5_hex($1);
-				$g_html_blocks{$key} = $1;
-				"\n\n" . $key . "\n\n";
-			}egmx;
-	# Special case just for <hr />. It was easier to make a special case than
-	# to make the other regex more complicated.	
-	$text =~ s{
-				(?:
-					(?<=\n\n)		# Starting after a blank line
-					|				# or
-					\A\n?			# the beginning of the doc
-				)
-				(						# save in $1
-					[ ]{0,$less_than_tab}
-					<(hr)				# start tag = $2
-					\b					# word break
-					([^<>])*?			# 
-					/?>					# the matching end tag
-					[ \t]*
-					(?=\n{2,}|\Z)		# followed by a blank line or end of document
-				)
-			}{
-				my $key = md5_hex($1);
-				$g_html_blocks{$key} = $1;
-				"\n\n" . $key . "\n\n";
-			}egx;
-
-	# Special case for standalone HTML comments:
-	$text =~ s{
-				(?:
-					(?<=\n\n)		# Starting after a blank line
-					|				# or
-					\A\n?			# the beginning of the doc
-				)
-				(						# save in $1
-					[ ]{0,$less_than_tab}
-					(?s:
-						<!
-						(--.*?--\s*)+
-						>
-					)
-					[ \t]*
-					(?=\n{2,}|\Z)		# followed by a blank line or end of document
-				)
-			}{
-				my $key = md5_hex($1);
-				$g_html_blocks{$key} = $1;
-				"\n\n" . $key . "\n\n";
-			}egx;
-
-
-	return $text;
-}
-
-
-sub _RunBlockGamut {
-#
-# These are all the transformations that form block-level
-# tags like paragraphs, headers, and list items.
-#
-	my $text = shift;
-
-	$text = _DoHeaders($text);
-
-	# Do Horizontal Rules:
-	$text =~ s{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
-	$text =~ s{^[ ]{0,2}([ ]? -[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
-	$text =~ s{^[ ]{0,2}([ ]? _[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
-
-	$text = _DoLists($text);
-
-	$text = _DoCodeBlocks($text);
-
-	$text = _DoBlockQuotes($text);
-
-	# We already ran _HashHTMLBlocks() before, in Markdown(), but that
-	# was to escape raw HTML in the original Markdown source. This time,
-	# we're escaping the markup we've just created, so that we don't wrap
-	# <p> tags around block-level tags.
-	$text = _HashHTMLBlocks($text);
-
-	$text = _FormParagraphs($text);
-
-	return $text;
-}
-
-
-sub _RunSpanGamut {
-#
-# These are all the transformations that occur *within* block-level
-# tags like paragraphs, headers, and list items.
-#
-	my $text = shift;
-
-	$text = _DoCodeSpans($text);
-
-	$text = _EscapeSpecialChars($text);
-
-	# Process anchor and image tags. Images must come first,
-	# because ![foo][f] looks like an anchor.
-	$text = _DoImages($text);
-	$text = _DoAnchors($text);
-
-	# Make links out of things like `<http://example.com/>`
-	# Must come after _DoAnchors(), because you can use < and >
-	# delimiters in inline links like [this](<url>).
-	$text = _DoAutoLinks($text);
-
-	$text = _EncodeAmpsAndAngles($text);
-
-	$text = _DoItalicsAndBold($text);
-
-	# Do hard breaks:
-	$text =~ s/ {2,}\n/ <br$g_empty_element_suffix\n/g;
-
-	return $text;
-}
-
-
-sub _EscapeSpecialChars {
-	my $text = shift;
-	my $tokens ||= _TokenizeHTML($text);
-
-	$text = '';   # rebuild $text from the tokens
-# 	my $in_pre = 0;	 # Keep track of when we're inside <pre> or <code> tags.
-# 	my $tags_to_skip = qr!<(/?)(?:pre|code|kbd|script|math)[\s>]!;
-
-	foreach my $cur_token (@$tokens) {
-		if ($cur_token->[0] eq "tag") {
-			# Within tags, encode * and _ so they don't conflict
-			# with their use in Markdown for italics and strong.
-			# We're replacing each such character with its
-			# corresponding MD5 checksum value; this is likely
-			# overkill, but it should prevent us from colliding
-			# with the escape values by accident.
-			$cur_token->[1] =~  s! \* !$g_escape_table{'*'}!gx;
-			$cur_token->[1] =~  s! _  !$g_escape_table{'_'}!gx;
-			$text .= $cur_token->[1];
-		} else {
-			my $t = $cur_token->[1];
-			$t = _EncodeBackslashEscapes($t);
-			$text .= $t;
-		}
-	}
-	return $text;
-}
-
-
-sub _DoAnchors {
-#
-# Turn Markdown link shortcuts into XHTML <a> tags.
-#
-	my $text = shift;
-
-	#
-	# First, handle reference-style links: [link text] [id]
-	#
-	$text =~ s{
-		(					# wrap whole match in $1
-		  \[
-		    ($g_nested_brackets)	# link text = $2
-		  \]
-
-		  [ ]?				# one optional space
-		  (?:\n[ ]*)?		# one optional newline followed by spaces
-
-		  \[
-		    (.*?)		# id = $3
-		  \]
-		)
-	}{
-		my $result;
-		my $whole_match = $1;
-		my $link_text   = $2;
-		my $link_id     = lc $3;
-
-		if ($link_id eq "") {
-			$link_id = lc $link_text;     # for shortcut links like [this][].
-		}
-
-		if (defined $g_urls{$link_id}) {
-			my $url = $g_urls{$link_id};
-			$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
-			$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
-			$result = "<a href=\"$url\"";
-			if ( defined $g_titles{$link_id} ) {
-				my $title = $g_titles{$link_id};
-				$title =~ s! \* !$g_escape_table{'*'}!gx;
-				$title =~ s!  _ !$g_escape_table{'_'}!gx;
-				$result .=  " title=\"$title\"";
-			}
-			$result .= ">$link_text</a>";
-		}
-		else {
-			$result = $whole_match;
-		}
-		$result;
-	}xsge;
-
-	#
-	# Next, inline-style links: [link text](url "optional title")
-	#
-	$text =~ s{
-		(				# wrap whole match in $1
-		  \[
-		    ($g_nested_brackets)	# link text = $2
-		  \]
-		  \(			# literal paren
-		  	[ \t]*
-			<?(.*?)>?	# href = $3
-		  	[ \t]*
-			(			# $4
-			  (['"])	# quote char = $5
-			  (.*?)		# Title = $6
-			  \5		# matching quote
-			)?			# title is optional
-		  \)
-		)
-	}{
-		my $result;
-		my $whole_match = $1;
-		my $link_text   = $2;
-		my $url	  		= $3;
-		my $title		= $6;
-
-		$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
-		$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
-		$result = "<a href=\"$url\"";
-
-		if (defined $title) {
-			$title =~ s/"/&quot;/g;
-			$title =~ s! \* !$g_escape_table{'*'}!gx;
-			$title =~ s!  _ !$g_escape_table{'_'}!gx;
-			$result .=  " title=\"$title\"";
-		}
-
-		$result .= ">$link_text</a>";
-
-		$result;
-	}xsge;
-
-	return $text;
-}
-
-
-sub _DoImages {
-#
-# Turn Markdown image shortcuts into <img> tags.
-#
-	my $text = shift;
-
-	#
-	# First, handle reference-style labeled images: ![alt text][id]
-	#
-	$text =~ s{
-		(				# wrap whole match in $1
-		  !\[
-		    (.*?)		# alt text = $2
-		  \]
-
-		  [ ]?				# one optional space
-		  (?:\n[ ]*)?		# one optional newline followed by spaces
-
-		  \[
-		    (.*?)		# id = $3
-		  \]
-
-		)
-	}{
-		my $result;
-		my $whole_match = $1;
-		my $alt_text    = $2;
-		my $link_id     = lc $3;
-
-		if ($link_id eq "") {
-			$link_id = lc $alt_text;     # for shortcut links like ![this][].
-		}
-
-		$alt_text =~ s/"/&quot;/g;
-		if (defined $g_urls{$link_id}) {
-			my $url = $g_urls{$link_id};
-			$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
-			$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
-			$result = "<img src=\"$url\" alt=\"$alt_text\"";
-			if (defined $g_titles{$link_id}) {
-				my $title = $g_titles{$link_id};
-				$title =~ s! \* !$g_escape_table{'*'}!gx;
-				$title =~ s!  _ !$g_escape_table{'_'}!gx;
-				$result .=  " title=\"$title\"";
-			}
-			$result .= $g_empty_element_suffix;
-		}
-		else {
-			# If there's no such link ID, leave intact:
-			$result = $whole_match;
-		}
-
-		$result;
-	}xsge;
-
-	#
-	# Next, handle inline images:  ![alt text](url "optional title")
-	# Don't forget: encode * and _
-
-	$text =~ s{
-		(				# wrap whole match in $1
-		  !\[
-		    (.*?)		# alt text = $2
-		  \]
-		  \(			# literal paren
-		  	[ \t]*
-			<?(\S+?)>?	# src url = $3
-		  	[ \t]*
-			(			# $4
-			  (['"])	# quote char = $5
-			  (.*?)		# title = $6
-			  \5		# matching quote
-			  [ \t]*
-			)?			# title is optional
-		  \)
-		)
-	}{
-		my $result;
-		my $whole_match = $1;
-		my $alt_text    = $2;
-		my $url	  		= $3;
-		my $title		= '';
-		if (defined($6)) {
-			$title		= $6;
-		}
-
-		$alt_text =~ s/"/&quot;/g;
-		$title    =~ s/"/&quot;/g;
-		$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
-		$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
-		$result = "<img src=\"$url\" alt=\"$alt_text\"";
-		if (defined $title) {
-			$title =~ s! \* !$g_escape_table{'*'}!gx;
-			$title =~ s!  _ !$g_escape_table{'_'}!gx;
-			$result .=  " title=\"$title\"";
-		}
-		$result .= $g_empty_element_suffix;
-
-		$result;
-	}xsge;
-
-	return $text;
-}
-
-
-sub _DoHeaders {
-	my $text = shift;
-
-	# Setext-style headers:
-	#	  Header 1
-	#	  ========
-	#  
-	#	  Header 2
-	#	  --------
-	#
-	$text =~ s{ ^(.+)[ \t]*\n=+[ \t]*\n+ }{
-		"<h1>"  .  _RunSpanGamut($1)  .  "</h1>\n\n";
-	}egmx;
-
-	$text =~ s{ ^(.+)[ \t]*\n-+[ \t]*\n+ }{
-		"<h2>"  .  _RunSpanGamut($1)  .  "</h2>\n\n";
-	}egmx;
-
-
-	# atx-style headers:
-	#	# Header 1
-	#	## Header 2
-	#	## Header 2 with closing hashes ##
-	#	...
-	#	###### Header 6
-	#
-	$text =~ s{
-			^(\#{1,6})	# $1 = string of #'s
-			[ \t]*
-			(.+?)		# $2 = Header text
-			[ \t]*
-			\#*			# optional closing #'s (not counted)
-			\n+
-		}{
-			my $h_level = length($1);
-			"<h$h_level>"  .  _RunSpanGamut($2)  .  "</h$h_level>\n\n";
-		}egmx;
-
-	return $text;
-}
-
-
-sub _DoLists {
-#
-# Form HTML ordered (numbered) and unordered (bulleted) lists.
-#
-	my $text = shift;
-	my $less_than_tab = $g_tab_width - 1;
-
-	# Re-usable patterns to match list item bullets and number markers:
-	my $marker_ul  = qr/[*+-]/;
-	my $marker_ol  = qr/\d+[.]/;
-	my $marker_any = qr/(?:$marker_ul|$marker_ol)/;
-
-	# Re-usable pattern to match any entirel ul or ol list:
-	my $whole_list = qr{
-		(								# $1 = whole list
-		  (								# $2
-			[ ]{0,$less_than_tab}
-			(${marker_any})				# $3 = first list item marker
-			[ \t]+
-		  )
-		  (?s:.+?)
-		  (								# $4
-			  \z
-			|
-			  \n{2,}
-			  (?=\S)
-			  (?!						# Negative lookahead for another list item marker
-				[ \t]*
-				${marker_any}[ \t]+
-			  )
-		  )
-		)
-	}mx;
-
-	# We use a different prefix before nested lists than top-level lists.
-	# See extended comment in _ProcessListItems().
-	#
-	# Note: There's a bit of duplication here. My original implementation
-	# created a scalar regex pattern as the conditional result of the test on
-	# $g_list_level, and then only ran the $text =~ s{...}{...}egmx
-	# substitution once, using the scalar as the pattern. This worked,
-	# everywhere except when running under MT on my hosting account at Pair
-	# Networks. There, this caused all rebuilds to be killed by the reaper (or
-	# perhaps they crashed, but that seems incredibly unlikely given that the
-	# same script on the same server ran fine *except* under MT. I've spent
-	# more time trying to figure out why this is happening than I'd like to
-	# admit. My only guess, backed up by the fact that this workaround works,
-	# is that Perl optimizes the substition when it can figure out that the
-	# pattern will never change, and when this optimization isn't on, we run
-	# afoul of the reaper. Thus, the slightly redundant code to that uses two
-	# static s/// patterns rather than one conditional pattern.
-
-	if ($g_list_level) {
-		$text =~ s{
-				^
-				$whole_list
-			}{
-				my $list = $1;
-				my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol";
-				# Turn double returns into triple returns, so that we can make a
-				# paragraph for the last item in a list, if necessary:
-				$list =~ s/\n{2,}/\n\n\n/g;
-				my $result = _ProcessListItems($list, $marker_any);
-				$result = "<$list_type>\n" . $result . "</$list_type>\n";
-				$result;
-			}egmx;
-	}
-	else {
-		$text =~ s{
-				(?:(?<=\n\n)|\A\n?)
-				$whole_list
-			}{
-				my $list = $1;
-				my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol";
-				# Turn double returns into triple returns, so that we can make a
-				# paragraph for the last item in a list, if necessary:
-				$list =~ s/\n{2,}/\n\n\n/g;
-				my $result = _ProcessListItems($list, $marker_any);
-				$result = "<$list_type>\n" . $result . "</$list_type>\n";
-				$result;
-			}egmx;
-	}
-
-
-	return $text;
-}
-
-
-sub _ProcessListItems {
-#
-#	Process the contents of a single ordered or unordered list, splitting it
-#	into individual list items.
-#
-
-	my $list_str = shift;
-	my $marker_any = shift;
-
-
-	# The $g_list_level global keeps track of when we're inside a list.
-	# Each time we enter a list, we increment it; when we leave a list,
-	# we decrement. If it's zero, we're not in a list anymore.
-	#
-	# We do this because when we're not inside a list, we want to treat
-	# something like this:
-	#
-	#		I recommend upgrading to version
-	#		8. Oops, now this line is treated
-	#		as a sub-list.
-	#
-	# As a single paragraph, despite the fact that the second line starts
-	# with a digit-period-space sequence.
-	#
-	# Whereas when we're inside a list (or sub-list), that line will be
-	# treated as the start of a sub-list. What a kludge, huh? This is
-	# an aspect of Markdown's syntax that's hard to parse perfectly
-	# without resorting to mind-reading. Perhaps the solution is to
-	# change the syntax rules such that sub-lists must start with a
-	# starting cardinal number; e.g. "1." or "a.".
-
-	$g_list_level++;
-
-	# trim trailing blank lines:
-	$list_str =~ s/\n{2,}\z/\n/;
-
-
-	$list_str =~ s{
-		(\n)?							# leading line = $1
-		(^[ \t]*)						# leading whitespace = $2
-		($marker_any) [ \t]+			# list marker = $3
-		((?s:.+?)						# list item text   = $4
-		(\n{1,2}))
-		(?= \n* (\z | \2 ($marker_any) [ \t]+))
-	}{
-		my $item = $4;
-		my $leading_line = $1;
-		my $leading_space = $2;
-
-		if ($leading_line or ($item =~ m/\n{2,}/)) {
-			$item = _RunBlockGamut(_Outdent($item));
-		}
-		else {
-			# Recursion for sub-lists:
-			$item = _DoLists(_Outdent($item));
-			chomp $item;
-			$item = _RunSpanGamut($item);
-		}
-
-		"<li>" . $item . "</li>\n";
-	}egmx;
-
-	$g_list_level--;
-	return $list_str;
-}
-
-
-
-sub _DoCodeBlocks {
-#
-#	Process Markdown `<pre><code>` blocks.
-#	
-
-	my $text = shift;
-
-	$text =~ s{
-			(?:\n\n|\A)
-			(	            # $1 = the code block -- one or more lines, starting with a space/tab
-			  (?:
-			    (?:[ ]{$g_tab_width} | \t)  # Lines must start with a tab or a tab-width of spaces
-			    .*\n+
-			  )+
-			)
-			((?=^[ ]{0,$g_tab_width}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
-		}{
-			my $codeblock = $1;
-			my $result; # return value
-
-			$codeblock = _EncodeCode(_Outdent($codeblock));
-			$codeblock = _Detab($codeblock);
-			$codeblock =~ s/\A\n+//; # trim leading newlines
-			$codeblock =~ s/\s+\z//; # trim trailing whitespace
-
-			$result = "\n\n<pre><code>" . $codeblock . "\n</code></pre>\n\n";
-
-			$result;
-		}egmx;
-
-	return $text;
-}
-
-
-sub _DoCodeSpans {
-#
-# 	*	Backtick quotes are used for <code></code> spans.
-# 
-# 	*	You can use multiple backticks as the delimiters if you want to
-# 		include literal backticks in the code span. So, this input:
-#     
-#         Just type ``foo `bar` baz`` at the prompt.
-#     
-#     	Will translate to:
-#     
-#         <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
-#     
-#		There's no arbitrary limit to the number of backticks you
-#		can use as delimters. If you need three consecutive backticks
-#		in your code, use four for delimiters, etc.
-#
-#	*	You can use spaces to get literal backticks at the edges:
-#     
-#         ... type `` `bar` `` ...
-#     
-#     	Turns to:
-#     
-#         ... type <code>`bar`</code> ...
-#
-
-	my $text = shift;
-
-	$text =~ s@
-			(`+)		# $1 = Opening run of `
-			(.+?)		# $2 = The code block
-			(?<!`)
-			\1			# Matching closer
-			(?!`)
-		@
- 			my $c = "$2";
- 			$c =~ s/^[ \t]*//g; # leading whitespace
- 			$c =~ s/[ \t]*$//g; # trailing whitespace
- 			$c = _EncodeCode($c);
-			"<code>$c</code>";
-		@egsx;
-
-	return $text;
-}
-
-
-sub _EncodeCode {
-#
-# Encode/escape certain characters inside Markdown code runs.
-# The point is that in code, these characters are literals,
-# and lose their special Markdown meanings.
-#
-    local $_ = shift;
-
-	# Encode all ampersands; HTML entities are not
-	# entities within a Markdown code span.
-	s/&/&amp;/g;
-
-	# Encode $'s, but only if we're running under Blosxom.
-	# (Blosxom interpolates Perl variables in article bodies.)
-	{
-		no warnings 'once';
-    	if (defined($blosxom::version)) {
-    		s/\$/&#036;/g;	
-    	}
-    }
-
-
-	# Do the angle bracket song and dance:
-	s! <  !&lt;!gx;
-	s! >  !&gt;!gx;
-
-	# Now, escape characters that are magic in Markdown:
-	s! \* !$g_escape_table{'*'}!gx;
-	s! _  !$g_escape_table{'_'}!gx;
-	s! {  !$g_escape_table{'{'}!gx;
-	s! }  !$g_escape_table{'}'}!gx;
-	s! \[ !$g_escape_table{'['}!gx;
-	s! \] !$g_escape_table{']'}!gx;
-	s! \\ !$g_escape_table{'\\'}!gx;
-
-	return $_;
-}
-
-
-sub _DoItalicsAndBold {
-	my $text = shift;
-
-	# <strong> must go first:
-	$text =~ s{ (\*\*|__) (?=\S) (.+?[*_]*) (?<=\S) \1 }
-		{<strong>$2</strong>}gsx;
-
-	$text =~ s{ (\*|_) (?=\S) (.+?) (?<=\S) \1 }
-		{<em>$2</em>}gsx;
-
-	return $text;
-}
-
-
-sub _DoBlockQuotes {
-	my $text = shift;
-
-	$text =~ s{
-		  (								# Wrap whole match in $1
-			(
-			  ^[ \t]*>[ \t]?			# '>' at the start of a line
-			    .+\n					# rest of the first line
-			  (.+\n)*					# subsequent consecutive lines
-			  \n*						# blanks
-			)+
-		  )
-		}{
-			my $bq = $1;
-			$bq =~ s/^[ \t]*>[ \t]?//gm;	# trim one level of quoting
-			$bq =~ s/^[ \t]+$//mg;			# trim whitespace-only lines
-			$bq = _RunBlockGamut($bq);		# recurse
-
-			$bq =~ s/^/  /g;
-			# These leading spaces screw with <pre> content, so we need to fix that:
-			$bq =~ s{
-					(\s*<pre>.+?</pre>)
-				}{
-					my $pre = $1;
-					$pre =~ s/^  //mg;
-					$pre;
-				}egsx;
-
-			"<blockquote>\n$bq\n</blockquote>\n\n";
-		}egmx;
-
-
-	return $text;
-}
-
-
-sub _FormParagraphs {
-#
-#	Params:
-#		$text - string to process with html <p> tags
-#
-	my $text = shift;
-
-	# Strip leading and trailing lines:
-	$text =~ s/\A\n+//;
-	$text =~ s/\n+\z//;
-
-	my @grafs = split(/\n{2,}/, $text);
-
-	#
-	# Wrap <p> tags.
-	#
-	foreach (@grafs) {
-		unless (defined( $g_html_blocks{$_} )) {
-			$_ = _RunSpanGamut($_);
-			s/^([ \t]*)/<p>/;
-			$_ .= "</p>";
-		}
-	}
-
-	#
-	# Unhashify HTML blocks
-	#
-	foreach (@grafs) {
-		if (defined( $g_html_blocks{$_} )) {
-			$_ = $g_html_blocks{$_};
-		}
-	}
-
-	return join "\n\n", @grafs;
-}
-
-
-sub _EncodeAmpsAndAngles {
-# Smart processing for ampersands and angle brackets that need to be encoded.
-
-	my $text = shift;
-
-	# Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
-	#   http://bumppo.net/projects/amputator/
- 	$text =~ s/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/&amp;/g;
-
-	# Encode naked <'s
- 	$text =~ s{<(?![a-z/?\$!])}{&lt;}gi;
-
-	return $text;
-}
-
-
-sub _EncodeBackslashEscapes {
-#
-#   Parameter:  String.
-#   Returns:    The string, with after processing the following backslash
-#               escape sequences.
-#
-    local $_ = shift;
-
-    s! \\\\  !$g_escape_table{'\\'}!gx;		# Must process escaped backslashes first.
-    s! \\`   !$g_escape_table{'`'}!gx;
-    s! \\\*  !$g_escape_table{'*'}!gx;
-    s! \\_   !$g_escape_table{'_'}!gx;
-    s! \\\{  !$g_escape_table{'{'}!gx;
-    s! \\\}  !$g_escape_table{'}'}!gx;
-    s! \\\[  !$g_escape_table{'['}!gx;
-    s! \\\]  !$g_escape_table{']'}!gx;
-    s! \\\(  !$g_escape_table{'('}!gx;
-    s! \\\)  !$g_escape_table{')'}!gx;
-    s! \\>   !$g_escape_table{'>'}!gx;
-    s! \\\#  !$g_escape_table{'#'}!gx;
-    s! \\\+  !$g_escape_table{'+'}!gx;
-    s! \\\-  !$g_escape_table{'-'}!gx;
-    s! \\\.  !$g_escape_table{'.'}!gx;
-    s{ \\!  }{$g_escape_table{'!'}}gx;
-
-    return $_;
-}
-
-
-sub _DoAutoLinks {
-	my $text = shift;
-
-	$text =~ s{<((https?|ftp):[^'">\s]+)>}{<a href="$1">$1</a>}gi;
-
-	# Email addresses: <address@domain.foo>
-	$text =~ s{
-		<
-        (?:mailto:)?
-		(
-			[-.\w]+
-			\@
-			[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
-		)
-		>
-	}{
-		_EncodeEmailAddress( _UnescapeSpecialChars($1) );
-	}egix;
-
-	return $text;
-}
-
-
-sub _EncodeEmailAddress {
-#
-#	Input: an email address, e.g. "foo@example.com"
-#
-#	Output: the email address as a mailto link, with each character
-#		of the address encoded as either a decimal or hex entity, in
-#		the hopes of foiling most address harvesting spam bots. E.g.:
-#
-#	  <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
-#       x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
-#       &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
-#
-#	Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
-#	mailing list: <http://tinyurl.com/yu7ue>
-#
-
-	my $addr = shift;
-
-	srand;
-	my @encode = (
-		sub { '&#' .                 ord(shift)   . ';' },
-		sub { '&#x' . sprintf( "%X", ord(shift) ) . ';' },
-		sub {                            shift          },
-	);
-
-	$addr = "mailto:" . $addr;
-
-	$addr =~ s{(.)}{
-		my $char = $1;
-		if ( $char eq '@' ) {
-			# this *must* be encoded. I insist.
-			$char = $encode[int rand 1]->($char);
-		} elsif ( $char ne ':' ) {
-			# leave ':' alone (to spot mailto: later)
-			my $r = rand;
-			# roughly 10% raw, 45% hex, 45% dec
-			$char = (
-				$r > .9   ?  $encode[2]->($char)  :
-				$r < .45  ?  $encode[1]->($char)  :
-							 $encode[0]->($char)
-			);
-		}
-		$char;
-	}gex;
-
-	$addr = qq{<a href="$addr">$addr</a>};
-	$addr =~ s{">.+?:}{">}; # strip the mailto: from the visible part
-
-	return $addr;
-}
-
-
-sub _UnescapeSpecialChars {
-#
-# Swap back in all the special characters we've hidden.
-#
-	my $text = shift;
-
-	while( my($char, $hash) = each(%g_escape_table) ) {
-		$text =~ s/$hash/$char/g;
-	}
-    return $text;
-}
-
-
-sub _TokenizeHTML {
-#
-#   Parameter:  String containing HTML markup.
-#   Returns:    Reference to an array of the tokens comprising the input
-#               string. Each token is either a tag (possibly with nested,
-#               tags contained therein, such as <a href="<MTFoo>">, or a
-#               run of text between tags. Each element of the array is a
-#               two-element array; the first is either 'tag' or 'text';
-#               the second is the actual value.
-#
-#
-#   Derived from the _tokenize() subroutine from Brad Choate's MTRegex plugin.
-#       <http://www.bradchoate.com/past/mtregex.php>
-#
-
-    my $str = shift;
-    my $pos = 0;
-    my $len = length $str;
-    my @tokens;
-
-    my $depth = 6;
-    my $nested_tags = join('|', ('(?:<[a-z/!$](?:[^<>]') x $depth) . (')*>)' x  $depth);
-    my $match = qr/(?s: <! ( -- .*? -- \s* )+ > ) |  # comment
-                   (?s: <\? .*? \?> ) |              # processing instruction
-                   $nested_tags/ix;                   # nested tags
-
-    while ($str =~ m/($match)/g) {
-        my $whole_tag = $1;
-        my $sec_start = pos $str;
-        my $tag_start = $sec_start - length $whole_tag;
-        if ($pos < $tag_start) {
-            push @tokens, ['text', substr($str, $pos, $tag_start - $pos)];
-        }
-        push @tokens, ['tag', $whole_tag];
-        $pos = pos $str;
-    }
-    push @tokens, ['text', substr($str, $pos, $len - $pos)] if $pos < $len;
-    \@tokens;
-}
-
-
-sub _Outdent {
-#
-# Remove one level of line-leading tabs or spaces
-#
-	my $text = shift;
-
-	$text =~ s/^(\t|[ ]{1,$g_tab_width})//gm;
-	return $text;
-}
-
-
-sub _Detab {
-#
-# Cribbed from a post by Bart Lateur:
-# <http://www.nntp.perl.org/group/perl.macperl.anyperl/154>
-#
-	my $text = shift;
-
-	$text =~ s{(.*?)\t}{$1.(' ' x ($g_tab_width - length($1) % $g_tab_width))}ge;
-	return $text;
-}
-
-
-1;
-
-__END__
-
-
-=pod
-
-=head1 NAME
-
-B<Markdown>
-
-
-=head1 SYNOPSIS
-
-B<Markdown.pl> [ B<--html4tags> ] [ B<--version> ] [ B<-shortversion> ]
-    [ I<file> ... ]
-
-
-=head1 DESCRIPTION
-
-Markdown is a text-to-HTML filter; it translates an easy-to-read /
-easy-to-write structured text format into HTML. Markdown's text format
-is most similar to that of plain text email, and supports features such
-as headers, *emphasis*, code blocks, blockquotes, and links.
-
-Markdown's syntax is designed not as a generic markup language, but
-specifically to serve as a front-end to (X)HTML. You can  use span-level
-HTML tags anywhere in a Markdown document, and you can use block level
-HTML tags (like <div> and <table> as well).
-
-For more information about Markdown's syntax, see:
-
-    http://daringfireball.net/projects/markdown/
-
-
-=head1 OPTIONS
-
-Use "--" to end switch parsing. For example, to open a file named "-z", use:
-
-	Markdown.pl -- -z
-
-=over 4
-
-
-=item B<--html4tags>
-
-Use HTML 4 style for empty element tags, e.g.:
-
-    <br>
-
-instead of Markdown's default XHTML style tags, e.g.:
-
-    <br />
-
-
-=item B<-v>, B<--version>
-
-Display Markdown's version number and copyright information.
-
-
-=item B<-s>, B<--shortversion>
-
-Display the short-form version number.
-
-
-=back
-
-
-
-=head1 BUGS
-
-To file bug reports or feature requests (other than topics listed in the
-Caveats section above) please send email to:
-
-    support@daringfireball.net
-
-Please include with your report: (1) the example input; (2) the output
-you expected; (3) the output Markdown actually produced.
-
-
-=head1 VERSION HISTORY
-
-See the readme file for detailed release notes for this version.
-
-1.0.1 - 14 Dec 2004
-
-1.0 - 28 Aug 2004
-
-
-=head1 AUTHOR
-
-    John Gruber
-    http://daringfireball.net
-
-    PHP port and other contributions by Michel Fortin
-    http://michelf.com
-
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright (c) 2003-2004 John Gruber   
-<http://daringfireball.net/>   
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-* Redistributions of source code must retain the above copyright notice,
-  this list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-* Neither the name "Markdown" nor the names of its contributors may
-  be used to endorse or promote products derived from this software
-  without specific prior written permission.
-
-This software is provided by the copyright holders and contributors "as
-is" and any express or implied warranties, including, but not limited
-to, the implied warranties of merchantability and fitness for a
-particular purpose are disclaimed. In no event shall the copyright owner
-or contributors be liable for any direct, indirect, incidental, special,
-exemplary, or consequential damages (including, but not limited to,
-procurement of substitute goods or services; loss of use, data, or
-profits; or business interruption) however caused and on any theory of
-liability, whether in contract, strict liability, or tort (including
-negligence or otherwise) arising in any way out of the use of this
-software, even if advised of the possibility of such damage.
-
-=cut
--- a/bin/md_cache
+++ /dev/null
@@ -1,17 +1,0 @@
-#!/usr/bin/env rc
-. 9.rc
-
-cachedir=/tmp/md_cache
-mkdir -p $cachedir >[2]/dev/null
-
-tmpfile=$cachedir/mdtmp.$pid
-score=`{{tee $tmpfile || exit 1} | sha1sum}
-cachefile=$cachedir/$score
-
-if(test -f $cachefile)
-    cat $cachefile
-if not {
-    markdown.pl < $tmpfile | tee $cachefile.$pid
-    mv $cachefile.$pid $cachefile
-}
-rm $tmpfile >[2]/dev/null
--- /dev/null
+++ b/bin/md_cache.rc
@@ -1,0 +1,17 @@
+#!/usr/bin/env rc
+. 9.rc
+
+cachedir=/tmp/md_cache
+mkdir -p $cachedir >[2]/dev/null
+
+tmpfile=$cachedir/mdtmp.$pid
+score=`{{tee $tmpfile || exit 1} | sha1sum}
+cachefile=$cachedir/$score
+
+if(test -f $cachefile)
+    cat $cachefile
+if not {
+    markdown.pl < $tmpfile | tee $cachefile.$pid
+    mv $cachefile.$pid $cachefile
+}
+rm $tmpfile >[2]/dev/null
--- a/bin/urldecode.awk
+++ /dev/null
@@ -1,39 +1,0 @@
-#!/usr/bin/awk -f
-BEGIN {
-	hextab ["0"] = 0; hextab ["8"] = 8;
-	hextab ["1"] = 1; hextab ["9"] = 9;
-	hextab ["2"] = 2; hextab ["A"] = hextab ["a"] = 10
-	hextab ["3"] = 3; hextab ["B"] = hextab ["b"] = 11;
-	hextab ["4"] = 4; hextab ["C"] = hextab ["c"] = 12;
-	hextab ["5"] = 5; hextab ["D"] = hextab ["d"] = 13;
-	hextab ["6"] = 6; hextab ["E"] = hextab ["e"] = 14;
-	hextab ["7"] = 7; hextab ["F"] = hextab ["f"] = 15;
-}
-{
-	decoded = ""
-	i   = 1
-	len = length ($0)
-	while ( i <= len ) {
-		c = substr ($0, i, 1)
-		if ( c == "%" ) {
-			if ( i+2 <= len ) {
-				c1 = substr ($0, i+1, 1)
-				c2 = substr ($0, i+2, 1)
-				if ( hextab [c1] == "" || hextab [c2] == "" ) {
-					print "WARNING: invalid hex encoding: %" c1 c2 | "cat >&2"
-				} else {
-					code = 0 + hextab [c1] * 16 + hextab [c2] + 0
-					c = sprintf ("%c", code)
-					i = i + 2
-				}
-			} else {
-				print "WARNING: invalid % encoding: " substr ($0, i, len - i)
-			}
-		} else if ( c == "+" ) {
-			c = " "
-		}
-		decoded = decoded c
-		++i
-	}
-	print decoded
-}
--- /dev/null
+++ b/bin/werc.rc
@@ -1,0 +1,322 @@
+#!/usr/local/plan9/bin/rc
+. ./cgilib.rc
+cd ..
+
+forbidden_uri_chars='[^a-zA-Z0-9_+\-\/\.]'
+response_format=html
+
+fn get_lib_file {
+    wantedfile = $1
+    if (test -f $sitedir/_werc/lib/$wantedfile)
+    	echo -n $sitedir/_werc/lib/$wantedfile
+    if not if(! ~ $#masterSite 0 && test -f sites/$masterSite/_werc/lib/$wantedfile)
+    	echo -n       sites/$masterSite/_werc/lib/$wantedfile
+    if not if (test -f lib/$wantedfile)
+    	echo -n        lib/$wantedfile
+}
+
+# Title
+fn gentitle {
+    echo '<h1 class="headerTitle"><a href="/">' ^ $"siteTitle ^ ' <span id="headerSubTitle">' ^ $"siteSubTitle ^ '</span></a></h1>'
+}
+
+# Don't change var name or trailing ';', some dirs change the filter!
+dirfilter = '/\/[._]/d; /'^$forbidden_uri_chars^'/d; /\/robots.\txt$/d; /\/sitemap\.txt$/d; s,^\./,,; s,\.md$,,; s,\.html,,; s,\.txt,,; '
+
+# To be used from config files
+fn hide_paths {
+    for(i in $*)
+       dirfilter = $dirfilter^'/^'$i'$/d; '
+}
+
+# Sidebar 
+fn menu {
+    ls -F $1 | sed $dirfilter | awk -F/ '
+    BEGIN { print "<ul class=\"side-bar\">" }
+    END { print "</ul>" }
+    # Should add \. to the list of allowed chars in file names, but need to find a way to filter out .txt and so on
+    /^([a-zA-Z0-9+_\-]+[\/*]?)+$/ && $NF != "index" {
+        isdir = match($0, "/$")
+        sub("[*/]$", "")
+
+        path = bname = $0
+        sub("^(.*/)?([0-9]+_)?", "", bname)
+        gsub("_", " ", bname)
+
+        if(isdir) {
+            bname = bname "/"
+            path = $0 "/"
+        }
+
+        if(index(ENVIRON["REQUEST_URI"] "/", "/" path) == 1) {
+            if(isdir) {
+                print "<li><a href=\"/" path "\" class=\"thisPage\">&raquo;<i> " bname "</i></a>"
+                system("rc -c ''menu " path "''")
+            } else {
+                print "<li><a href=\"/" path "\" class=\"thisPage\">&raquo;<i> " bname "</i></a>"
+            }
+        } else 
+            print "<li><a href=\"/" path "\">&rsaquo; " bname "</a>"
+
+        print "</li>"
+    }'
+}
+
+fn gensidebar {
+    @{
+        cd $sitedir
+        menu .
+    }
+}
+
+fn sortedBlogPostList {
+    # the /./ is added so we can sort -t. and order only the file name
+    if (! ~ $#* 0)
+        ls $*^'/./' | grep '/[0-9]+.*\.md$'| sort -r -t. +1
+}
+
+fn gen_blog_post_title {
+    title=`{basename $1 | sed 's/^[0-9\-]*_(.*)\.md$/\1/; s/_/ /g' }
+    permlink= `{echo $1 | sed 's,^/[a-z/]*www/,/,; s,^sites/[^/]*/*/,/,; s/\.md$//' }
+    du=`{ls -l $1}
+    by = ''
+    if (! ~ $#blogAuthor 0)
+        by='By '$"blogAuthor
+    if not if (~ $#blogDirs 1)
+        by='By '$du(4)
+    echo '##<a href="'^$"permlink^'">' $"title^'</a> *( '$by Last mod: $du(7 8 9) ' )*'
+}
+
+
+
+# Handlers
+fn set_handler {
+    handler = $1
+    shift
+    handler_args = $*
+}
+
+fn md_handler { cat $* | $formatter }
+
+fn tpl_handler { template $1 }
+
+fn html_handler {
+    cat $1 | /bin/sed '0,/<[Bb][Oo][Dd][Yy][^>]*>/d; /<\/[Bb][Oo][Dd][Yy]>/,$d' 
+}
+
+fn txt_handler {
+    echo '<pre>'
+    # XXX Insering a blank line between lines in input is good for fortunes.txt, but maybe not for other .txt files
+    # XXX Words are not broken, even if they are way beyond 82 chars long
+    cat $1 |sed 's/$/\n/g; s/</\&lt;/g; s/>/\&gt;/g' |fmt -l 82 -j
+    echo '</pre>'
+}
+
+fn dir_listing_handler {
+    d = `{basename -d $1}
+    echo '<h1 style="text-transform: capitalize;">' `{echo $d|sed -e 's,.*//,,g' -e 's,/$,,' -e 's,/, / ,g' } '</h1>'
+    echo '<ul style="text-transform: capitalize;">'
+    ls -F $d | sed -e $dirfilter' s,^'$sitedir'/.*/([^$].*),<li><a href="\1">\1</a></li>,' # XXX I'm not sure what the [^$] does there
+    echo '</ul>'
+}
+
+fn 404_handler { template `{ get_lib_file 404.tpl } }
+
+fn blog_dir_handler {
+    blogDirs = $*
+    tpl_handler lib/feeds/html.tpl
+}
+
+fn blog_post_handler {
+    gen_blog_post_title $1 | $formatter
+    $formatter < $1
+}
+
+
+fn select_handler {
+
+    if (test -f $body.md) {
+        if (! ~ $#inBlog 0)
+            set_handler blog_post_handler $body.md
+        if not
+            set_handler md_handler $body.md
+        if (! ~ $#allowComments 0 && ~ $REQUEST_METHOD POST) {
+            get_post_args comment_text
+            d = `{date -n} # FIXME Obvious race
+            d = $body.md_werc/comments/$d/
+
+            u = $logged_user
+            if (~ $#logged_user 0) {
+                get_post_args comment_user_name comment_user_password
+                # TODO Should take this path too if the user can login but doesn't
+                # belong to required group
+                if (! login_user $comment_user_name $comment_user_password) {
+                    u = $comment_user_name ':' $comment_user_password
+                    d = $d^'_pending'
+                }
+                if not
+                    u = $logged_user
+            }
+
+            umask 002
+            mkdir -m 775 -p $d
+            echo $u > $d/user
+            echo $comment_text > $d/body
+        }
+    }
+    if not if (test -f $body.tpl)
+        set_handler tpl_handler $body.tpl
+
+    if not if (test -f $body.html)
+        set_handler html_handler $body.html
+
+    # Explicit .html urls, unfortunately usually the web server will handle this as static files
+    if not if (~ $body *.html && test -f $body)
+        perm_redirect `{ echo $REQUEST_URI|sed 's/.html$//' }
+
+    # Rss feeds. TODO: we should check that the request is for a real blog dir
+    if not if (~ $REQUEST_URI */index.rss) {
+        uri = `{echo $uri | sed 's/index.rss$//'}
+        if (~ $#blogDirs 0)
+            blogDirs = $sitedir^$uri
+        uri=$baseuri$"uri
+        master_template=feeds/rss20.tpl
+        response_format=raw
+    }
+
+    # Blog handler
+    if not if (~ $body */[bB]log/index */[bB]log//index || ! ~ $#blogDirs 0) {
+        if (~ $#blogDirs 0)
+            blogDirs = `{basename -d $body}
+
+        rssuri=`{basename -d $uri}
+        rssuri=$baseuri`{cleanname $"rssuri^/index.rss}
+        extraHeaders=$"extraHeaders ^ \
+    	'<link rel="alternate" type="application/rss+xml" title="RSS" href="'$rssuri'" />
+'
+        set_handler tpl_handler lib/feeds/html.tpl
+    }
+
+    # Global tpl (eg sitemap.tpl)
+    if not if (test -f lib/^$uri^.tpl)
+        set_handler tpl_handler lib/^$uri^.tpl
+
+    if not if (test -f $body.txt)
+        set_handler txt_handler $body.txt
+
+    # Dir listing
+    if not if(~ $body */index)
+        set_handler dir_listing_handler $body
+
+    # File not found
+    if not {
+        set_handler 404_handler $body
+        dprint 'NOT FOUND: '$SERVER_NAME^$REQUEST_URI^' - '^$"HTTP_REFERER^' - '^$"HTTP_USER_AGENT
+        echo 'Status: 404 Not Found'
+    }
+
+}
+
+
+fn genbody {
+    $handler $handler_args
+}
+
+
+# Careful, the proper p9p path might not be set until initrc.local is sourced
+path=(. $PLAN9/bin ./bin/ /bin/ /usr/bin) 
+
+site=$SERVER_NAME
+headers=lib/headers.tpl
+master_template=default_master.tpl
+sidebar=sidebar
+baseuri=http://$site/
+for(i in siteTitle siteSubTitle pageTitle extraHeaders)
+    $i = ''
+
+. etc/initrc
+
+if(test -f etc/initrc.local)
+    . etc/initrc.local
+
+
+# Parse request URL
+uri = `{echo -n $REQUEST_URI | sed -e 's/\?.*//; s/'^$forbidden_uri_chars^'//g; s/\.\.*/./g;' -e '1q'}
+ifs='/' { args = `{echo -n $uri} }
+
+
+if(! ~ $#debug 0)
+    dprint '  ' $SERVER_NAME^$REQUEST_URI^' - '^$"HTTP_USER_AGENT
+
+# Hack: preload post data so we can access it from templates where cgi's stdin is not accesible
+if(~ $REQUEST_METHOD POST) {
+    get_post_args 
+    login_user
+}
+
+if (! ~ $args '') {
+    if (~ $args($#args) 'index')
+        perm_redirect `{ echo $REQUEST_URI | sed 's,/index$,/,' }
+        
+    pageTitle=`{echo $args | sed -e 's/ / - /g' -e 's/_/ /g'}
+    req_path=$uri
+}
+if not {
+    req_path='/'
+}
+
+fpath=$sitedir
+for (i in ('' $args)) {
+    fpath = $fpath/$i
+    # We don't want blog settings to cascade into posts, note that we are inBlog instead
+    if (! ~ $#blogDirs 0 && ! ~ $req_path */index.rss */[bB]log */[bB]log/) {
+        inBlog = $blogDirs
+        blogDirs = () 
+    }
+
+    if (test -f $fpath/_werc/config)
+        . $fpath/_werc/config
+
+    if (~ $#blogDirs 0 && ~ $#inBlog 0 && ~ $i [Bb]log)
+        inBlog = 'yes'
+}
+
+# Redirections and other preprocessing
+if (~ $#redirectPermanent 1)
+    perm_redirect $"redirectPermanent
+
+if not if (~ $#redirectPermanent 2 && {echo $SERVER_NAME^$REQUEST_URI|grep -s $redirectPermanent(1) }) {
+    # Experimental regexp sub-based redirect, probably should find a nicer interface
+    # For now only used at sites/harmful.cat-v.org/software/OO_programming/_werc/config
+    to=`{echo $SERVER_NAME^$REQUEST_URI|sed 's@'^$redirectPermanent(1)^'@'^$redirectPermanent(2)^'@'}
+    if(! ~ $to $REQUEST_URI)
+        perm_redirect $to
+}
+
+# Set Page title
+if(~ $pageTitle '')
+	pageTitle=$siteTitle^' '^$siteSubTitle
+if not
+	pageTitle=$"pageTitle^' | '^$"siteTitle^' '^$"siteSubTitle
+
+
+body=$sitedir/$req_path
+if (test -d $body) {
+    if(! ~ $body */)
+    	perm_redirect $REQUEST_URI^'/'
+    body=$body/index
+}
+
+select_handler
+
+# Template/body selection
+master_template= `{get_lib_file $master_template}
+
+if(~ $response_format html) {
+    # Is awk_buffer really useful?
+	template $headers $master_template | awk_buffer
+	echo '</body></html>'
+}
+if not if (~ $response_format raw) {
+	template < $master_template 
+}