]> git.mdlowis.com Git - projs/mdlowis.com.git/commitdiff
Update to custom scripts
authorMichael D. Lowis <mike@mdlowis.com>
Mon, 10 Aug 2015 02:04:12 +0000 (22:04 -0400)
committerMichael D. Lowis <mike@mdlowis.com>
Mon, 10 Aug 2015 02:04:12 +0000 (22:04 -0400)
20 files changed:
Makefile
config.sh [new file with mode: 0644]
index.html [changed from symlink to file mode: 0644]
pages.static/index.html [deleted file]
pages/home.md [new file with mode: 0644]
pages/index.md [deleted file]
scripts/Markdown.pl [deleted file]
scripts/sw [deleted file]
scripts/whereis [deleted file]
site/home.html [new file with mode: 0644]
site/logo.png [new file with mode: 0644]
site/style.css [new file with mode: 0644]
site/style2.css [new file with mode: 0644]
style.css [deleted file]
sw.conf [deleted file]
templates/page.html [new file with mode: 0644]
tools/genpage [new file with mode: 0755]
tools/md2html.awk [moved from scripts/md2html.awk with 100% similarity]
tools/mo [new file with mode: 0755]
tools/serve [new file with mode: 0755]

index bd207526593a00e4c11c3d6bf62237ae76ae2148..11d470a8705fbabf15e5fbd9a55cf45eb074434c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,25 +1,15 @@
-export PATH := $(PWD)/scripts/:$(PATH)
 
-all:
-       $(PWD)/scripts/sw ./pages
+PATH := $(PWD)/tools:$(PATH)
+PAGES = $(addprefix site/,$(addsuffix .html,$(basename $(notdir $(wildcard pages/*.md)))))
 
-clean:
-       rm -rf site.static
+.PHONY: serve
+all: $(PAGES)
+
+serve:
+       cd site && serve
 
-## sw - suckless webframework - 2012 - MIT License - nibble <develsec.org>
-#
-#DESTDIR?=
-#PREFIX?=/usr/local
-#P=${DESTDIR}/${PREFIX}
-#
-#all: sw.conf
-#
-#sw.conf:
-#      cp sw.conf.def sw.conf
-#
-#install:
-#      mkdir -p ${P}/bin
-#      sed -e "s,/usr/bin/awk,`./whereis awk`,g" md2html.awk > ${P}/bin/md2html.awk
-#      chmod +x ${P}/bin/md2html.awk
-#      cp -f sw ${P}/bin/sw
-#      chmod +x ${P}/bin/sw
+site/%.html : pages/%.md
+       genpage $< > $@
+
+clean:
+       rm site/*.html
diff --git a/config.sh b/config.sh
new file mode 100644 (file)
index 0000000..79a54d9
--- /dev/null
+++ b/config.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+export TITLE="Simplicity is the ultimate sophistication"
+export SUBTITLE=$TITLE
+export COPYRIGHT="© 2015 Michael D. Lowis"
+
deleted file mode 120000 (symlink)
index 5bd8a1f338180de6de6d09152a4588e4c89bb7ef..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-pages.static/index.html
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..0d61a830d9698d0bf03e04a7f578553decb958fa
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <meta charset="utf-8">
+    </head>
+    <body>
+        <script>
+            window.location = location.host + "/site/home.html";
+        </script>
+    </body>
+</html>
diff --git a/pages.static/index.html b/pages.static/index.html
deleted file mode 100644 (file)
index a03d4ae..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-<!doctype html>
-<html>
-<head>
-<title>mdlowis</title>
-<link rel="icon" href="/favicon.png" type="image/png">
-<meta charset="UTF-8">
-<style type="text/css">
-/* Based on werc.cat-v.org, suckless.org and garbe.us */
-
-/* General */
-body {
-       color: #aaa;
-       font-family: sans-serif;
-       font-size: 80%;
-       margin: 0;
-       padding: 0
-}
-
-/* Header */
-.header { background-color: #ccc; border: 0; }
-.header a { border: 0; color: #111; text-decoration: none; }
-.midHeader img { border: 0; }
-
-.headerTitle { font-size: 1.6em; font-weight: bold; margin: 0 0 0 0.5em; padding: 0.5em; }
-.headerTitle a { border: 0; text-decoration: none; }
-
-.headerSubTitle { font-size: 0.6em; font-weight: normal; margin-left: 1em; }
-
-/* Side */
-#side-bar {
-       width: 100%;
-       clear: both;
-       border: 0;
-       margin: 0;
-       padding: 0;
-       background-color: #ddd;
-}
-
-#side-bar ul {
-       margin: 0;
-       padding: 0.3em;
-       list-style-type: none;
-       list-style-image: none;
-       border: 0;
-}
-
-#side-bar li { display: inline; line-height: 1.6em; white-space: nowrap; }
-
-#side-bar ul li a {
-       margin: 0;
-       padding: 0.1em 1ex 0.1em 1ex;
-       color: #336699;
-       background-color: transparent;
-       text-decoration: none;
-       font-size: 1em;
-       border: 0;
-}
-
-#side-bar ul li a:hover { color: #111; text-decoration: none; }
-/* Main Copy */
-#main {
-       max-width: 70em;
-       color: #111;
-       margin: 0 auto 0 2em;
-       padding: 1em 3em 2em 1em;
-       border: 0;
-}
-
-#main a { color: #336699; text-decoration: none; }
-#main a:hover { text-decoration: underline; }
-#main h1, #main-copy h2 { color: #666; }
-#main ul { list-style-type: square; }
-
-/* Footer */
-#footer {
-       background-color: #ddd;
-       color: #111;
-       font-size: 91%;
-       padding: 2em;
-       clear: both;
-}
-
-#footer .left { text-align: left; float: left; clear: left; }
-#footer .right { text-align: right; }
-#footer a { color: #111; text-decoration: none; }
-#footer a:hover { text-decoration: underline; }
-
-abbr, acronym { border-bottom: 1px dotted #333; cursor: help; }
-blockquote { border-left: 1px solid #333; font-style: italic; padding: 1em; }
-hr { border-width: 0 0 0.1em 0; border-color: #666; }
-
-code, pre { font-size: 1.1em }
-pre { margin-left: 2em; }
-</style>
-</head>
-<body>
-<div class="header">
-<h1 class="headerTitle">
-<a href="index.html">mdlowis</a> <span class="headerSubtitle"></span>
-</h1>
-</div>
-<div id="side-bar">
-<ul>
-</ul>
-</div>
-<div id="main">
-<h1>Hello</h1>
-
-<p>aslkdjalksjdlakjsdlakjsdlkjas</p>
-</div>
-<div id="footer">
-<div class="right"><a href="https://github.com/jroimartin/sw">Powered by sw</a></div>
-</div>
-</body>
-</html>
diff --git a/pages/home.md b/pages/home.md
new file mode 100644 (file)
index 0000000..80b506c
--- /dev/null
@@ -0,0 +1,18 @@
+TYPE page
+---
+# Header 1
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed accumsan sapien. Phasellus consequat metus sit amet est imperdiet vestibulum. Morbi vitae placerat mauris. Etiam ac mauris finibus, sagittis augue vitae, tempus dolor. Mauris egestas maximus urna eu fringilla. Pellentesque et pulvinar mi. Phasellus massa ipsum, mattis vel ipsum dictum, pretium dignissim dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut mattis risus elit, sit amet commodo lacus tincidunt eget. Phasellus arcu lorem, consequat ut augue sed, iaculis lobortis ex. Integer non sem tincidunt urna aliquam tempor eget sed libero. Aliquam hendrerit velit quis augue ullamcorper, quis maximus sapien volutpat. In hac habitasse platea dictumst. Aenean tincidunt, odio at luctus suscipit, justo mauris faucibus turpis, et posuere enim nibh in nulla. Nam in lorem vel sem vehicula consectetur a et tortor.
+
+## Header 2
+
+Vivamus a metus elit. Aliquam tincidunt, magna nec rutrum interdum, ipsum elit volutpat dui, ut sagittis erat justo pretium metus. Curabitur eget libero nisi. Phasellus et placerat sem. Ut venenatis, erat malesuada vulputate auctor, turpis sem vehicula arcu, non euismod massa augue vehicula tellus. Aenean placerat dolor et turpis luctus convallis. Phasellus lectus purus, cursus at fermentum dapibus, tincidunt et dolor. Sed ullamcorper eleifend justo nec maximus. Integer elementum, nisi ac eleifend congue, orci lorem varius sapien, vitae tincidunt ipsum purus eget dolor. Nulla vel elit tristique, sodales purus ut, sollicitudin felis. Phasellus porttitor eros vel dolor maximus, at iaculis diam interdum.
+
+### Header 3
+
+Cras interdum sodales nisl, sed iaculis orci venenatis ac. Duis vel finibus risus, ac tincidunt elit. Vivamus ut lectus bibendum, consectetur nisi eget, tincidunt mi. Phasellus vulputate urna in lacus cursus, eu condimentum ligula porta. Phasellus posuere condimentum diam, id luctus nibh. Mauris at sodales odio. Vivamus tincidunt a ante a rhoncus. Sed augue turpis, lobortis ut placerat sed, auctor nec lectus. Ut elementum cursus est, sed tristique ante malesuada nec. Maecenas vitae mattis urna, vel commodo dolor. Sed eu efficitur augue. Cras enim velit, fermentum et nibh sed, pharetra pulvinar sem. Sed quis ullamcorper dolor. Donec dictum imperdiet sapien vitae mattis. Mauris sit amet leo vitae purus sodales posuere eget id dui.
+
+#### Header 4
+
+Nullam vulputate, quam sed congue convallis, arcu lectus feugiat nibh, eget efficitur nunc tortor a velit. Aliquam ultrices tellus quis mollis lobortis. Cras et dolor porttitor nisi suscipit venenatis vel vel sem. Mauris tristique lectus quis dolor mattis, rhoncus scelerisque tortor pellentesque. Sed eu tincidunt urna. Morbi a euismod mauris. Nunc nec nibh et augue tristique mattis et eget augue. Mauris lobortis, felis in fringilla euismod, sapien libero consequat dui, id maximus nisi eros quis turpis. Quisque nec lobortis augue, sed rutrum sem. Ut ultricies pellentesque felis, a luctus nulla finibus ut. Morbi eu nunc sapien. Etiam scelerisque ex ut lacus gravida, sed consectetur mauris suscipit. Aliquam sed cursus urna, sit amet efficitur eros. Mauris in metus vitae augue aliquet convallis. Quisque eget odio egestas, gravida nisl at, accumsan mauris.
+
diff --git a/pages/index.md b/pages/index.md
deleted file mode 100644 (file)
index 73c1069..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-# Hello
-aslkdjalksjdlakjsdlakjsdlkjas
diff --git a/scripts/Markdown.pl b/scripts/Markdown.pl
deleted file mode 100755 (executable)
index e4c8469..0000000
+++ /dev/null
@@ -1,1450 +0,0 @@
-#!/usr/bin/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
diff --git a/scripts/sw b/scripts/sw
deleted file mode 100755 (executable)
index ed28413..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-#!/bin/sh
-# sw - suckless webframework - 2012 - MIT License - nibble <develsec.org>
-
-sw_filter() {
-    for b in $BL; do
-        [ "$b" = "$1" ] && return 0
-    done
-}
-
-sw_main() {
-    $MDHANDLER $1
-}
-
-sw_menu() {
-    echo "<ul>"
-    [ -z "`echo $1 | grep index.md`" ] && echo "<li><a href=\"index.html\">.</a></li>"
-    [ "`dirname $1`" != "." ] && echo "<li><a href=\"../index.html\">..</a></li>"
-    FILES=`ls \`dirname $1\` | sed -e 's,.md$,.html,g'`
-    for i in $FILES ; do
-        sw_filter $i && continue
-        NAME=`echo $i | sed -e 's/\..*$//' -e 's/_/ /g'`
-        [ -z "`echo $i | grep '\..*$'`" ] && i="$i/index.html"
-        echo "<li><a href=\"$i\">$NAME</a></li>"
-    done
-    echo "</ul>"
-}
-
-sw_page() {
-    # Header
-    cat << _header_
-<!doctype html>
-<html>
-<head>
-<title>${TITLE}</title>
-<link rel="icon" href="/favicon.png" type="image/png">
-<meta charset="UTF-8">
-_header_
-    # Stylesheet
-    sw_style
-    cat << _header_
-</head>
-<body>
-<div class="header">
-<h1 class="headerTitle">
-<a href="`echo $1 | sed -e 's,[^/]*/,../,g' -e 's,[^/]*.md$,index.html,g'`">${TITLE}</a> <span class="headerSubtitle">${SUBTITLE}</span>
-</h1>
-</div>
-_header_
-    # Menu
-    echo "<div id=\"side-bar\">"
-    sw_menu $1
-    echo "</div>"
-    # Body
-    echo "<div id=\"main\">"
-    sw_main $1
-    echo "</div>"
-    # Footer
-    cat << _footer_
-<div id="footer">
-<div class="right"><a href="https://github.com/jroimartin/sw">Powered by sw</a></div>
-</div>
-</body>
-</html>
-_footer_
-}
-
-sw_style() {
-    if [ -f $CDIR/$STYLE ]; then
-        echo '<style type="text/css">'
-        cat $CDIR/$STYLE
-        echo '</style>'
-    fi
-}
-
-# Set input dir
-IDIR="`echo $1 | sed -e 's,/*$,,'`"
-if [ -z "$IDIR" ] || [ ! -d $IDIR ]; then
-    echo "Usage: sw [dir]"
-    exit 1
-fi
-
-# Load config file
-if [ ! -f $PWD/sw.conf ]; then
-    echo "Cannot find sw.conf in current directory"
-    exit 1
-fi
-. $PWD/sw.conf
-
-# Setup output dir structure
-CDIR=$PWD
-ODIR="$CDIR/`basename $IDIR`.static"
-rm -rf $ODIR
-mkdir -p $ODIR
-cp -rf $IDIR/* $ODIR
-rm -f `find $ODIR -type f -iname '*.md'`
-
-# Parse files
-cd $IDIR
-FILES=`find . -iname '*.md' | sed -e 's,^\./,,'`
-for a in $FILES; do
-    b="$ODIR/`echo $a | sed -e 's,.md$,.html,g'`"
-    echo "* $a"
-    sw_page $a > $b;
-done
-
-exit 0
diff --git a/scripts/whereis b/scripts/whereis
deleted file mode 100755 (executable)
index 9cf5412..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-[ -z "$1" ] && exit 1
-IFS=:
-for a in $PATH ; do
-  if [ -e "$a/$1" ]; then
-    echo "$a/$1"
-    break
-  fi
-done
diff --git a/site/home.html b/site/home.html
new file mode 100644 (file)
index 0000000..402e15c
--- /dev/null
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<!-- Site design *heavily* based on the excellent design of http://suckless.org/ -->
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Simplicity is the ultimate sophistication</title>
+    <link rel="shortcut icon" type="image/x-icon" href="logo.png" />
+    <link rel="stylesheet" type="text/css" href="style.css">
+    <meta charset="utf-8">
+</head>
+<body>
+    <div class="header">
+        <a href="index.html"><img src="logo.png"></a>
+        <a class="headerLink" href="index.html">mdlowis</a>
+        <span class="headerSubtitle">Simplicity is the ultimate sophistication</span>
+    </div>
+
+    <div class="menu">
+        <span class="left">
+            <a class="current" href="index.html">home</a>
+            <a href="">articles</a>
+            <a href="">projects</a>
+            <a href="">prototypes</a>
+        </span>
+        <!--
+        <span class="right">
+            <a href="">download</a>
+            <a href="">source</a>
+        </span>
+        -->
+    </div>
+
+    <div class="content">
+        <div class="nav">
+            <ul>
+                <li><a href="" class="thisPage">link1</a></li>
+                <li><a href="">link2</a></li>
+                <li><a href="">link3</a></li>
+            </ul>
+        </div>
+
+        <div class="main">
+            <h1>Header 1</h1>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sed accumsan sapien. Phasellus consequat metus sit amet est imperdiet vestibulum. Morbi vitae placerat mauris. Etiam ac mauris finibus, sagittis augue vitae, tempus dolor. Mauris egestas maximus urna eu fringilla. Pellentesque et pulvinar mi. Phasellus massa ipsum, mattis vel ipsum dictum, pretium dignissim dui. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut mattis risus elit, sit amet commodo lacus tincidunt eget. Phasellus arcu lorem, consequat ut augue sed, iaculis lobortis ex. Integer non sem tincidunt urna aliquam tempor eget sed libero. Aliquam hendrerit velit quis augue ullamcorper, quis maximus sapien volutpat. In hac habitasse platea dictumst. Aenean tincidunt, odio at luctus suscipit, justo mauris faucibus turpis, et posuere enim nibh in nulla. Nam in lorem vel sem vehicula consectetur a et tortor.</p>
+<h2>Header 2</h2>
+<p>Vivamus a metus elit. Aliquam tincidunt, magna nec rutrum interdum, ipsum elit volutpat dui, ut sagittis erat justo pretium metus. Curabitur eget libero nisi. Phasellus et placerat sem. Ut venenatis, erat malesuada vulputate auctor, turpis sem vehicula arcu, non euismod massa augue vehicula tellus. Aenean placerat dolor et turpis luctus convallis. Phasellus lectus purus, cursus at fermentum dapibus, tincidunt et dolor. Sed ullamcorper eleifend justo nec maximus. Integer elementum, nisi ac eleifend congue, orci lorem varius sapien, vitae tincidunt ipsum purus eget dolor. Nulla vel elit tristique, sodales purus ut, sollicitudin felis. Phasellus porttitor eros vel dolor maximus, at iaculis diam interdum.</p>
+<h3>Header 3</h3>
+<p>Cras interdum sodales nisl, sed iaculis orci venenatis ac. Duis vel finibus risus, ac tincidunt elit. Vivamus ut lectus bibendum, consectetur nisi eget, tincidunt mi. Phasellus vulputate urna in lacus cursus, eu condimentum ligula porta. Phasellus posuere condimentum diam, id luctus nibh. Mauris at sodales odio. Vivamus tincidunt a ante a rhoncus. Sed augue turpis, lobortis ut placerat sed, auctor nec lectus. Ut elementum cursus est, sed tristique ante malesuada nec. Maecenas vitae mattis urna, vel commodo dolor. Sed eu efficitur augue. Cras enim velit, fermentum et nibh sed, pharetra pulvinar sem. Sed quis ullamcorper dolor. Donec dictum imperdiet sapien vitae mattis. Mauris sit amet leo vitae purus sodales posuere eget id dui.</p>
+<h4>Header 4</h4>
+<p>Nullam vulputate, quam sed congue convallis, arcu lectus feugiat nibh, eget efficitur nunc tortor a velit. Aliquam ultrices tellus quis mollis lobortis. Cras et dolor porttitor nisi suscipit venenatis vel vel sem. Mauris tristique lectus quis dolor mattis, rhoncus scelerisque tortor pellentesque. Sed eu tincidunt urna. Morbi a euismod mauris. Nunc nec nibh et augue tristique mattis et eget augue. Mauris lobortis, felis in fringilla euismod, sapien libero consequat dui, id maximus nisi eros quis turpis. Quisque nec lobortis augue, sed rutrum sem. Ut ultricies pellentesque felis, a luctus nulla finibus ut. Morbi eu nunc sapien. Etiam scelerisque ex ut lacus gravida, sed consectetur mauris suscipit. Aliquam sed cursus urna, sit amet efficitur eros. Mauris in metus vitae augue aliquet convallis. Quisque eget odio egestas, gravida nisl at, accumsan mauris.</p>
+        </div>
+    </div>
+
+    <div class="footer">
+        <span class="right">© 2015 Michael D. Lowis</span>
+    </div>
+</body><div></div></html>
diff --git a/site/logo.png b/site/logo.png
new file mode 100644 (file)
index 0000000..445b402
Binary files /dev/null and b/site/logo.png differ
diff --git a/site/style.css b/site/style.css
new file mode 100644 (file)
index 0000000..dc83528
--- /dev/null
@@ -0,0 +1,117 @@
+/** Base Tag Styling */
+body {
+    background-color: #eeeeee;
+    font-family: sans-serif;
+    padding: 0;
+    margin: 0;
+}
+
+h1, h2, h3, h4 { margin: 1em 1ex 0.5ex 0; }
+
+h1 { font-size: 1.4em; }
+
+h2 { font-size: 1.3em; }
+
+h3 { font-size: 1.2em; }
+
+h4 { font-size: 1.1em; }
+
+a {
+    color: #005386;
+    text-decoration: none;
+}
+
+.left { float: left; }
+
+.right { float: right; }
+
+/** Header Styling */
+.header {
+    color: #555555;
+    font-size: 1.78em;
+    padding: 0.7ex 0.7ex 0.7ex 0.7em;
+}
+
+.headerLink {
+    color: #1177aa;
+    margin-left: 5px;
+}
+
+.headerSubtitle {
+    font-size: 0.75em;
+    font-style: italic;
+    margin-left: 1em;
+}
+
+/** Menu Styling */
+.menu {
+    overflow: hidden;
+    background-color: #1177aa;
+    padding: 0.7ex;
+    border-top: 1px solid black;
+    border-bottom: 1px solid black;
+}
+
+.menu a {
+    padding: 0.5ex 1ex 0.5ex 1ex;
+    color: #fff;
+}
+
+.menu a:hover { background-color: #006699; }
+
+.menu a.current { font-weight: bold; }
+
+/** Main Page Content */
+.content { background-color: #fff; }
+
+/** Navigation Bar */
+.nav {
+    background-color: #fff;
+    float: left;
+    margin: 0 1px 0 0;
+    padding: 1em 0;
+    width: 200px;
+}
+
+.nav ul {
+    margin: 0;
+    padding: 0;
+}
+
+.nav li {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+}
+
+.nav li ul {
+    padding-left: 0.6em !important;
+}
+
+.nav li a {
+    display: block;
+    margin: 0;
+    padding: 0.8ex 2em 0.8ex 1em;
+}
+
+.nav li a.thisPage {
+    color: black;
+    font-weight: bold;
+}
+
+/** Article/Page Text */
+.main {
+    max-width: 50em;
+    margin: 0 0 0 200px;
+    padding: 1.5em;
+    border-left: 1px solid #cccccc;
+}
+
+/** Page Footer */
+.footer {
+    color: #666666;
+    padding: 1em;
+    margin: 0 0 1.5em 0;
+    border-top: 1px solid #cccccc;
+}
+
diff --git a/site/style2.css b/site/style2.css
new file mode 100644 (file)
index 0000000..fd958a8
--- /dev/null
@@ -0,0 +1,169 @@
+/**
+*  * Content Styling
+*   **/
+body {
+background-color: #eee;
+color: #333;
+font-family: sans-serif;
+padding: 0;
+margin: 0;
+}
+
+h1, h2, h3, h4 { margin: 1em 1ex 0.5ex 0; }
+
+h1 { font-size: 1.4em; }
+
+h2 { font-size: 1.3em; }
+
+h3 { font-size: 1.2em; }
+
+h4 { font-size: 1.1em; }
+
+a {
+color: #005386;
+text-decoration: none;
+}
+
+a:hover {
+background-color: #eeeeee;
+}
+
+.left {
+float: left;
+margin: 0;
+padding: 0;
+}
+
+.right {
+float: right;
+margin: 0;
+padding: 0;
+}
+
+/**
+*  * Header Styling
+*   **/
+.header {
+clear: both;
+color: #333;
+font-size: 1.78em;
+padding: 0.7ex 0.7ex 0.7ex 0.7em;
+}
+
+.headerLink {
+color: #aa1111;
+margin-left: 5px;
+}
+
+.headerSubtitle {
+font-size: 0.75em;
+font-style: italic;
+margin-left: 1em;
+}
+
+/**
+*  * Menu Styling
+*   **/
+.menu {
+clear: both;
+overflow: hidden;
+background-color: #a11;
+padding: 0.7ex;
+font-size: 94%;
+/*
+*     border-top: 2px solid #0f6793;
+*         border-bottom: 2px solid #0f6793;
+*         */
+border-top: 2px solid black;
+border-bottom: 2px solid black;
+}
+
+.menu a {
+padding: 0.5ex 1ex 0.5ex 1ex;
+color: #fff;
+vertical-align: middle;
+}
+
+.menu a:hover {
+background-color: #f00;
+vertical-align: middle;
+}
+
+.menu a.current {
+font-weight: bold;
+vertical-align: middle;
+}
+
+/**
+*  * Main Page Content
+*   **/
+.content {
+clear: both;
+margin: 0;
+padding: 0;
+overflow: hidden;
+}
+
+/**
+*  * Navigation Bar
+*   **/
+.nav {
+float: left;
+margin: 0 1px 0 0;
+padding: 1em 0;
+width: 200px;
+}
+
+.nav ul {
+margin: 0;
+padding: 0;
+}
+
+.nav li {
+list-style: none;
+padding: 0;
+margin: 0;
+}
+
+.nav li ul {
+padding-left: 0.6em !important;
+}
+
+.nav li a {
+display: block;
+margin: 0;
+padding: 0.8ex 2em 0.8ex 1em;
+color: #a11;
+}
+.nav li a:hover {
+color: #f00;
+text-decoration: underline;
+}
+
+.nav li a.thisPage {
+color: #333;
+font-weight: bold;
+}
+
+/**
+*  * Article/Page Text
+*   **/
+.main {
+margin: 0 0 0 200px;
+padding: 1.5em;
+max-width: 50em;
+border-left: 1px solid #000;
+}
+
+/**
+*  * Page Footer
+*   **/
+.footer {
+clear: both;
+color: #fff;
+background: #a11;
+border-top: 1px solid #000;
+font-size: 84%;
+padding: 1em 1em 1.5em 1em;
+}
+
diff --git a/style.css b/style.css
deleted file mode 100644 (file)
index 03eb739..0000000
--- a/style.css
+++ /dev/null
@@ -1,87 +0,0 @@
-/* Based on werc.cat-v.org, suckless.org and garbe.us */
-
-/* General */
-body {
-       color: #aaa;
-       font-family: sans-serif;
-       font-size: 80%;
-       margin: 0;
-       padding: 0
-}
-
-/* Header */
-.header { background-color: #ccc; border: 0; }
-.header a { border: 0; color: #111; text-decoration: none; }
-.midHeader img { border: 0; }
-
-.headerTitle { font-size: 1.6em; font-weight: bold; margin: 0 0 0 0.5em; padding: 0.5em; }
-.headerTitle a { border: 0; text-decoration: none; }
-
-.headerSubTitle { font-size: 0.6em; font-weight: normal; margin-left: 1em; }
-
-/* Side */
-#side-bar {
-       width: 100%;
-       clear: both;
-       border: 0;
-       margin: 0;
-       padding: 0;
-       background-color: #ddd;
-}
-
-#side-bar ul {
-       margin: 0;
-       padding: 0.3em;
-       list-style-type: none;
-       list-style-image: none;
-       border: 0;
-}
-
-#side-bar li { display: inline; line-height: 1.6em; white-space: nowrap; }
-
-#side-bar ul li a {
-       margin: 0;
-       padding: 0.1em 1ex 0.1em 1ex;
-       color: #336699;
-       background-color: transparent;
-       text-decoration: none;
-       font-size: 1em;
-       border: 0;
-}
-
-#side-bar ul li a:hover { color: #111; text-decoration: none; }
-/* Main Copy */
-#main {
-       max-width: 70em;
-       color: #111;
-       margin: 0 auto 0 2em;
-       padding: 1em 3em 2em 1em;
-       border: 0;
-}
-
-#main a { color: #336699; text-decoration: none; }
-#main a:hover { text-decoration: underline; }
-#main h1, #main-copy h2 { color: #666; }
-#main ul { list-style-type: square; }
-
-/* Footer */
-#footer {
-       background-color: #ddd;
-       color: #111;
-       font-size: 91%;
-       padding: 2em;
-       clear: both;
-}
-
-#footer .left { text-align: left; float: left; clear: left; }
-#footer .right { text-align: right; }
-#footer a { color: #111; text-decoration: none; }
-#footer a:hover { text-decoration: underline; }
-
-abbr, acronym { border-bottom: 1px dotted #333; cursor: help; }
-blockquote { border-left: 1px solid #333; font-style: italic; padding: 1em; }
-hr { border-width: 0 0 0.1em 0; border-color: #666; }
-
-code, pre { font-size: 1.1em }
-pre { margin-left: 2em; }
diff --git a/sw.conf b/sw.conf
deleted file mode 100644 (file)
index 4d9e4bd..0000000
--- a/sw.conf
+++ /dev/null
@@ -1,9 +0,0 @@
-# sw - suckless webframework - 2012 - nibble <develsec.org>
-
-# Configuration
-TITLE="mdlowis"        # Site title
-SUBTITLE=""            # Site subtitle
-BL="index.html images" # Black list
-STYLE="style.css"      # Stylesheet name
-# External apps
-MDHANDLER="Markdown.pl" # md handler
diff --git a/templates/page.html b/templates/page.html
new file mode 100644 (file)
index 0000000..c92af98
--- /dev/null
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!-- Site design *heavily* based on the excellent design of http://suckless.org/ -->
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>{{TITLE}}</title>
+    <link rel="shortcut icon" type="image/x-icon" href="logo.png" />
+    <link rel="stylesheet" type="text/css" href="style.css">
+    <meta charset="utf-8">
+</head>
+<body>
+    <div class="header">
+        <a href="index.html"><img src="logo.png"></a>
+        <a class="headerLink" href="index.html">mdlowis</a>
+        <span class="headerSubtitle">{{SUBTITLE}}</span>
+    </div>
+
+    <div class="menu">
+        <span class="left">
+            <a class="current" href="index.html">home</a>
+            <a href="">articles</a>
+            <a href="">projects</a>
+            <a href="">prototypes</a>
+        </span>
+        <!--
+        <span class="right">
+            <a href="">download</a>
+            <a href="">source</a>
+        </span>
+        -->
+    </div>
+
+    <div class="content">
+        <div class="nav">
+            <ul>
+                <li><a href="" class="thisPage">link1</a></li>
+                <li><a href="">link2</a></li>
+                <li><a href="">link3</a></li>
+            </ul>
+        </div>
+
+        <div class="main">
+            {{CONTENT}}
+        </div>
+    </div>
+
+    <div class="footer">
+        <span class="right">{{COPYRIGHT}}</span>
+    </div>
+</body><div></div></html>
diff --git a/tools/genpage b/tools/genpage
new file mode 100755 (executable)
index 0000000..54577c0
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+awk_script='
+BEGIN {
+    file = OFILE
+    mode = 0
+}
+
+mode == 1 { print $0 > file }
+
+mode == 0 {
+    if (($0 != "") && ($1 != "#")) {
+        if ($0 != "---") {
+            first = $1
+            $1 = ""
+            print "export " first "=\"" substr($0, 2) "\""
+        } else {
+            mode = 1
+        }
+    }
+}'
+ifile=${1:?"missing filename"}
+ofile="contents.${0##*/}-$$"
+touch $ofile
+eval `awk -v OFILE="$ofile" "$awk_script" $ifile`
+export CONTENT=`md2html.awk $ofile`
+source config.sh
+if [ -f "templates/$TYPE.html" ]; then
+    mo "templates/$TYPE.html"
+fi
+rm $ofile
similarity index 100%
rename from scripts/md2html.awk
rename to tools/md2html.awk
diff --git a/tools/mo b/tools/mo
new file mode 100755 (executable)
index 0000000..07da321
--- /dev/null
+++ b/tools/mo
@@ -0,0 +1,726 @@
+#!/bin/bash
+#
+# Mo is a mustache template rendering software written in bash.  It inserts
+# environment variables into templates.
+#
+# Learn more about mustache templates at https://mustache.github.io/
+#
+# Mo is under a MIT style licence with an additional non-advertising clause.
+# See LICENSE.md for the full text.
+#
+# This is open source!  Please feel free to contribute.
+#
+# https://github.com/tests-always-included/mo
+
+
+# Scan content until the right end tag is found.  Returns an array with the
+# following members:
+#     [0] = Content before end tag
+#     [1] = End tag (complete tag)
+#     [2] = Content after end tag
+#
+# Everything using this function uses the "standalone tags" logic.
+#
+# Parameters:
+#     $1: Where to store the array
+#     $2: Content
+#     $3: Name of end tag
+#     $4: If -z, do standalone tag processing before finishing
+mustache-find-end-tag() {
+    local CONTENT SCANNED
+
+    # Find open tags
+    SCANNED=""
+    mustache-split CONTENT "$2" '{{' '}}'
+
+    while [[ "${#CONTENT[@]}" -gt 1 ]]; do
+        mustache-trim-whitespace TAG "${CONTENT[1]}"
+
+        # Restore CONTENT[1] before we start using it
+        CONTENT[1]='{{'"${CONTENT[1]}"'}}'
+
+        case $TAG in
+            '#'* | '^'*)
+                # Start another block
+                SCANNED="${SCANNED}${CONTENT[0]}${CONTENT[1]}"
+                mustache-trim-whitespace TAG "${TAG:1}"
+                mustache-find-end-tag CONTENT "${CONTENT[2]}" "$TAG" "loop"
+                SCANNED="${SCANNED}${CONTENT[0]}${CONTENT[1]}"
+                CONTENT=${CONTENT[2]}
+                ;;
+
+            '/'*)
+                # End a block - could be ours
+                mustache-trim-whitespace TAG "${TAG:1}"
+                SCANNED="$SCANNED${CONTENT[0]}"
+
+                if [[ "$TAG" == "$3" ]]; then
+                    # Found our end tag
+                    if [[ -z "$4" ]] && mustache-is-standalone STANDALONE_BYTES "$SCANNED" "${CONTENT[2]}" true; then
+                        # This is also a standalone tag - clean up whitespace
+                        # and move those whitespace bytes to the "tag" element
+                        STANDALONE_BYTES=( $STANDALONE_BYTES )
+                        CONTENT[1]="${SCANNED:${STANDALONE_BYTES[0]}}${CONTENT[1]}${CONTENT[2]:0:${STANDALONE_BYTES[1]}}"
+                        SCANNED="${SCANNED:0:${STANDALONE_BYTES[0]}}"
+                        CONTENT[2]="${CONTENT[2]:${STANDALONE_BYTES[1]}}"
+                    fi
+
+                    local "$1" && mustache-indirect-array "$1" "$SCANNED" "${CONTENT[1]}" "${CONTENT[2]}"
+                    return 0
+                fi
+
+                SCANNED="$SCANNED${CONTENT[1]}"
+                CONTENT=${CONTENT[2]}
+                ;;
+
+            *)
+                # Ignore all other tags
+                SCANNED="${SCANNED}${CONTENT[0]}${CONTENT[1]}"
+                CONTENT=${CONTENT[2]}
+                ;;
+        esac
+
+        mustache-split CONTENT "$CONTENT" '{{' '}}'
+    done
+
+    # Did not find our closing tag
+    SCANNED="$SCANNED${CONTENT[0]}"
+    local "$1" && mustache-indirect-array "$1" "${SCANNED}" "" ""
+}
+
+
+# Find the first index of a substring
+#
+# Parameters:
+#     $1: Destination variable
+#     $2: Haystack
+#     $3: Needle
+mustache-find-string() {
+    local POS STRING
+
+    STRING=${2%%$3*}
+    [[ "$STRING" == "$2" ]] && POS=-1 || POS=${#STRING}
+    local "$1" && mustache-indirect "$1" $POS
+}
+
+
+# Return a dotted name based on current context and target name
+#
+# Parameters:
+#     $1: Target variable to store results
+#     $2: Context name
+#     $3: Desired variable name
+mustache-full-tag-name() {
+    if [[ -z "$2" ]]; then
+        local "$1" && mustache-indirect "$1" "$3"
+    else
+        local "$1" && mustache-indirect "$1" "${2}.${3}"
+    fi
+}
+
+
+# Return the content to parse.  Can be a list of partials for files or
+# the content from stdin.
+#
+# Parameters:
+#     $1: Variable name to assign this content back as
+#     $2-*: File names (optional)
+mustache-get-content() {
+    local CONTENT FILENAME TARGET
+
+    TARGET=$1
+    shift
+    if [[ "${#@}" -gt 0 ]]; then
+        CONTENT=""
+
+        for FILENAME in "$@"; do
+            # This is so relative paths work from inside template files
+            CONTENT="$CONTENT"'{{>'"$FILENAME"'}}'
+        done
+    else
+        mustache-load-file CONTENT /dev/stdin
+    fi
+
+    local "$TARGET" && mustache-indirect "$TARGET" "$CONTENT"
+}
+
+
+# Indent a string, placing the indent at the beginning of every
+# line that has any content.
+#
+# Parameters:
+#     $1: Name of destination variable to get an array of lines
+#     $2: The indent string
+#     $3: The string to reindent
+mustache-indent-lines() {
+    local CONTENT FRAGMENT LEN POS_N POS_R RESULT TRIMMED
+
+    RESULT=""
+    LEN=$((${#3} - 1))
+    CONTENT="${3:0:$LEN}" # Remove newline and dot from workaround - in mustache-partial
+
+    if [ -z "$2" ]; then
+        local "$1" && mustache-indirect "$1" "$CONTENT"
+        return 0
+    fi
+
+    mustache-find-string POS_N "$CONTENT" $'\n'
+    mustache-find-string POS_R "$CONTENT" $'\r'
+
+    while [[ "$POS_N" -gt -1 ]] || [[ "$POS_R" -gt -1 ]]; do
+        if [[ "$POS_N" -gt -1 ]]; then
+            FRAGMENT="${CONTENT:0:$POS_N + 1}"
+            CONTENT=${CONTENT:$POS_N + 1}
+        else
+            FRAGMENT="${CONTENT:0:$POS_R + 1}"
+            CONTENT=${CONTENT:$POS_R + 1}
+        fi
+
+        mustache-trim-chars TRIMMED "$FRAGMENT" false true " " $'\t' $'\n' $'\r'
+
+        if [ ! -z "$TRIMMED" ]; then
+            FRAGMENT="$2$FRAGMENT"
+        fi
+
+        RESULT="$RESULT$FRAGMENT"
+        mustache-find-string POS_N "$CONTENT" $'\n'
+        mustache-find-string POS_R "$CONTENT" $'\r'
+    done
+
+    mustache-trim-chars TRIMMED "$CONTENT" false true " " $'\t'
+
+    if [ ! -z "$TRIMMED" ]; then
+        CONTENT="$2$CONTENT"
+    fi
+
+    RESULT="$RESULT$CONTENT"
+
+    local "$1" && mustache-indirect "$1" "$RESULT"
+}
+
+
+# Send a variable up to caller of a function
+#
+# Parameters:
+#     $1: Variable name
+#     $2: Value
+mustache-indirect() {
+    unset -v "$1"
+    printf -v "$1" '%s' "$2"
+}
+
+
+# Send an array up to caller of a function
+#
+# Parameters:
+#     $1: Variable name
+#     $2-*: Array elements
+mustache-indirect-array() {
+    unset -v "$1"
+    eval $1=\(\"\${@:2}\"\)
+}
+
+
+# Determine if a given environment variable exists and if it is an array.
+#
+# Parameters:
+#     $1: Name of environment variable
+#
+# Return code:
+#     0 if the name is not empty, 1 otherwise
+mustache-is-array() {
+    local MUSTACHE_TEST
+
+    MUSTACHE_TEST=$(declare -p "$1" 2>/dev/null) || return 1
+    [[ "${MUSTACHE_TEST:0:10}" == "declare -a" ]] && return 0
+    [[ "${MUSTACHE_TEST:0:10}" == "declare -A" ]] && return 0
+
+    return 1
+}
+
+
+# Return 0 if the passed name is a function.
+#
+# Parameters:
+#     $1: Name to check if it's a function
+#
+# Return code:
+#     0 if the name is a function, 1 otherwise
+mustache-is-function() {
+    local FUNCTIONS NAME
+
+    FUNCTIONS=$(declare -F)
+    FUNCTIONS=( ${FUNCTIONS//declare -f /} )
+
+    for NAME in ${FUNCTIONS[@]}; do
+        if [[ "$NAME" == "$1" ]]; then
+            return 0
+        fi
+    done
+
+    return 1
+}
+
+
+# Determine if the tag is a standalone tag based on whitespace before and
+# after the tag.
+#
+# Passes back a string containing two numbers in the format "BEFORE AFTER"
+# like "27 10".  It indicates the number of bytes remaining in the "before"
+# string (27) and the number of bytes to trim in the "after" string (10).
+# Useful for string manipulation:
+#
+#     mustache-is-standalone RESULT "$before" "$after" false || return 0
+#     RESULT_ARRAY=( $RESULT )
+#     echo "${before:0:${RESULT_ARRAY[0]}}...${after:${RESULT_ARRAY[1]}}"
+#
+# Parameters:
+#     $1: Variable to pass data back
+#     $2: Content before the tag
+#     $3: Content after the tag
+#     $4: true/false: is this the beginning of the content?
+mustache-is-standalone() {
+    local AFTER_TRIMMED BEFORE_TRIMMED CHAR
+
+    mustache-trim-chars BEFORE_TRIMMED "$2" false true " " $'\t'
+    mustache-trim-chars AFTER_TRIMMED "$3" true false " " $'\t'
+    CHAR=$((${#BEFORE_TRIMMED} - 1))
+    CHAR=${BEFORE_TRIMMED:$CHAR}
+
+    if [[ "$CHAR" != $'\n' ]] && [[ "$CHAR" != $'\r' ]]; then
+        if [[ ! -z "$CHAR" ]] || ! $4; then
+            return 1;
+        fi
+    fi
+
+    CHAR=${AFTER_TRIMMED:0:1}
+
+    if [[ "$CHAR" != $'\n' ]] && [[ "$CHAR" != $'\r' ]] && [[ ! -z "$CHAR" ]]; then
+        return 2;
+    fi
+
+    if [[ "$CHAR" == $'\r' ]] && [[ "${AFTER_TRIMMED:1:1}" == $'\n' ]]; then
+        CHAR="$CHAR"$'\n'
+    fi
+
+    local "$1" && mustache-indirect "$1" "$((${#BEFORE_TRIMMED})) $((${#3} + ${#CHAR} - ${#AFTER_TRIMMED}))"
+}
+
+
+# Join / implode an array
+#
+# Parameters:
+#     $1: Variable name to receive the joined content
+#     $2: Joiner
+#     $3-$*: Elements to join
+mustache-join() {
+    local JOINER PART RESULT TARGET
+
+    TARGET=$1
+    JOINER=$2
+    RESULT=$3
+    shift 3
+
+    for PART in "$@"; do
+        RESULT="$RESULT$JOINER$PART"
+    done
+
+    local "$TARGET" && mustache-indirect "$TARGET" "$RESULT"
+}
+
+# Read a file
+#
+# Parameters:
+#     $1: Variable name to receive the file's content
+#     $2: Filename to load
+mustache-load-file() {
+    local CONTENT LEN
+
+    # The subshell removes any trailing newlines.  We forcibly add
+    # a dot to the content to preserve all newlines.
+    # TODO: remove cat and replace with read loop?
+    CONTENT=$(cat $2; echo '.')
+    LEN=$((${#CONTENT} - 1))
+    CONTENT=${CONTENT:0:$LEN}  # Remove last dot
+
+    local "$1" && mustache-indirect "$1" "$CONTENT"
+}
+
+
+# Process a chunk of content some number of times.
+#
+# Parameters:
+#     $1: Content to parse and reparse and reparse
+#     $2: Tag prefix (context name)
+#     $3-*: Names to insert into the parsed content
+mustache-loop() {
+    local CONTENT CONTEXT CONTEXT_BASE IGNORE
+
+    CONTENT=$1
+    CONTEXT_BASE=$2
+    shift 2
+
+    while [[ "${#@}" -gt 0 ]]; do
+        mustache-full-tag-name CONTEXT "$CONTEXT_BASE" "$1"
+        mustache-parse "$CONTENT" "$CONTEXT" false
+        shift
+    done
+}
+
+
+# Parse a block of text
+#
+# Parameters:
+#     $1: Block of text to change
+#     $2: Current name (the variable NAME for what {{.}} means)
+#     $3: true when no content before this, false otherwise
+mustache-parse() {
+    # Keep naming variables MUSTACHE_* here to not overwrite needed variables
+    # used in the string replacements
+    local MUSTACHE_BLOCK MUSTACHE_CONTENT MUSTACHE_CURRENT MUSTACHE_IS_BEGINNING MUSTACHE_TAG
+
+    MUSTACHE_CURRENT=$2
+    MUSTACHE_IS_BEGINNING=$3
+
+    # Find open tags
+    mustache-split MUSTACHE_CONTENT "$1" '{{' '}}'
+
+    while [[ "${#MUSTACHE_CONTENT[@]}" -gt 1 ]]; do
+        mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_CONTENT[1]}"
+
+        case $MUSTACHE_TAG in
+            '#'*)
+                # Loop, if/then, or pass content through function
+                # Sets context
+                mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
+                mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}"
+                mustache-find-end-tag MUSTACHE_BLOCK "$MUSTACHE_CONTENT" "$MUSTACHE_TAG"
+                mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
+
+                if mustache-test "$MUSTACHE_TAG"; then
+                    # Show / loop / pass through function
+                    if mustache-is-function "$MUSTACHE_TAG"; then
+                        # TODO: Consider piping the output to
+                        # mustache-get-content so the lambda does not
+                        # execute in a subshell?
+                        MUSTACHE_CONTENT=$($MUSTACHE_TAG "${MUSTACHE_BLOCK[0]}")
+                        mustache-parse "$MUSTACHE_CONTENT" "$MUSTACHE_CURRENT" false
+                        MUSTACHE_CONTENT="${MUSTACHE_BLOCK[2]}"
+                    elif mustache-is-array "$MUSTACHE_TAG"; then
+                        eval 'mustache-loop "${MUSTACHE_BLOCK[0]}" "$MUSTACHE_TAG" "${!'"$MUSTACHE_TAG"'[@]}"'
+                    else
+                        mustache-parse "${MUSTACHE_BLOCK[0]}" "$MUSTACHE_CURRENT" false
+                    fi
+                fi
+
+                MUSTACHE_CONTENT="${MUSTACHE_BLOCK[2]}"
+                ;;
+
+            '>'*)
+                # Load partial - get name of file relative to cwd
+                mustache-partial MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING "$MUSTACHE_CURRENT"
+                ;;
+
+            '/'*)
+                # Closing tag - If hit in this loop, we simply ignore
+                # Matching tags are found in mustache-find-end-tag
+                mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
+                ;;
+
+            '^'*)
+                # Display section if named thing does not exist
+                mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
+                mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}"
+                mustache-find-end-tag MUSTACHE_BLOCK "$MUSTACHE_CONTENT" "$MUSTACHE_TAG"
+                mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
+
+                if ! mustache-test "$MUSTACHE_TAG"; then
+                    mustache-parse "${MUSTACHE_BLOCK[0]}" "$MUSTACHE_CURRENT" false "$MUSTACHE_CURRENT"
+                fi
+
+                MUSTACHE_CONTENT="${MUSTACHE_BLOCK[2]}"
+                ;;
+
+            '!'*)
+                # Comment - ignore the tag content entirely
+                # Trim spaces/tabs before the comment
+                mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
+                ;;
+
+            .)
+                # Current content (environment variable or function)
+                mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}"
+                mustache-show "$MUSTACHE_CURRENT" "$MUSTACHE_CURRENT"
+                ;;
+
+            '=')
+                # Change delimiters
+                # Any two non-whitespace sequences separated by whitespace.
+                # TODO
+                mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
+                ;;
+
+            '{'*)
+                # Unescaped - split on }}} not }}
+                mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}"
+                MUSTACHE_CONTENT="${MUSTACHE_TAG:1}"'}}'"$MUSTACHE_CONTENT"
+                mustache-split MUSTACHE_CONTENT "$MUSTACHE_CONTENT" '}}}'
+                mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_CONTENT[0]}"
+                mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
+                MUSTACHE_CONTENT=${MUSTACHE_CONTENT[1]}
+
+                # Now show the value
+                mustache-show "$MUSTACHE_TAG" "$MUSTACHE_CURRENT"
+                ;;
+
+            '&'*)
+                # Unescaped
+                mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}"
+                mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}"
+                mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
+                mustache-show "$MUSTACHE_TAG" "$MUSTACHE_CURRENT"
+                ;;
+
+            *)
+                # Normal environment variable or function call
+                mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}"
+                mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
+                mustache-show "$MUSTACHE_TAG" "$MUSTACHE_CURRENT"
+                ;;
+        esac
+
+        MUSTACHE_IS_BEGINNING=false
+        mustache-split MUSTACHE_CONTENT "$MUSTACHE_CONTENT" '{{' '}}'
+    done
+
+    echo -n "${MUSTACHE_CONTENT[0]}"
+}
+
+
+# Process a partial
+#
+# Indentation should be applied to the entire partial
+#
+# Prefix all variables
+#
+# Parameters:
+#     $1: Name of destination "content" variable.
+#     $2: Content before the tag that was not yet written
+#     $3: Tag content
+#     $4: Content after the tag
+#     $5: true/false: is this the beginning of the content?
+#     $6: Current context name
+mustache-partial() {
+    local MUSTACHE_CONTENT MUSTACHE_FILENAME MUSTACHE_INDENT MUSTACHE_LINE MUSTACHE_PARTIAL MUSTACHE_STANDALONE
+
+    if mustache-is-standalone MUSTACHE_STANDALONE "$2" "$4" $5; then
+        MUSTACHE_STANDALONE=( $MUSTACHE_STANDALONE )
+        echo -n "${2:0:${MUSTACHE_STANDALONE[0]}}"
+        MUSTACHE_INDENT=${2:${MUSTACHE_STANDALONE[0]}}
+        MUSTACHE_CONTENT=${4:${MUSTACHE_STANDALONE[1]}}
+    else
+        MUSTACHE_INDENT=""
+        echo -n "$2"
+        MUSTACHE_CONTENT=$4
+    fi
+
+    mustache-trim-whitespace MUSTACHE_FILENAME "${3:1}"
+
+    # Execute in subshell to preserve current cwd and environment
+    (
+        # TODO:  Remove dirname and use a function instead
+        cd "$(dirname "$MUSTACHE_FILENAME")"
+        mustache-indent-lines MUSTACHE_PARTIAL "$MUSTACHE_INDENT" "$(
+            mustache-load-file MUSTACHE_PARTIAL "${MUSTACHE_FILENAME##*/}"
+
+            # Fix bash handling of subshells
+            # The extra dot is removed in mustache-indent-lines
+            echo -n "${MUSTACHE_PARTIAL}."
+        )"
+        mustache-parse "$MUSTACHE_PARTIAL" "$6" true
+    )
+
+    local "$1" && mustache-indirect "$1" "$MUSTACHE_CONTENT"
+}
+
+
+# Show an environment variable or the output of a function.
+#
+# Limit/prefix any variables used
+#
+# Parameters:
+#     $1: Name of environment variable or function
+#     $2: Current context
+mustache-show() {
+    local JOINED MUSTACHE_NAME_PARTS
+
+    if mustache-is-function "$1"; then
+        CONTENT=$($1 "")
+        mustache-parse "$CONTENT" "$2" false
+        return 0
+    fi
+
+    mustache-split MUSTACHE_NAME_PARTS "$1" "."
+
+    if [[ -z "${MUSTACHE_NAME_PARTS[1]}" ]]; then
+        if mustache-is-array "$1"; then
+            eval mustache-join JOINED "," "\${$1[@]}"
+            echo -n "$JOINED"
+        else
+            echo -n "${!1}"
+        fi
+    else
+        # Further subindexes are disallowed
+        eval 'echo -n "${'"${MUSTACHE_NAME_PARTS[0]}"'['"${MUSTACHE_NAME_PARTS[1]%%.*}"']}"'
+    fi
+}
+
+
+# Split a larger string into an array
+#
+# Parameters:
+#     $1: Destination variable
+#     $2: String to split
+#     $3: Starting delimiter
+#     $4: Ending delimiter (optional)
+mustache-split() {
+    local POS RESULT
+
+    RESULT=( "$2" )
+    mustache-find-string POS "${RESULT[0]}" "$3"
+
+    if [[ "$POS" -ne -1 ]]; then
+        # The first delimiter was found
+        RESULT[1]=${RESULT[0]:$POS + ${#3}}
+        RESULT[0]=${RESULT[0]:0:$POS}
+
+        if [[ ! -z "$4" ]]; then
+            mustache-find-string POS "${RESULT[1]}" "$4"
+
+            if [[ "$POS" -ne -1 ]]; then
+                # The second delimiter was found
+                RESULT[2]="${RESULT[1]:$POS + ${#4}}"
+                RESULT[1]="${RESULT[1]:0:$POS}"
+            fi
+        fi
+    fi
+
+    local "$1" && mustache-indirect-array "$1" "${RESULT[@]}"
+}
+
+
+# Handle the content for a standalone tag.  This means removing whitespace
+# (not newlines) before a tag and whitespace and a newline after a tag.
+# That is, assuming, that the line is otherwise empty.
+#
+# Parameters:
+#     $1: Name of destination "content" variable.
+#     $2: Content before the tag that was not yet written
+#     $3: Tag content (not used)
+#     $4: Content after the tag
+#     $5: true/false: is this the beginning of the content?
+mustache-standalone-allowed() {
+    local STANDALONE_BYTES
+
+    if mustache-is-standalone STANDALONE_BYTES "$2" "$4" $5; then
+        STANDALONE_BYTES=( $STANDALONE_BYTES )
+        echo -n "${2:0:${STANDALONE_BYTES[0]}}"
+        local "$1" && mustache-indirect "$1" "${4:${STANDALONE_BYTES[1]}}"
+    else
+        echo -n "$2"
+        local "$1" && mustache-indirect "$1" "$4"
+    fi
+}
+
+
+# Handle the content for a tag that is never "standalone".  No adjustments
+# are made for newlines and whitespace.
+#
+# Parameters:
+#     $1: Name of destination "content" variable.
+#     $2: Content before the tag that was not yet written
+#     $3: Tag content (not used)
+#     $4: Content after the tag
+mustache-standalone-denied() {
+    echo -n "$2"
+    local "$1" && mustache-indirect "$1" "$4"
+}
+
+
+# Returns 0 (success) if the named thing is a function or if it is a non-empty
+# environment variable.
+#
+# Do not use unprefixed variables here if possible as this needs to check
+# if any name exists in the environment
+#
+# Parameters:
+#     $1: Name of environment variable or function
+#     $2: Current value (our context)
+#
+# Return code:
+#     0 if the name is not empty, 1 otherwise
+mustache-test() {
+    # Test for functions
+    mustache-is-function "$1" && return 0
+
+    if mustache-is-array "$1"; then
+        # Arrays must have at least 1 element
+        eval '[[ "${#'"$1"'[@]}" -gt 0 ]]' && return 0
+    else
+        # Environment variables must not be empty
+        [[ ! -z "${!1}" ]] && return 0
+    fi
+
+    return 1
+}
+
+
+# Trim the leading whitespace only
+#
+# Parameters:
+#     $1: Name of destination variable
+#     $2: The string
+#     $3: true/false - trim front?
+#     $4: true/false - trim end?
+#     $5-*: Characters to trim
+mustache-trim-chars() {
+    local BACK CURRENT FRONT LAST TARGET VAR
+
+    TARGET=$1
+    CURRENT=$2
+    FRONT=$3
+    BACK=$4
+    LAST=""
+    shift # Remove target
+    shift # Remove string
+    shift # Remove trim front flag
+    shift # Remove trim end flag
+
+    while [[ "$CURRENT" != "$LAST" ]]; do
+        LAST=$CURRENT
+
+        for VAR in "$@"; do
+            $FRONT && CURRENT="${CURRENT/#$VAR}"
+            $BACK && CURRENT="${CURRENT/%$VAR}"
+        done
+    done
+
+    local "$TARGET" && mustache-indirect "$TARGET" "$CURRENT"
+}
+
+
+# Trim leading and trailing whitespace from a string
+#
+# Parameters:
+#     $1: Name of variable to store trimmed string
+#     $2: The string
+mustache-trim-whitespace() {
+    local RESULT
+
+    mustache-trim-chars RESULT "$2" true true $'\r' $'\n' $'\t' " "
+    local "$1" && mustache-indirect "$1" "$RESULT"
+}
+
+
+mustache-get-content MUSTACHE_CONTENT "$@"
+mustache-parse "$MUSTACHE_CONTENT" "" true
diff --git a/tools/serve b/tools/serve
new file mode 100755 (executable)
index 0000000..1d7b169
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+PORT=8080
+
+bb(){
+    which busybox > /dev/null && echo "httpd running on port $PORT..."
+    busybox httpd -f -p $PORT
+}
+
+py3(){
+    python3 -m http.server $PORT
+}
+
+py(){
+    if [ "`python -c 'import sys; print(sys.version_info[:])[0]'`" == "3" ]; then
+        python3 -m http.server $PORT
+    else
+        py3
+    fi
+}
+
+rb(){
+    ruby -run -ehttpd . -p$PORT
+}
+
+bb || py3 || py || rb || "Error: No suitable interpreter available"