[Reproducible-commits] [discount] 01/121: Imported Upstream version 2.0.4

Jérémy Bobbio lunar at moszumanska.debian.org
Tue Sep 23 20:56:11 UTC 2014


This is an automated email from the git hooks/post-receive script.

lunar pushed a commit to branch pu/reproducible_builds
in repository discount.

commit 57c981d8c922b13c3991b1e4e722bc9a19844f62
Author: Alessandro Ghedini <al3xbio at gmail.com>
Date:   Fri Jan 14 17:20:48 2011 +0100

    Imported Upstream version 2.0.4
---
 COPYRIGHT             |   47 ++
 CREDITS               |   33 +
 Csio.c                |   61 ++
 INSTALL               |   41 ++
 Makefile.in           |  116 ++++
 Plan9/README          |   40 ++
 Plan9/markdown.1      |  169 +++++
 Plan9/markdown.2      |  332 ++++++++++
 Plan9/markdown.6      |  543 ++++++++++++++++
 Plan9/mkfile          |   37 ++
 README                |   16 +
 VERSION               |    1 +
 amalloc.c             |  111 ++++
 amalloc.h             |   29 +
 basename.c            |   43 ++
 configure.inc         | 1660 +++++++++++++++++++++++++++++++++++++++++++++++++
 configure.sh          |  141 +++++
 css.c                 |   85 +++
 cstring.h             |   77 +++
 docheader.c           |   49 ++
 dumptree.c            |  152 +++++
 emmatch.c             |  188 ++++++
 flags.c               |   83 +++
 generate.c            | 1643 ++++++++++++++++++++++++++++++++++++++++++++++++
 html5.c               |   24 +
 main.c                |  235 +++++++
 makepage.1            |   34 +
 makepage.c            |   27 +
 markdown.1            |  164 +++++
 markdown.3            |  136 ++++
 markdown.7            | 1020 ++++++++++++++++++++++++++++++
 markdown.c            | 1215 ++++++++++++++++++++++++++++++++++++
 markdown.h            |  169 +++++
 mkd-callbacks.3       |   71 +++
 mkd-extensions.7      |  190 ++++++
 mkd-functions.3       |  182 ++++++
 mkd-line.3            |   41 ++
 mkd2html.1            |   52 ++
 mkd2html.c            |  185 ++++++
 mkdio.c               |  344 ++++++++++
 mkdio.h.in            |  100 +++
 resource.c            |  157 +++++
 setup.c               |   47 ++
 tags.c                |  123 ++++
 tags.h                |   19 +
 tests/autolink.t      |   27 +
 tests/automatic.t     |   27 +
 tests/backslash.t     |   16 +
 tests/callbacks.t     |   17 +
 tests/chrome.text     |   13 +
 tests/code.t          |   35 ++
 tests/compat.t        |   29 +
 tests/crash.t         |   31 +
 tests/div.t           |   45 ++
 tests/dl.t            |   96 +++
 tests/embedlinks.text |    9 +
 tests/emphasis.t      |   19 +
 tests/flow.t          |   33 +
 tests/footnotes.t     |   16 +
 tests/functions.sh    |   77 +++
 tests/header.t        |   26 +
 tests/html.t          |  141 +++++
 tests/html5.t         |   17 +
 tests/links.text      |   14 +
 tests/linkylinky.t    |  130 ++++
 tests/linkypix.t      |   21 +
 tests/list.t          |  155 +++++
 tests/list3deep.t     |   38 ++
 tests/misc.t          |   12 +
 tests/pandoc.t        |   44 ++
 tests/para.t          |   19 +
 tests/paranoia.t      |   12 +
 tests/peculiarities.t |   77 +++
 tests/pseudo.t        |   20 +
 tests/reddit.t        |   27 +
 tests/reparse.t       |   14 +
 tests/schiraldi.t     |   91 +++
 tests/smarty.t        |   24 +
 tests/snakepit.t      |   29 +
 tests/strikethrough.t |   15 +
 tests/style.t         |   34 +
 tests/superscript.t   |   17 +
 tests/syntax.text     |  897 ++++++++++++++++++++++++++
 tests/tables.t        |  167 +++++
 tests/tabstop.t       |   48 ++
 tests/toc.t           |   35 ++
 tests/xml.t           |   18 +
 theme.1               |  142 +++++
 theme.c               |  609 ++++++++++++++++++
 toc.c                 |  101 +++
 tools/checkbits.sh    |   11 +
 tools/cols.c          |   38 ++
 tools/echo.c          |   23 +
 version.c.in          |   22 +
 xml.c                 |   82 +++
 xmlpage.c             |   48 ++
 96 files changed, 13940 insertions(+)

diff --git a/COPYRIGHT b/COPYRIGHT
new file mode 100644
index 0000000..0cf9820
--- /dev/null
+++ b/COPYRIGHT
@@ -0,0 +1,47 @@
+->Copyright (C) 2007 David Loren Parsons.  
+All rights reserved.<-
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicence, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+ 1. Redistributions of source code must retain the above copyright
+    notice, this list of conditions, and the following disclaimer.
+
+ 2. 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, and in the same place and form as other
+    copyright, license and disclaimer information.
+
+ 3. The end-user documentation included with the redistribution, if
+    any, must include the following acknowledgment:
+
+        This product includes software developed by
+        David Loren Parsons <http://www.pell.portland.or.us/~orc>
+
+    in the same place and form as other third-party acknowledgments.
+    Alternately, this acknowledgment may appear in the software
+    itself, in the same form and location as other such third-party
+    acknowledgments.
+
+ 4. Except as contained in this notice, the name of David Loren
+    Parsons shall not be used in advertising or otherwise to promote
+    the sale, use or other dealings in this Software without prior
+    written authorization from David Loren Parsons.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 DAVID LOREN PARSONS 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.
diff --git a/CREDITS b/CREDITS
new file mode 100644
index 0000000..3ec9d8c
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,33 @@
+Discount is primarily my work, but it has only reached the point
+where it is via contributions, critiques, and bug reports from a
+host of other people, some of which are listed before.   If your
+name isn't on this list, please remind me
+				    -david parsons (orc at pell.chi.il.us)
+
+
+Josh Wood	--  Plan9 support.
+Mike Schiraldi	--  Reddit style automatic links, MANY MANY MANY
+		    bug reports about boundary conditions and
+		    places where I didn't get it right.
+Jjgod Jiang	--  Table of contents support.
+Petite Abeille	--  Many bug reports about places where I didn't
+		    get it right.
+Tim Channon	--  inspiration for the `mkd_xhtmlpage()` function
+Christian Herenz--  Many bug reports regarding my implementation of
+		    `[]()` and `![]()`
+A.S.Bradbury	--  Portability bug reports for 64 bit systems.
+Joyent		--  Loan of a solaris box so I could get discount
+		    working under solaris.
+Ryan Tomayko	--  Portability requests (and the rdiscount ruby
+		    binding.)
+yidabu		--  feedback on the documentation, bug reports
+		    against utf-8 support.
+Pierre Joye	--  bug reports, php discount binding.
+Masayoshi Sekimura- perl discount binding.
+Jeremy Hinegardner- bug reports about list handling.
+Andrew White	--  bug reports about the format of generated urls.
+Steve Huff	--  bug reports about Makefile portability (for Fink)
+Ignacio Burgue?o--  bug reports about `>%class%`
+Henrik Nyh	--  bug reports about embedded html handling.
+
+		    
diff --git a/Csio.c b/Csio.c
new file mode 100644
index 0000000..4358b33
--- /dev/null
+++ b/Csio.c
@@ -0,0 +1,61 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+
+/* putc() into a cstring
+ */
+void
+Csputc(int c, Cstring *iot)
+{
+    EXPAND(*iot) = c;
+}
+
+
+/* printf() into a cstring
+ */
+int
+Csprintf(Cstring *iot, char *fmt, ...)
+{
+    va_list ptr;
+    int siz=100;
+
+    do {
+	RESERVE(*iot, siz);
+	va_start(ptr, fmt);
+	siz = vsnprintf(T(*iot)+S(*iot), ALLOCATED(*iot)-S(*iot), fmt, ptr);
+	va_end(ptr);
+    } while ( siz > (ALLOCATED(*iot)-S(*iot)) );
+
+    S(*iot) += siz;
+    return siz;
+}
+
+
+/* write() into a cstring
+ */
+int
+Cswrite(Cstring *iot, char *bfr, int size)
+{
+    RESERVE(*iot, size);
+    memcpy(T(*iot)+S(*iot), bfr, size);
+    S(*iot) += size;
+    return size;
+}
+
+
+/* reparse() into a cstring
+ */
+void
+Csreparse(Cstring *iot, char *buf, int size, int flags)
+{
+    MMIOT f;
+    ___mkd_initmmiot(&f, 0);
+    ___mkd_reparse(buf, size, 0, &f);
+    ___mkd_emblock(&f);
+    SUFFIX(*iot, T(f.out), S(f.out));
+    ___mkd_freemmiot(&f, 0);
+}
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..c591387
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,41 @@
+
+		    HOW TO BUILD AND INSTALL DISCOUNT
+
+1) Unpacking the distribution
+
+The DISCOUNT sources are distributed in tarballs.  After extracting from
+the tarball, you should end up with all the source and build files in the
+directory
+		discount-(version)
+
+2) Installing the distribution
+
+DISCOUNT uses configure.sh to set itself up for compilation.   To run
+configure, just do ``./configure.sh'' and it will check your system for
+build dependencies and build makefiles for you.   If configure.sh finishes
+without complaint, you can then do a ``make'' to compile everything and a
+``make install'' to install the binaries.
+
+Configure.sh has a few options that can be set:
+
+--src=DIR		where the source lives (.)
+--prefix=DIR		where to install the final product (/usr/local)
+--execdir=DIR		where to put executables (prefix/bin)
+--sbindir=DIR		where to put static executables (prefix/sbin)
+--confdir=DIR		where to put configuration information (/etc)
+--libdir=DIR		where to put libraries (prefix/lib)
+--libexecdir=DIR	where to put private executables
+--mandir=DIR		where to put manpages
+--enable-dl-tag	Use the DL tag extension
+--enable-pandoc-header	Use pandoc-style header blocks
+--enable-superscript	A^B expands to A<sup>B</sup>
+--enable-amalloc	Use a debugging memory allocator (to detect leaks)
+--relaxed-emphasis	Don't treat _ in the middle of a word as emphasis
+--with-tabstops=N	Set tabstops to N characters (default is 4)
+
+3) Installing sample programs and manpages
+
+The standard ``make install'' rule just installs the binaries. If you
+want to install the sample programs, they are installed with
+``make install.samples'';  to install manpages, ``make install.man''.
+A shortcut to install everything is ``make install.everything''
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..6c38a5f
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,116 @@
+CC=@CC@ -I. -L.
+CFLAGS=@CFLAGS@
+AR=@AR@
+RANLIB=@RANLIB@
+
+BINDIR=@exedir@
+MANDIR=@mandir@
+LIBDIR=@libdir@
+INCDIR=@prefix@/include
+
+PGMS=markdown
+SAMPLE_PGMS=mkd2html makepage
+ at THEME@SAMPLE_PGMS+= theme
+MKDLIB=libmarkdown
+OBJS=mkdio.o markdown.o dumptree.o generate.o \
+     resource.o docheader.o version.o toc.o css.o \
+     xml.o Csio.o xmlpage.o basename.o emmatch.o \
+     setup.o tags.o html5.o flags.o @AMALLOC@
+
+MAN3PAGES=mkd-callbacks.3 mkd-functions.3 markdown.3 mkd-line.3
+
+all: $(PGMS) $(SAMPLE_PGMS)
+
+install: $(PGMS)
+	@INSTALL_PROGRAM@ $(PGMS) $(DESTDIR)/$(BINDIR)
+	./librarian.sh install libmarkdown VERSION $(DESTDIR)/$(LIBDIR)
+	@INSTALL_DATA@ mkdio.h $(DESTDIR)/$(INCDIR)
+
+install.everything: install install.samples install.man
+
+install.samples: $(SAMPLE_PGMS) install
+	@INSTALL_PROGRAM@ $(SAMPLE_PGMS) $(DESTDIR)/$(BINDIR)
+	@INSTALL_DIR@ $(DESTDIR)/$(MANDIR)/man1
+	@INSTALL_DATA@ theme.1 makepage.1 mkd2html.1 $(DESTDIR)/$(MANDIR)/man1
+
+install.man:
+	@INSTALL_DIR@ $(DESTDIR)/$(MANDIR)/man3
+	@INSTALL_DATA@ $(MAN3PAGES) $(DESTDIR)/$(MANDIR)/man3
+	for x in mkd_line mkd_generateline; do \
+	    ( echo '.\"' ; echo ".so man3/mkd-line.3" ) > $(DESTDIR)/$(MANDIR)/man3/$$x.3;\
+	done
+	for x in mkd_in mkd_string; do \
+	    ( echo '.\"' ; echo ".so man3/markdown.3" ) > $(DESTDIR)/$(MANDIR)/man3/$$x.3;\
+	done
+	for x in mkd_compile mkd_css mkd_generatecss mkd_generatehtml mkd_cleanup mkd_doc_title mkd_doc_author mkd_doc_date; do \
+	    ( echo '.\"' ; echo ".so man3/mkd-functions.3" ) > $(DESTDIR)/$(MANDIR)/man3/$$x.3; \
+	done
+	@INSTALL_DIR@ $(DESTDIR)/$(MANDIR)/man7
+	@INSTALL_DATA@ markdown.7 mkd-extensions.7 $(DESTDIR)/$(MANDIR)/man7
+	@INSTALL_DIR@ $(DESTDIR)/$(MANDIR)/man1
+	@INSTALL_DATA@ markdown.1 $(DESTDIR)/$(MANDIR)/man1
+
+install.everything: install install.man
+
+version.o: version.c VERSION
+	$(CC) -DVERSION=\"`cat VERSION`\" -c version.c
+
+markdown: main.o $(MKDLIB)
+	$(CC) $(CFLAGS) -o markdown main.o -lmarkdown @LIBS@
+
+# example programs
+ at THEME@theme:  theme.o $(MKDLIB) mkdio.h
+ at THEME@	$(CC) -o theme theme.o -lmarkdown @LIBS@
+
+
+mkd2html:  mkd2html.o $(MKDLIB) mkdio.h
+	$(CC) -o mkd2html mkd2html.o -lmarkdown @LIBS@
+
+makepage:  makepage.c $(MKDLIB) mkdio.h
+	$(CC) -o makepage makepage.c -lmarkdown @LIBS@
+
+main.o: main.c mkdio.h config.h
+	$(CC) -I. -c main.c
+
+$(MKDLIB): $(OBJS)
+	./librarian.sh make $(MKDLIB) VERSION $(OBJS)
+
+verify: echo tools/checkbits.sh
+	@./echo -n "headers ... "; tools/checkbits.sh && echo "GOOD"
+
+test:	$(PGMS) echo cols verify
+	@for x in tests/*.t; do \
+	    @LD_LIBRARY_PATH@=`pwd` sh $$x || exit 1; \
+	done
+
+cols:   tools/cols.c config.h
+	$(CC) -o cols tools/cols.c
+echo:   tools/echo.c config.h
+	$(CC) -o echo tools/echo.c
+	
+clean:
+	rm -f $(PGMS) $(SAMPLE_PGMS) *.o
+	rm -f $(MKDLIB) `./librarian.sh files $(MKDLIB) VERSION`
+
+distclean spotless: clean
+	rm -f @GENERATED_FILES@ @CONFIGURE_FILES@
+
+Csio.o: Csio.c cstring.h amalloc.h config.h markdown.h
+amalloc.o: amalloc.c
+basename.o: basename.c config.h cstring.h amalloc.h markdown.h
+css.o: css.c config.h cstring.h amalloc.h markdown.h
+docheader.o: docheader.c config.h cstring.h amalloc.h markdown.h
+dumptree.o: dumptree.c markdown.h cstring.h amalloc.h config.h
+emmatch.o: emmatch.c config.h cstring.h amalloc.h markdown.h
+generate.o: generate.c config.h cstring.h amalloc.h markdown.h
+main.o: main.c config.h amalloc.h
+makepage.o: makepage.c
+markdown.o: markdown.c config.h cstring.h amalloc.h markdown.h
+mkd2html.o: mkd2html.c config.h mkdio.h cstring.h amalloc.h
+mkdio.o: mkdio.c config.h cstring.h amalloc.h markdown.h
+resource.o: resource.c config.h cstring.h amalloc.h markdown.h
+theme.o: theme.c config.h mkdio.h cstring.h amalloc.h
+toc.o: toc.c config.h cstring.h amalloc.h markdown.h
+version.o: version.c config.h
+xml.o: xml.c config.h cstring.h amalloc.h markdown.h
+xmlpage.o: xmlpage.c config.h cstring.h amalloc.h markdown.h
diff --git a/Plan9/README b/Plan9/README
new file mode 100644
index 0000000..255b212
--- /dev/null
+++ b/Plan9/README
@@ -0,0 +1,40 @@
+% Discount on Plan 9
+% Josh Wood
+% 2009-06-12
+
+# *Discount* Markdown compiler on Plan 9
+
+## Build
+
+	% CONFIG='--enable-all-features' mk config
+	% mk install
+	% markdown -V
+	markdown: discount X.Y.Z DL_TAG HEADER DEBUG SUPERSCRIPT RELAXED DIV
+
+`--enable-all-features` may be replaced by zero or more of:
+
+		--enable-dl-tag		Use the DL tag extension
+		--enable-pandoc-header	Use pandoc-style header blocks
+		--enable-superscript	A^B becomes A<sup>B</sup>
+		--enable-amalloc	Enable memory allocation debugging
+		--relaxed-emphasis	underscores aren't special in the middle of words
+		--with-tabstops=N	Set tabstops to N characters (default is 4)
+		--enable-div		Enable >%id% divisions
+		--enable-alpha-list	Enable (a)/(b)/(c) lists
+		--enable-all-features	Turn on all stable optional features
+
+## Notes
+
+The supplied mkfile merely drives Discount's own configure script and
+then APE's *psh* environment to build the Discount source, then copies
+the result(s) to locations appropriate for system-wide use on Plan 9.
+There are a few other *mk*(1) targets:
+
+`install.libs`: Discount includes a C library and header.
+Installation is optional.  Plan 9 binaries are statically linked.
+
+`install.man`: Add manual pages for markdown(1) and (6).
+
+`install.progs`: Extra programs.  *makepage* writes complete XHTML
+documents, rather than fragments.  *mkd2html* is similar, but produces
+HTML.
diff --git a/Plan9/markdown.1 b/Plan9/markdown.1
new file mode 100644
index 0000000..b38947f
--- /dev/null
+++ b/Plan9/markdown.1
@@ -0,0 +1,169 @@
+.TH MARKDOWN 1 
+.SH NAME
+markdown \- convert Markdown text to HTML
+.SH SYNOPSIS
+.B markdown
+[
+.B -dTV
+]
+[
+.BI -b " url-base
+]
+[
+.BI -F " bitmap
+]
+[
+.BI -f " flags
+]
+[
+.BI -o " ofile
+]
+[
+.BI -s " text
+]
+[
+.BI -t " text
+]
+[
+.I file
+]
+.SH DESCRIPTION
+The
+.I markdown
+utility reads the
+.IR Markdown (6)-formatted
+.I file
+(or standard input) and writes its
+.SM HTML
+fragment representation on standard output.
+.PP
+The options are:
+.TF dfdoptions
+.TP
+.BI -b " url-base
+Links in source begining with
+.B /
+will be prefixed with
+.I url-base
+in the output.
+.TP
+.B -d
+Instead of printing an
+.SM HTML
+fragment, print a parse tree.
+.TP
+.BI -F " bitmap
+Set translation flags.
+.I Bitmap
+is a bit map of the various configuration options described in
+.IR markdown (2).
+.TP
+.BI -f " flags
+Set or clear various translation
+.IR flags ,
+described below.
+.I Flags
+are in a comma-delimited list, with an optional
+.B +
+(set) prefix on each flag.
+.TP
+.BI -o " ofile
+Write the generated
+.SM HTML
+to
+.IR ofile .
+.TP
+.BI -s " text
+Use the
+.IR markdown (2)
+function to format the
+.I text
+on standard input.
+.TP
+.B -T
+Under
+.B -f
+.BR toc ,
+print the table of contents as an unordered list before the usual
+.SM HTML
+output.
+.TP
+.BI -t " text
+Use
+.IR mkd_text
+(in
+.IR markdown (2))
+to format
+.I text
+instead of processing standard input with
+.IR markdown .
+.TP
+.B -V
+Show version number and configuration. If the version includes the string
+.BR DL_TAG ,
+.I markdown
+was configured with definition list support. If the version includes the string
+.BR HEADER ,
+.I markdown
+was configured to support pandoc header blocks.
+.PD
+.SS TRANSLATION FLAGS
+The translation flags understood by
+.B -f
+are:
+.TF \ noheader
+.TP
+.B noimage
+Don't allow image tags.
+.TP
+.B nolinks
+Don't allow links.
+.TP
+.B nohtml
+Don't allow any embedded HTML.
+.TP
+.B cdata
+Generate valid XML output.
+.TP
+.B noheader
+Do not process pandoc headers.
+.TP
+.B notables
+Do not process the syntax extension for tables.
+.TP
+.B tabstops
+Use Markdown-standard 4-space tabstops.
+.TP
+.B strict
+Disable superscript and relaxed emphasis.
+.TP
+.B relax
+Enable superscript and relaxed emphasis (the default).
+.TP
+.B toc
+Enable table of contents support, generated from headings (in 
+.IR markdown (6))
+in the source.
+.TP
+.B 1.0
+Revert to Markdown 1.0 compatibility.
+.PD
+.PP
+For example,
+.B -f nolinks,quot
+tells
+.I markdown
+not to allow
+.B <a>
+tags, and to expand double quotes.
+.SH SOURCE
+.B /sys/src/cmd/discount
+.SH SEE ALSO
+.IR markdown (2),
+.IR markdown (6)
+.PP
+http://daringfireball.net/projects/markdown/,
+``Markdown''.
+.SH DIAGNOSTICS
+.I Markdown
+exits 0 on success and >0 if an error occurs.
diff --git a/Plan9/markdown.2 b/Plan9/markdown.2
new file mode 100644
index 0000000..9cb1c9d
--- /dev/null
+++ b/Plan9/markdown.2
@@ -0,0 +1,332 @@
+.TH MARKDOWN 2
+.SH NAME
+mkd_in, mkd_string, markdown, mkd_compile, mkd_css, mkd_generatecss,
+mkd_document, mkd_generatehtml, mkd_xhtmlpage, mkd_toc, mkd_generatetoc,
+mkd_cleanup, mkd_doc_title, mkd_doc_author, mkd_doc_date, mkd_line,
+mkd_generateline \- convert Markdown text to HTML
+.SH SYNOPSIS
+.ta \w'MMIOT* 'u
+.B #include <mkdio.h>
+.PP
+.B
+MMIOT* mkd_in(FILE *input, int flags)
+.PP
+.B
+MMIOT* mkd_string(char *buf, int size, int flags)
+.PP
+.B
+int	markdown(MMIOT *doc, FILE *output, int flags)
+.PP
+.B
+int	mkd_compile(MMIOT *document, int flags)
+.PP
+.B
+int	mkd_css(MMIOT *document, char **doc)
+.PP
+.B
+int	mkd_generatecss(MMIOT *document, FILE *output)
+.PP
+.B
+int	mkd_document(MMIOT *document, char **doc)
+.PP
+.B
+int	mkd_generatehtml(MMIOT *document, FILE *output)
+.PP
+.B
+int	mkd_xhtmlpage(MMIOT *document, int flags, FILE *output)
+.PP
+.B
+int	mkd_toc(MMIOT *document, char **doc)
+.PP
+.B
+int	mkd_generatetoc(MMIOT *document, FILE *output)
+.PP
+.B
+void	mkd_cleanup(MMIOT*);
+.PP
+.B
+char*	mkd_doc_title(MMIOT*)
+.PP
+.B
+char*	mkd_doc_author(MMIOT*)
+.PP
+.B
+char*	mkd_doc_date(MMIOT*)
+.PP
+.B
+int	mkd_line(char *string, int size, char **doc, int flags)
+.PP
+.B
+int	mkd_generateline(char *string, int size, FILE *output, int flags)
+.PD
+.PP
+.SH DESCRIPTION
+These functions convert
+.IR Markdown (6)
+text into
+.SM HTML
+markup.
+.PP
+.I Mkd_in
+reads the text referenced by pointer to
+.B FILE
+.I input
+and returns a pointer to an
+.B MMIOT
+structure of the form expected by
+.I markdown
+and the other converters.
+.I Mkd_string
+accepts one
+.I string
+and returns a pointer to
+.BR MMIOT .
+.PP
+After such preparation,
+.I markdown
+converts
+.I doc
+and writes the result to
+.IR output ,
+while
+.I mkd_compile
+transforms
+.I document
+in-place.
+.PP
+One or more of the following
+.I flags
+(combined with
+.BR OR )
+control
+.IR markdown 's
+processing of
+.IR doc :
+.TF MKD_NOIMAGE
+.TP
+.B MKD_NOIMAGE
+Do not process
+.B ![]
+and remove
+.B <img>
+tags from the output.
+.TP
+.B MKD_NOLINKS
+Do not process
+.B []
+and remove
+.B <a>
+tags from the output.
+.TP
+.B MKD_NOPANTS
+Suppress Smartypants-style replacement of quotes, dashes, or ellipses.
+.TP
+.B MKD_STRICT
+Disable superscript and relaxed emphasis processing if configured; otherwise a no-op.
+.TP
+.B MKD_TAGTEXT
+Process as inside an
+.SM HTML
+tag: no
+.BR <em> ,
+no
+.BR <bold> ,
+no
+.SM HTML
+or
+.B []
+expansion.
+.TP
+.B MKD_NO_EXT
+Don't process pseudo-protocols (in
+.IR markdown (6)).
+.TP
+.B MKD_CDATA
+Generate code for
+.SM XML
+.B ![CDATA[...]]
+element.
+.TP
+.B MKD_NOHEADER
+Don't process Pandoc-style headers.
+.TP
+.B MKD_TABSTOP
+When reading documents, expand tabs to 4 spaces, overriding any compile-time configuration.
+.TP
+.B MKD_TOC
+Label headings for use with the
+.I mkd_generatetoc
+and
+.I mkd_toc
+functions.
+.TP
+.B MKD_1_COMPAT
+MarkdownTest_1.0 compatibility. Trim trailing spaces from first line of code blocks and disable implicit reference links (in
+.IR markdown (6)).
+.TP
+.B MKD_AUTOLINK
+Greedy
+.SM URL
+generation. When set, any
+.SM URL
+is converted to a hyperlink, even those not encased in
+.BR <> .
+.TP
+.B MKD_SAFELINK
+Don't make hyperlinks from
+.B [][]
+links that have unknown
+.SM URL
+protocol types.
+.TP
+.B MKD_NOTABLES
+Do not process the syntax extension for tables (in
+.IR markdown (6)).
+.TP
+.B MKD_EMBED
+All of
+.BR MKD_NOLINKS ,
+.BR MKD_NOIMAGE ,
+and
+.BR MKD_TAGTEXT .
+.PD
+.PP
+This implementation supports
+Pandoc-style
+headers and inline
+.SM CSS
+.B <style>
+blocks, but
+.I markdown
+does not access the data provided by these extensions.
+The following functions do, and allow other manipulations.
+.PP
+Given a pointer to
+.B MMIOT
+prepared by
+.I mkd_in
+or
+.IR mkd_string ,
+.I mkd_compile
+compiles the
+.I document
+into
+.BR <style> ,
+Pandoc, and
+.SM HTML
+sections. It accepts the
+.I flags
+described for
+.IR markdown ,
+above.
+.PP
+Once compiled, the document particulars can be read and written:
+.PP
+.I Mkd_css
+allocates a string and populates it with any
+.B <style>
+sections from the document.
+.I Mkd_generatecss
+writes any
+.B <style>
+sections to
+.IR output .
+.PP
+.I Mkd_document
+points
+.I doc
+to the
+.B MMIOT
+.IR document ,
+returning
+.IR document 's
+size.
+.PP
+.I Mkd_generatehtml
+writes the rest of the
+.I document
+to the
+.IR output .
+.PP
+.IR Mkd_doc_title ,
+.IR mkd_doc_author ,
+and
+.I mkd_doc_date
+read the contents of any Pandoc header.
+.PP
+.I Mkd_xhtmlpage
+writes an
+.SM XHTML
+page representation of the document.
+It accepts the
+.I flags
+described for
+.IR markdown ,
+above.
+.PP
+.I Mkd_toc
+.IR malloc s
+a buffer into which it writes an outline, in the form of a
+.B <ul>
+element populated with
+.BR <li> s
+each containing a link to successive headings in the
+.IR document .
+It returns the size of that string.
+.I Mkd_generatetoc
+is similar,
+but writes the outline to the
+.I output
+referenced by a pointer to
+.BR FILE .
+.PP
+.I Mkd_cleanup
+deletes a processed
+.BR MMIOT .
+.PP
+The last two functions convert a single line of markdown source, for example a page title or a signature.
+.I Mkd_line
+allocates a buffer into which it writes an
+.SM HTML
+fragment representation of the
+.IR string .
+.I Mkd_generateline
+writes the result to
+.IR output .
+.SH SOURCE
+.B /sys/src/cmd/discount
+.SH SEE ALSO
+.IR markdown (1),
+.IR markdown (6)
+.SH DIAGNOSTICS
+The
+.I mkd_in
+and
+.I mkd_string
+functions return a pointer to
+.B MMIOT
+on success, null on failure.
+.IR Markdown ,
+.IR mkd_compile ,
+.IR mkd_style ,
+and
+.I mkd_generatehtml
+return
+.B 0
+on success,
+.B -1
+otherwise.
+.SH BUGS
+Error handling is minimal at best.
+.PP
+The
+.B MMIOT
+created by
+.I mkd_string
+is deleted by the
+.I markdown
+function.
+.PP
+This is an
+.SM APE
+library.
diff --git a/Plan9/markdown.6 b/Plan9/markdown.6
new file mode 100644
index 0000000..fdefcd4
--- /dev/null
+++ b/Plan9/markdown.6
@@ -0,0 +1,543 @@
+.TH MARKDOWN 6
+.SH NAME
+Markdown \- text formatting syntax
+.SH DESCRIPTION
+Markdown
+is a text markup syntax for machine conversion to
+the more complex
+.SM HTML
+or
+.SM XHTML
+markup languages.
+It is intended to be easy to read and to write, with
+emphasis on readability.
+A Markdown-formatted document should be publishable as-is,
+in plain text, without the formatting distracting the reader.
+.PP
+The biggest source of inspiration for Markdown's
+syntax is the format of plain text email.  The markup is comprised entirely
+of punctuation characters, chosen so as to look like what they mean.
+Asterisks around a word look like
+.IR *emphasis* .
+Markdown lists look like lists.  Even
+blockquotes look like quoted passages of text, assuming the reader has
+used email.
+.PP
+.SS Block Elements
+.TF W
+.PD
+.TP
+Paragraphs and Line Breaks
+A paragraph is one or more consecutive lines of text, separated
+by one or more blank lines.  (A blank line is any line that looks like a
+blank line -- a line containing nothing but spaces or tabs is considered
+blank.) Normal paragraphs should not be indented with spaces or tabs.
+.IP
+Lines may be freely broken for readability; Markdown
+does not translate source line breaks to
+.B <br />
+tags.  To request generation of
+.B <br />
+in the output, end a line with two or more spaces, then a newline.
+.TP
+Headings
+Headings can be marked in two ways, called
+.I setext
+and
+.IR atx .
+.IP
+Setext-style headings are
+``underlined'' using equal signs (for first-level
+headings) and dashes (for second-level headings).
+.IP
+Atx-style headings use 1-6 hash characters at the start of the line,
+corresponding to
+.SM HTML
+.BR <h^(1-6)^> .
+Optional closing hashes may follow
+the heading text.
+.TP
+Blockquotes
+Lines beginning with
+.B >
+are output in blockquotes.
+Blockquotes can be nested
+by multiple levels of
+.BR >> .
+Blockquotes can contain other Markdown elements, including
+headings, lists, and code blocks.
+.TP
+Lists
+Markdown supports ordered (numbered) and unordered (bulleted) lists.
+List markers typically start at the left margin, but may be indented by
+up to three spaces.
+List markers must be followed by one or more spaces
+or a tab, then the list item text.
+A newline terminates each list item.
+.IP
+Unordered lists use asterisks, pluses, and hyphens interchangeably as
+list markers.
+.IP
+Ordered lists use integers followed by periods as list markers.
+The order of the integers is not interpreted,
+but the list should begin with
+.BR 1 .
+.IP
+If list items are separated by blank lines, Markdown will wrap each list
+item in
+.B <p>
+tags in the
+.SM HTML
+output.
+.IP
+List items may consist of multiple paragraphs.
+Each subsequent
+paragraph within a list item must be indented by either 4 spaces
+or one tab.
+To put a blockquote within a list item, the blockquote's
+.B >
+marker needs to be indented.
+To put a code block within a list item, the code block needs
+to be indented
+.I twice
+-- 8 spaces or two tabs.
+.TP
+Code Blocks
+To produce a code block, indent every line of the
+block by at least 4 spaces or 1 tab.
+A code block continues until it reaches a line that is not indented.
+.IP
+Rather than forming normal paragraphs, the lines
+of a code block are interpreted literally.
+Regular Markdown syntax is not processed within code blocks.
+Markdown wraps a code block in both
+.B <pre>
+and
+.B <code>
+tags.
+One level of indentation -- 4
+spaces or 1 tab -- is removed from each line of the code block in
+the output.
+.TP
+Horizontal Rules
+Produce a horizontal rule tag
+.RB ( <hr\ /> )
+by placing three or
+more hyphens, asterisks, or underscores on a line by themselves.
+.SS Span Elements
+.TF W
+.PD
+.TP
+Links
+Markdown supports two styles of links:
+.I inline
+and
+.IR reference .
+In both styles, the link text is delimited by square brackets
+.RB ( [] ).
+To create an inline link, use a set of regular parentheses immediately
+after the link text's closing square bracket.
+Inside the parentheses,
+put the link URL, along with an optional
+title for the link surrounded in double quotes.
+For example:
+.IP
+.EX
+	An [example](http://example.com/ "Title") inline link.
+.EE
+.IP
+Reference-style links use a second set of square brackets, inside
+which you place a label of your choosing to identify the link:
+.IP
+.EX
+	An [example][id] reference-style link.
+.EE
+.IP
+The label is then assigned a value on its own line, anywhere in the document:
+.IP
+.EX
+	[id]: http://example.com/  "Optional Title"
+.EE
+.IP
+Link label names may consist of letters, numbers, spaces, and
+punctuation.
+Labels are not case sensitive.
+An empty label bracket
+set after a reference-style link implies the link label is equivalent to
+the link text.
+A URL value can then be assigned to the link by referencing
+the link text as the label name.
+.TP
+Emphasis
+Markdown treats asterisks
+.RB ( * )
+and underscores
+.RB ( _ )
+as indicators of emphasis.
+Text surrounded with single asterisks or underscores
+will be wrapped with an
+.SM HTML
+.B <em>
+tag.
+Double asterisks or underscores generate an
+.SM HTML
+.B <strong>
+tag.
+.TP
+Code
+To indicate a span of code, wrap it with backtick quotes
+.RB ( ` ).
+Unlike a code block, a code span indicates code within a
+normal paragraph.
+To include a literal backtick character within a code span, you can use
+multiple backticks as the opening and closing delimiters:
+.IP
+.EX
+	``There is a literal backtick (`) here.``
+.EE
+.TP
+Images
+Markdown image syntax is intended to resemble that
+for links, allowing for two styles, once again
+.I inline
+and
+.IR reference .
+The syntax is as for each respective style of link, described above, but
+prefixed with an exclamation mark character
+.RB ( ! ).
+Inline image syntax looks like this:
+.IP
+.EX
+	![Alt text](/path/to/img.jpg "Optional title")
+.EE
+.IP
+That is:
+An exclamation mark;
+followed by a set of square brackets containing the `alt'
+attribute text for the image;
+followed by a set of parentheses containing the URL or path to
+the image, and an optional `title' attribute enclosed in double
+or single quotes.
+.IP
+Reference-style image syntax looks like this:
+.IP
+.EX
+	![Alt text][id]
+.EE
+.IP
+Where
+.I id
+is a label used as for reference-style URL links, described above.
+.SS Convenience
+.TF W
+.PD
+.TP
+Automatic Links
+There is a shortcut style for creating ``automatic''
+links for URLs and email addresses.
+Surround the URL
+or address with angle brackets.
+.TP
+Backslash Escapes
+Use backslash escapes to generate literal
+characters which would otherwise have special meaning in Markdown's
+formatting syntax.
+.TP
+Inline HTML
+For markup that is not covered by Markdown's
+syntax, simply use the
+.SM HTML
+directly.
+The only restrictions are that block-level
+.SM HTML
+elements -- 
+.BR <div> ,
+.BR <table> ,
+.BR <pre> ,
+.BR <p> ,
+etc. -- must be separated from surrounding
+content by blank lines, and the start and end tags of the block should
+not be indented with tabs or spaces. Markdown formatting syntax is
+not processed within block-level
+.SM HTML
+tags.
+.IP
+Span-level
+.SM HTML
+tags -- e.g. 
+.BR <span> ,
+.BR <cite> ,
+or
+.B <del>
+-- can be
+used anywhere in a Markdown
+paragraph, list item, or heading.
+It is permitted to use
+.SM HTML
+tags instead of Markdown formatting; e.g.
+.SM HTML 
+.B <a>
+or
+.B <img>
+tags instead of Markdown's
+link or image syntax.
+Unlike block-level
+.SM HTML
+tags, Markdown
+syntax
+.I is
+processed within the elements of span-level tags.
+.TP
+Automatic Special Character Escapes
+To be displayed literally in a user agent, the characters
+.B <
+and
+.B &
+must appear as escaped entities in
+.SM HTML
+source, e.g.
+.B <
+and
+.BR & .
+Markdown
+allows natural use of these characters, taking care of
+the necessary escaping.
+The ampersand part of a directly-used
+.SM HTML
+entity remains unchanged; otherwise it will be translated
+into
+.BR & .
+Inside code spans and blocks, angle brackets and
+ampersands are always encoded automatically.
+This makes it easy to use Markdown to write about
+.SM HTML
+code.
+.PP
+.SS Smarty Pants
+The
+.IR markdown (1)
+utility transforms a few plain text symbols into their typographically-fancier
+.SM HTML
+entity equivalents.
+These are extensions to the standard Markdown syntax.
+.TF W
+.PD
+.TP
+Punctuation
+Input single- and double-quotes are transformed
+into ``curly'' quote entities in the output (e.g., 
+.B 'text'
+becomes
+.BR ‘text’ ).
+Input double-dashes
+.RB ( -- )
+and triple-dashes become en- and em-dashes, respectively,
+while a series of three dots
+.RB ( ... )
+in the input becomes an ellipsis entity
+.RB ( … )
+in the
+.SM HTML
+output.
+.TP
+Symbols
+Three other transformations replace the common plain-text shorthands
+.BR (c) ,
+.BR (r) ,
+and
+.BR (tm)
+from the input with their respective
+.SM HTML
+entities. (As in
+.B (c)
+becoming
+.BR © ,
+the Copyright symbol entity.)
+.TP
+Fractions
+A small set of plain-text shorthands for fractions is recognized.
+.B 1/4
+becomes
+.BR &frac14; ,
+for example. These fraction notations are replaced with their
+.SM HTML
+entity equivalents:
+.BR 1/4 ,
+.BR 1/2 ,
+.BR 3/4 .
+.B 1/4th
+and
+.B 3/4ths
+are replaced with their entity and the indicated ordinal suffix letters.
+.PP
+Like the basic Markdown syntax, none of the ``Smarty Pants'' extensions are processed
+inside code blocks or spans.
+.PP
+.SS Discount Extensions
+.IR Markdown (1)
+recognizes some extensions to the Markdown format,
+many of them adopted or adapted from other Markdown
+interpreters or document formatting systems.
+.TF W
+.PD
+.TP
+Pandoc Headers
+If
+.I markdown
+was configured with
+.BR --enable-pandoc-header ,
+the markdown source can have a 3-line Pandoc header in the format of
+.IP
+.EX
+% Title
+% Author
+% Date
+.EE
+.IP
+whose data is available to the
+.IR mkd_doc_title ,
+.IR mkd_doc_author ,
+and
+.I mkd_doc_date
+(in
+.IR markdown (2))
+functions.
+.TP
+Embedded Stylesheets
+Stylesheets may be defined and modified in a
+.B <style>
+block.   A style block is parsed like any other block-level
+.SM HTML;
+.B <style>
+starting on column 1, raw
+.SM HTML
+(or, in this case,
+.SM CSS \)
+following it, and either ending with a
+.B </style>
+at the end of the line or at the beginning of a subsequent line.
+.IP
+Style blocks apply to the entire document regardless of where they are defined.
+.TP
+Image Dimensions
+Image specification has been extended with an argument describing image dimensions:
+.BI = height x width.
+For an image 400 pixels high and 300 wide, the new syntax is:
+.IP
+.EX
+	![Alt text](/path/to/image.jpg =400x300 "Title")
+.EE
+.TP
+Pseudo-Protocols
+Pseudo-protocols that may replace the common
+.B http:
+or
+.B mailto:
+have been added to the link syntax described above.
+.IP
+.BR abbr :
+Text following is used as the
+.B title
+attribute of an
+.B abbr
+tag wrapping the link text. So
+.B [LT](abbr:Link Text)
+gives
+.B <abbr title="Link Text">LT</abbr>.
+.IP
+.BR id :
+The link text is marked up and written to the output, wrapped with
+.B <a id=text following>
+and
+.BR </a> .
+.IP
+.BR class :
+ The link text is marked up and written to the output, wrapped with
+.B <span class=text following>
+and
+.BR </span> .
+.IP
+.BR raw :
+Text following is written to the output with no further processing.
+The link text is discarded.
+.TP
+Alphabetic Lists
+If
+.I markdown
+was configured with
+.BR --enable-alpha-list ,
+.IP
+.EX
+a. this
+b. is
+c. an alphabetic
+d. list
+.EE
+.IP
+yields an
+.SM HTML
+.B ol
+ordered list.
+.TP
+Definition Lists
+If configured with
+.BR --enable-dl-tag ,
+markup for definition lists is enabled.  A definition list item is defined as
+.IP
+.EX
+=term=
+	definition
+.EE
+.TP
+Tables
+Tables are specified with a pipe
+.RB ( | )
+and dash
+.RB ( - )
+marking. The markdown text
+.IP
+.EX
+header0|header1
+-------|-------
+  textA|textB
+  textC|textD
+.EE
+.IP
+will produce an
+.SM HTML
+.B table
+of two columns and three rows.
+A header row is designated by ``underlining'' with dashes.
+Declare a column's alignment by affixing a colon
+.RB ( : )
+to the left or right end of the dashes underlining its header.
+In the output, this
+yields the corresponding value for the
+.B align
+attribute on each
+.B td
+cell in the column.
+A colon at both ends of a column's header dashes indicates center alignment.
+.TP
+Relaxed Emphasis
+If configured with
+.BR --relaxed-emphasis ,
+the rules for emphasis are changed so that a single
+.B _
+will not count as an emphasis character in the middle of a word.
+This is useful for documenting some code where
+.B _
+appears frequently, and would normally require a backslash escape.
+.PD
+.SH SEE ALSO
+.IR markdown (1),
+.IR markdown (2)
+.PP
+http://daringfireball.net/projects/markdown/syntax/,
+``Markdown: Syntax''.
+.PP
+http://daringfireball.net/projects/smartypants/,
+``Smarty Pants''.
+.PP
+http://michelf.com/projects/php-markdown/extra/#table,
+``PHP Markdown Extra: Tables''.
diff --git a/Plan9/mkfile b/Plan9/mkfile
new file mode 100644
index 0000000..f15f987
--- /dev/null
+++ b/Plan9/mkfile
@@ -0,0 +1,37 @@
+BIN=/$objtype/bin
+CC='cc -D_BSD_EXTENSION -D_C99_SNPRINTF_EXTENSION'
+
+markdown:
+	ape/psh -c 'cd .. && make'
+
+none:V: markdown
+
+test: markdown
+	ape/psh -c 'cd ..&& make test'
+
+install: markdown
+	cp ../markdown $BIN/markdown
+
+install.progs: install
+	cp ../makepage $BIN/makepage
+	cp ../mkd2html $BIN/mkd2html
+
+install.libs: install
+	cp ../mkdio.h /sys/include/ape/mkdio.h
+	cp ../libmarkdown.a /$objtype/lib/ape/libmarkdown.a
+
+install.man: install
+	cp markdown.1 /sys/man/1/markdown
+	cp markdown.2 /sys/man/2/markdown
+	cp markdown.6 /sys/man/6/markdown
+
+installall:V: install.libs install.man install.progs
+
+config:
+	ape/psh -c 'cd .. && ./configure.sh $CONFIG'
+
+clean:
+	ape/psh -c 'cd .. && make clean'
+
+nuke:
+	ape/psh -c 'cd .. && make distclean'
diff --git a/README b/README
new file mode 100644
index 0000000..84d38e9
--- /dev/null
+++ b/README
@@ -0,0 +1,16 @@
+DISCOUNT is a implementation of John Gruber's Markdown markup
+language.   It implements, as far as I can tell, all of the
+language as described in
+<http://daringfireball.net/projects/markdown/syntax>
+and passes the Markdown test suite at
+<http://daringfireball.net/projects/downloads/MarkdownTest_1.0.zip>
+
+DISCOUNT is free software written by David Parsons <orc at pell.chi.il.us>;
+it is released under a BSD-style license that allows you to do
+as you wish with it as long as you don't attempt to claim it as
+your own work.
+
+Most of the programs included in the DISCOUNT distribution have
+manual pages describing how they work.
+
+The file INSTALL describes how to build and install discount
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..2165f8f
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+2.0.4
diff --git a/amalloc.c b/amalloc.c
new file mode 100644
index 0000000..bb20ab6
--- /dev/null
+++ b/amalloc.c
@@ -0,0 +1,111 @@
+/*
+ * debugging malloc()/realloc()/calloc()/free() that attempts
+ * to keep track of just what's been allocated today.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define MAGIC 0x1f2e3d4c
+
+struct alist { int magic, size; struct alist *next, *last; };
+
+static struct alist list =  { 0, 0, 0, 0 };
+
+static int mallocs=0;
+static int reallocs=0;
+static int frees=0;
+
+void *
+acalloc(int size, int count)
+{
+    struct alist *ret = calloc(size + sizeof(struct alist), count);
+
+    if ( ret ) {
+	ret->magic = MAGIC;
+	ret->size = size * count;
+	if ( list.next ) {
+	    ret->next = list.next;
+	    ret->last = &list;
+	    ret->next->last = ret;
+	    list.next = ret;
+	}
+	else {
+	    ret->last = ret->next = &list;
+	    list.next = list.last = ret;
+	}
+	++mallocs;
+	return ret+1;
+    }
+    return 0;
+}
+
+
+void*
+amalloc(int size)
+{
+    return acalloc(size,1);
+}
+
+
+void
+afree(void *ptr)
+{
+    struct alist *p2 = ((struct alist*)ptr)-1;
+
+    if ( p2->magic == MAGIC ) {
+	p2->last->next = p2->next;
+	p2->next->last = p2->last;
+	++frees;
+	free(p2);
+    }
+    else
+	free(ptr);
+}
+
+
+void *
+arealloc(void *ptr, int size)
+{
+    struct alist *p2 = ((struct alist*)ptr)-1;
+    struct alist save;
+
+    if ( p2->magic == MAGIC ) {
+	save.next = p2->next;
+	save.last = p2->last;
+	p2 = realloc(p2, sizeof(*p2) + size);
+
+	if ( p2 ) {
+	    p2->size = size;
+	    p2->next->last = p2;
+	    p2->last->next = p2;
+	    ++reallocs;
+	    return p2+1;
+	}
+	else {
+	    save.next->last = save.last;
+	    save.last->next = save.next;
+	    return 0;
+	}
+    }
+    return realloc(ptr, size);
+}
+
+
+void
+adump()
+{
+    struct alist *p;
+
+
+    for ( p = list.next; p && (p != &list); p = p->next ) {
+	fprintf(stderr, "allocated: %d byte%s\n", p->size, (p->size==1) ? "" : "s");
+	fprintf(stderr, "           [%.*s]\n", p->size, (char*)(p+1));
+    }
+
+    if ( getenv("AMALLOC_STATISTICS") ) {
+	fprintf(stderr, "%d malloc%s\n", mallocs, (mallocs==1)?"":"s");
+	fprintf(stderr, "%d realloc%s\n", reallocs, (reallocs==1)?"":"s");
+	fprintf(stderr, "%d free%s\n", frees, (frees==1)?"":"s");
+    }
+}
diff --git a/amalloc.h b/amalloc.h
new file mode 100644
index 0000000..43ca985
--- /dev/null
+++ b/amalloc.h
@@ -0,0 +1,29 @@
+/*
+ * debugging malloc()/realloc()/calloc()/free() that attempts
+ * to keep track of just what's been allocated today.
+ */
+#ifndef AMALLOC_D
+#define AMALLOC_D
+
+#include "config.h"
+
+#ifdef USE_AMALLOC
+
+extern void *amalloc(int);
+extern void *acalloc(int,int);
+extern void *arealloc(void*,int);
+extern void afree(void*);
+extern void adump();
+
+#define malloc	amalloc
+#define	calloc	acalloc
+#define realloc	arealloc
+#define free	afree
+
+#else
+
+#define adump()	(void)1
+
+#endif
+
+#endif/*AMALLOC_D*/
diff --git a/basename.c b/basename.c
new file mode 100644
index 0000000..237022a
--- /dev/null
+++ b/basename.c
@@ -0,0 +1,43 @@
+/*
+ * mkdio -- markdown front end input functions
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "mkdio.h"
+#include "cstring.h"
+#include "amalloc.h"
+
+static char *
+e_basename(const char *string, const int size, void *context)
+{
+    char *ret;
+    char *base = (char*)context;
+    
+    if ( base && string && (*string == '/') && (ret=malloc(strlen(base)+size+2)) ) {
+	strcpy(ret, base);
+	strncat(ret, string, size);
+	return ret;
+    }
+    return 0;
+}
+
+static void
+e_free(char *string, void *context)
+{
+    if ( string ) free(string);
+}
+
+void
+mkd_basename(MMIOT *document, char *base)
+{
+    mkd_e_url(document, e_basename);
+    mkd_e_data(document, base);
+    mkd_e_free(document, e_free);
+}
diff --git a/configure.inc b/configure.inc
new file mode 100755
index 0000000..5a8dac2
--- /dev/null
+++ b/configure.inc
@@ -0,0 +1,1660 @@
+#   @(#) configure.inc 1.42@(#)
+#   Copyright (c) 1999-2007 David Parsons. All rights reserved.
+#   
+#   Redistribution and use in source and binary forms, with or without
+#   modification, are permitted provided that the following conditions
+#   are met:
+#  1. Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+#  2. 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.
+#  3. My name may not be used to endorse or promote products derived
+#     from this software without specific prior written permission.
+#     
+#  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``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 DAVID
+#  PARSONS 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.
+#
+
+
+#
+# this preamble code is executed when this file is sourced and it picks
+# interesting things off the command line.
+#
+ac_default_path="/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin"
+
+ac_standard="--src=DIR		where the source lives (.)
+--prefix=DIR		where to install the final product (/usr/local)
+--execdir=DIR		where to put executables (prefix/bin)
+--sbindir=DIR		where to put static executables (prefix/sbin)
+--confdir=DIR		where to put configuration information (/etc)
+--libdir=DIR		where to put libraries (prefix/lib)
+--libexecdir=DIR	where to put private executables
+--mandir=DIR		where to put manpages"
+
+__fail=exit
+
+if dirname B/A 2>/dev/null >/dev/null; then
+__ac_dirname() {
+    dirname "$1"
+}
+else
+__ac_dirname() {
+    echo "$1" | sed -e 's:/[^/]*$::'
+}
+fi
+
+ac_progname=$0
+ac_configure_command=
+Q=\'
+for x in "$@"; do
+    ac_configure_command="$ac_configure_command $Q$x$Q"
+done
+# ac_configure_command="$*"
+
+__d=`__ac_dirname "$ac_progname"`
+if [ "$__d" = "$ac_progname" ]; then
+    AC_SRCDIR=`pwd`
+else
+    AC_SRCDIR=`cd $__d;pwd`
+fi
+
+__ac_dir() {
+    if test -d "$1"; then
+	(cd "$1";pwd)
+    else
+	echo "$1";
+    fi
+}
+
+#
+# echo w/o newline
+#
+echononl()
+{
+    ${ac_echo:-echo} "${@}$ac_echo_nonl"
+}
+
+#
+# log something to the terminal and to a logfile.
+#
+LOG () {
+    echo "$@"
+    echo "$@" 1>&5
+}
+
+#
+# log something to the terminal without a newline, and to a logfile with
+# a newline
+#
+LOGN () {
+    echononl "$@" 1>&5
+    echo "$@"
+}
+
+#
+# log something to the terminal
+#
+TLOG () {
+    echo "$@" 1>&5
+}
+
+#
+# log something to the terminal, no newline
+#
+TLOGN () {
+    echononl "$@" 1>&5
+}
+
+
+#
+# AC_CONTINUE tells configure not to bomb if something fails, but to
+# continue blithely along
+#
+AC_CONTINUE () {
+    __fail="return"
+}
+
+
+#
+# generate a .o file from sources
+#
+__MAKEDOTO() {
+    AC_PROG_CC
+
+    if $AC_CC -c -o /tmp/doto$$.o "$@" $AC_LIBS 2>/tmp/doto$$.err; then
+	rm -f /tmp/doto$$.o /tmp/doto$$.err
+	TLOG " (found)"
+	return 0
+    fi
+    rm -f /tmp/doto$$.o
+    TLOG " (not found)"
+    echo "test failed:  command was $AC_CC -c -o /tmp/doto$$.o" "$@" $AC_LIBS
+    echo "output:"
+    cat /tmp/doto$$.err
+    rm -f /tmp/doto$$.err
+    echo "offending sources:"
+    for x in "$@"; do
+	echo "$x:"
+	cat $x
+    done
+    return 1
+}
+
+
+#
+# Emulate gnu autoconf's AC_CHECK_HEADERS() function
+#
+AC_CHECK_HEADERS () {
+
+    echo "/* AC_CHECK_HEADERS */" > /tmp/ngc$$.c
+    for hdr in $*; do
+	echo "#include <$hdr>" >> /tmp/ngc$$.c
+    done
+    echo "main() { }" >> /tmp/ngc$$.c
+
+    LOGN "looking for header $hdr"
+
+    if __MAKEDOTO /tmp/ngc$$.c; then
+	AC_DEFINE 'HAVE_'`echo $hdr | $AC_UPPERCASE | tr './' '_'` 1
+	rc=0
+    else
+	rc=1
+    fi
+    rm -f /tmp/ngc$$.c
+    return $rc
+}
+
+
+#
+# emulate GNU autoconf's AC_CHECK_FUNCS function
+#
+AC_CHECK_FUNCS () {
+    AC_PROG_CC
+
+    B=`echo "$1" | sed -e 's/(.*)//'`
+
+    case "$B" in
+    "$1") F="$1()" ;;
+    *)    F="$1" ;;
+    esac
+
+    shift
+    rm -f /tmp/ngc$$.c
+
+    while [ "$1" ]; do
+	echo "#include <$1>" >> /tmp/ngc$$.c
+	shift
+    done
+
+    cat >> /tmp/ngc$$.c << EOF
+main()
+{
+
+    $F;
+}
+EOF
+
+    LOGN "looking for the $B function"
+
+    if $AC_CC -o /tmp/ngc$$ /tmp/ngc$$.c $LIBS; then
+	AC_DEFINE `echo ${2:-HAVE_$B} | $AC_UPPERCASE` 1
+	TLOG " (found)"
+	rc=0
+    else
+	echo "offending command was:"
+	cat /tmp/ngc$$.c
+	echo "$AC_CC -o /tmp/ngc$$ /tmp/ngc$$.c $LIBS"
+	TLOG " (not found)"
+	rc=1
+    fi
+    rm -f /tmp/ngc$$.c /tmp/ngc$$
+    return $rc
+}
+
+
+#
+# check to see if some structure exists
+#
+# usage: AC_CHECK_STRUCT structure {include ...}
+#
+AC_CHECK_STRUCT () {
+    struct=$1
+    shift
+
+    rm -f /tmp/ngc$$.c
+
+    for include in $*; do
+	echo "#include <$include>" >> /tmp/ngc$$.c
+    done
+
+    cat >> /tmp/ngc$$.c << EOF
+main()
+{
+    struct $struct foo;
+}
+EOF
+
+    LOGN "looking for struct $struct"
+
+    if __MAKEDOTO /tmp/ngc$$.c; then
+	AC_DEFINE HAVE_STRUCT_`echo ${struct} | $AC_UPPERCASE`
+	rc=0
+    else
+	rc=1
+    fi
+    rm -f /tmp/ngc$$.c
+    return $rc
+}
+
+
+#
+# check to see if some type exists
+#
+# usage: AC_CHECK_TYPE type {include ...}
+#
+AC_CHECK_TYPE () {
+    type=$1
+    shift
+
+    rm -f /tmp/ngc$$.c
+
+    for include in $*; do
+	echo "#include <$include>" >> /tmp/ngc$$.c
+    done
+
+    cat >> /tmp/ngc$$.c << EOF
+main()
+{
+    $type foo;
+}
+EOF
+
+    LOGN "looking for $type type"
+
+    if __MAKEDOTO /tmp/ngc$$.c; then
+	AC_DEFINE HAVE_TYPE_`echo ${type} | $AC_UPPERCASE`
+	rc=0
+    else
+	rc=1
+    fi
+    rm -f /tmp/ngc$$.c
+    return $rc
+}
+
+
+#
+# check to see if some structure contains a field
+#
+# usage: AC_CHECK_FIELD structure field {include ...}
+#
+AC_CHECK_FIELD () {
+
+    struct=$1
+    field=$2
+    shift 2
+
+    rm -f /tmp/ngc$$.c
+
+    for include in $*;do
+	echo "#include <$include>" >> /tmp/ngc$$.c
+    done
+
+    cat >> /tmp/ngc$$.c << EOF
+main()
+{
+    struct $struct foo;
+
+    foo.$field;
+}
+EOF
+
+    LOGN "checking that struct $struct has a $field field"
+
+    if __MAKEDOTO /tmp/ngc$$.c; then
+	AC_DEFINE HAVE_`echo ${struct}_$field | $AC_UPPERCASE`
+	rc=0
+    else
+	rc=1
+    fi
+    rm -f /tmp/ngc$$.c
+    return $rc
+}
+
+
+#
+# check that the C compiler works
+#
+AC_PROG_CC () {
+    test "$AC_CC" && return 0
+
+    cat > /tmp/ngc$$.c << \EOF
+#include <stdio.h>
+main()
+{
+    puts("hello, sailor");
+}
+EOF
+
+    TLOGN "checking the C compiler"
+
+    unset AC_CFLAGS AC_LDFLAGS
+
+    if [ "$CC" ] ; then
+	AC_CC="$CC"
+    elif [ "$WITH_PATH" ]; then
+	AC_CC=`acLookFor cc`
+    elif [ "`acLookFor cc`" ]; then
+	# don't specify the full path if the user is looking in their $PATH
+	# for a C compiler.
+	AC_CC=cc
+    fi
+
+    # finally check for POSIX c89
+    test "$AC_CC" || AC_CC=`acLookFor c89`
+
+    if [ ! "$AC_CC" ]; then
+	TLOG " (no C compiler found)"
+	$__fail 1
+    fi
+    echo "checking out the C compiler"
+
+    $AC_CC -o /tmp/ngc$$ /tmp/ngc$$.c
+    status=$?
+
+    TLOGN " ($AC_CC)"
+    if [ $status -eq 0 ]; then
+	if $AC_CC -v 2>&1 | grep 'gcc version' >/dev/null; then
+	    TLOG " oh ick, it looks like gcc"
+	    IS_BROKEN_CC=T
+	else
+	    TLOG " ok"
+	fi
+
+	# check that the CFLAGS and LDFLAGS aren't bogus
+
+	unset AC_CFLAGS AC_LDFLAGS
+
+	if [ "$CFLAGS" ]; then
+	    test "$CFLAGS" && echo "validating CFLAGS=${CFLAGS}"
+	    if $AC_CC $CFLAGS -o /tmp/ngc$$.o /tmp/ngc$$.c ; then
+		AC_CFLAGS=${CFLAGS:-"-g"}
+		test "$CFLAGS" && echo "CFLAGS=\"${CFLAGS}\" are okay"
+	    elif [ "$CFLAGS" ]; then
+		echo "ignoring bogus CFLAGS=\"${CFLAGS}\""
+	    fi
+	else
+	    AC_CFLAGS=-g
+	fi
+	if [ "$LDFLAGS" ]; then
+	    test "$LDFLAGS" && echo "validating LDFLAGS=${LDFLAGS}"
+	    if $AC_CC $LDFLAGS -o /tmp/ngc$$ /tmp/ngc$$.o; then
+		AC_LDFLAGS=${LDFLAGS:-"-g"}
+		test "$LDFLAGS" && TLOG "LDFLAGS=\"${LDFLAGS}\" are okay"
+	    elif [ "$LDFLAGS" ]; then
+		TLOG "ignoring bogus LDFLAGS=\"${LDFLAGS}\""
+	    fi
+	else
+	    AC_LDFLAGS=${CFLAGS:-"-g"}
+	fi
+    else
+	AC_FAIL " does not compile code properly"
+    fi
+
+    AC_SUB 'CC' "$AC_CC"
+
+    rm -f /tmp/ngc$$ /tmp/ngc$$.c /tmp/ngc$$.o
+
+    return $status
+}
+
+
+#
+# acLookFor actually looks for a program, without setting anything.
+#
+acLookFor () {
+    path=${AC_PATH:-$ac_default_path}
+    case "X$1" in
+    X-[rx]) __mode=$1
+	    shift
+	    ;;
+    *)	    __mode=-x
+	    ;;
+    esac
+    oldifs="$IFS"
+    for program in $*; do
+	IFS=":"
+	for x in $path; do
+	    if [ $__mode $x/$program -a -f $x/$program ]; then
+		echo $x/$program
+		break 2
+	    fi
+	done
+    done
+    IFS="$oldifs"
+    unset __mode
+}
+
+
+#
+# check that a program exists and set its path
+#
+MF_PATH_INCLUDE () {
+    SYM=$1; shift
+
+    case X$1 in
+    X-[rx]) __mode=$1
+	    shift
+	    ;;
+    *)      unset __mode
+	    ;;
+    esac
+
+    TLOGN "looking for $1"
+
+    DEST=`acLookFor $__mode $*`
+
+    __sym=`echo "$SYM" | $AC_UPPERCASE`
+    if [ "$DEST" ]; then
+	TLOG " ($DEST)"
+	echo "$1 is $DEST"
+	AC_MAK $SYM
+	AC_DEFINE PATH_$__sym \""$DEST"\"
+	AC_SUB $__sym "$DEST"
+	eval CF_$SYM=$DEST
+	return 0
+    else
+	#AC_SUB $__sym ''
+	echo "$1 is not found"
+	TLOG " (not found)"
+	return 1
+    fi
+}
+
+#
+# AC_INIT starts the ball rolling
+#
+# After AC_INIT, fd's 1 and 2 point to config.log
+# and fd 5 points to what used to be fd 1
+#
+AC_INIT () {
+    __config_files="config.cmd config.sub config.h config.mak config.log"
+    rm -f $__config_files
+    __cwd=`pwd`
+    exec 5>&1 1>$__cwd/config.log 2>&1
+    AC_CONFIGURE_FOR=__AC_`echo $1 | sed -e 's/\..$//' | $AC_UPPERCASE  | tr ' ' '_'`_D
+
+    # check to see whether to use echo -n or echo ...\c
+    #
+    echo -n hello > $$
+    echo world >> $$
+    if grep "helloworld" $$ >/dev/null; then
+	ac_echo="echo -n"
+	echo "[echo -n] works"
+    else
+	ac_echo="echo"
+	echo 'hello\c' > $$
+	echo 'world' >> $$
+	if grep "helloworld" $$ >/dev/null; then
+	    ac_echo_nonl='\c'
+	    echo "[echo ...\\c] works"
+	fi
+    fi
+    rm -f $$
+
+    LOG "Configuring for [$1]"
+
+    cat > $__cwd/config.h << EOF
+/*
+ * configuration for $1${2:+" ($2)"}, generated `date`
+ * by ${LOGNAME:-`whoami`}@`hostname`
+ */
+#ifndef $AC_CONFIGURE_FOR
+#define $AC_CONFIGURE_FOR 1
+
+
+EOF
+
+    unset __share
+    if [ -d $AC_PREFIX/share/man ]; then
+	for t in 1 2 3 4 5 6 7 8 9; do
+	    if [ -d $AC_PREFIX/share/man/man$t ]; then
+		__share=/share
+	    elif [ -d $AC_PREFIX/share/man/cat$t ]; then
+		__share=/share
+	    fi
+	done
+    else
+	__share=
+    fi
+
+    if [ -d $AC_PREFIX/libexec ]; then
+	__libexec=libexec
+    else
+	__libexec=lib
+    fi
+
+
+    AC_PREFIX=${AC_PREFIX:-/usr/local}
+    AC_EXECDIR=${AC_EXECDIR:-$AC_PREFIX/bin}
+    AC_SBINDIR=${AC_SBINDIR:-$AC_PREFIX/sbin}
+    AC_LIBDIR=${AC_LIBDIR:-$AC_PREFIX/lib}
+    AC_MANDIR=${AC_MANDIR:-$AC_PREFIX$__share/man}
+    AC_LIBEXEC=${AC_LIBEXEC:-$AC_PREFIX/$__libexec}
+    AC_CONFDIR=${AC_CONFDIR:-/etc}
+
+    AC_PATH=${WITH_PATH:-$PATH}
+    AC_PROG_CPP
+    AC_PROG_INSTALL
+
+    ac_os=`uname -s`
+    _os=`echo $ac_os | $AC_UPPERCASE | sed -e 's/[^A-Z0-9_].*$//'`
+    AC_DEFINE OS_$_os	1
+    eval OS_${_os}=1
+    unset _os
+}
+
+
+#
+# AC_LIBRARY checks to see if a given library exists and contains the
+# given function.
+# usage: AC_LIBRARY function library [alternate ...]
+#
+AC_LIBRARY() {
+    SRC=$1
+    shift
+
+    __acllibs=
+    __aclhdrs=
+
+    for x in "$@"; do
+	case X"$x" in
+	X-l*) __acllibs="$__acllibs $x" ;;
+	*)    __aclhdrs="$__aclhdrs $x" ;;
+	esac
+    done
+
+    # first see if the function can be found in any of the
+    # current libraries
+    AC_QUIET AC_CHECK_FUNCS $SRC $__aclhdrs && return 0
+
+    # then search through the list of libraries
+    __libs="$LIBS"
+    for x in $__acllibs; do
+	LIBS="$__libs $x"
+	if AC_QUIET AC_CHECK_FUNCS $SRC $__aclhdrs; then
+	    AC_LIBS="$AC_LIBS $x"
+	    return 0
+	fi
+    done
+    return 1
+}
+
+
+#
+# AC_PROG_LEX checks to see if LEX exists, and if it's lex or flex.
+#
+AC_PROG_LEX() {
+    TLOGN "looking for lex "
+
+    DEST=`acLookFor lex`
+    if [ "$DEST" ]; then
+	AC_MAK LEX
+	AC_DEFINE PATH_LEX \"$DEST\"
+	AC_SUB 'LEX' "$DEST"
+	echo "lex is $DEST"
+    else
+	DEST=`acLookFor flex`
+	if [ "$DEST" ]; then
+	    AC_MAK FLEX
+	    AC_DEFINE 'LEX' \"$DEST\"
+	    AC_SUB 'LEX', "$DEST"
+	    echo "lex is $DEST"
+	else
+	    AC_SUB LEX ''
+	    echo "neither lex or flex found"
+	    TLOG " (not found)"
+	    return 1
+	fi
+    fi
+
+    if AC_LIBRARY yywrap -ll -lfl; then
+	TLOG "($DEST)"
+	return 0
+    fi
+    TLOG "(no lex library found)"
+    return 1
+}
+
+
+#
+# AC_PROG_YACC checks to see if YACC exists, and if it's bison or
+# not.
+#
+AC_PROG_YACC () {
+
+    TLOGN "looking for yacc "
+
+    DEST=`acLookFor yacc`
+    if [ "$DEST" ]; then
+	AC_MAK YACC
+	AC_DEFINE PATH_YACC \"$DEST\"
+	AC_SUB 'YACC' "$DEST"
+	TLOG "($DEST)"
+	echo "yacc is $DEST"
+    else
+	DEST=`acLookFor bison`
+	if [ "$DEST" ]; then
+	    AC_MAK BISON
+	    AC_DEFINE 'YACC' \"$DEST\"
+	    AC_SUB 'YACC' "$DEST -y"
+	    echo "yacc is $DEST -y"
+	    TLOG "($DEST -y)"
+	else
+	    AC_SUB 'YACC' ''
+	    echo "neither yacc or bison found"
+	    TLOG " (not found)"
+	    return 1
+	fi
+    fi
+    return 0
+}
+
+
+#
+# AC_PROG looks for a program
+#
+AC_PROG () {
+    PN=`basename $1 | $AC_UPPERCASE | tr -dc $AC_UPPER_PAT`
+
+    if set | grep -v PROG_$PN >/dev/null; then
+	TLOGN "looking for $1"
+	DEST=`acLookFor $1`
+	if [ "$DEST" ]; then
+	    eval PROG_$PN="$DEST"
+	    AC_SUB $PN $DEST
+	    TLOG " ($DEST)"
+	    return 0
+	fi
+	AC_SUB $PN true
+	TLOG " (not found)"
+	return 1
+    fi
+}
+
+
+#
+# AC_PROG_LN_S checks to see if ln exists, and, if so, if ln -s works
+#
+AC_PROG_LN_S () {
+    test "$AC_FIND_PROG" || AC_PROG_FIND
+
+    test "$AC_FIND_PROG" || return 1
+    
+    TLOGN "looking for \"ln -s\""
+    DEST=`acLookFor ln`
+
+    if [ "$DEST" ]; then
+	rm -f /tmp/b$$
+	$DEST -s /tmp/a$$ /tmp/b$$
+	if [ "`$AC_FIND_PROG /tmp/b$$ -type l -print`" ]; then
+	    TLOG " ($DEST)"
+	    echo "$DEST exists, and ln -s works"
+	    PROG_LN_S="$DEST -s"
+	    AC_SUB 'LN_S' "$DEST -s"
+	    rm -f /tmp/b$$
+	else
+	    AC_SUB 'LN_S' ''
+	    TLOG " ($DEST exists, but -s does not seem to work)"
+	    echo "$DEST exists, but ln -s doesn't seem to work"
+	    rm -f /tmp/b$$
+	    return 1
+	fi
+    else
+	AC_SUB 'LN_S' ''
+	echo "ln not found"
+	TLOG " (not found)"
+	return 1
+    fi
+}
+
+
+#
+# AC_PROG_FIND looks for the find program and sets the FIND environment
+# variable
+#
+AC_PROG_FIND () {
+    if test -z "$AC_FIND_PROG"; then
+	MF_PATH_INCLUDE FIND find
+	rc=$?
+	AC_FIND_PROG=$DEST
+	return $rc
+    fi
+    return 0
+}
+
+
+#
+# AC_PROG_AWK looks for the awk program and sets the AWK environment
+# variable
+#
+AC_PROG_AWK () {
+    if test -z "$AC_AWK_PROG"; then
+	MF_PATH_INCLUDE AWK awk
+	rc=$?
+	AC_AWK_PROG=$DEST
+	return $rc
+    fi
+    return 0
+}
+
+
+#
+# AC_PROG_SED looks for the sed program and sets the SED environment
+# variable
+#
+AC_PROG_SED () {
+    if test -z "$AC_SED_PROG"; then
+	MF_PATH_INCLUDE SED sed
+	rc=$?
+	AC_SED_PROG=$DEST
+	return $rc
+    fi
+    return 0
+}
+
+
+#
+# AC_HEADER_SYS_WAIT looks for sys/wait.h
+#
+AC_HEADER_SYS_WAIT () {
+    AC_CHECK_HEADERS sys/wait.h || return 1
+}
+
+#
+# AC_TYPE_PID_T checks to see if the pid_t type exists
+#
+AC_TYPE_PID_T () {
+
+    AC_CHECK_TYPE pid_t sys/types.h
+    return $?
+}
+
+
+#
+# AC_C_CONST checks to see if the compiler supports the const keyword
+#
+AC_C_CONST () {
+    cat > /tmp/pd$$.c << EOF
+const char me=1;
+EOF
+    LOGN "checking for \"const\" keyword"
+
+    if __MAKEDOTO /tmp/pd$$.c; then
+	rc=0
+    else
+	AC_DEFINE 'const' '/**/'
+	rc=1
+    fi
+    rm -f /tmp/pd$$.c
+    return $rc
+}
+
+
+#
+# AC_C_VOLATILE checks to see if the compiler supports the volatile keyword
+#
+AC_C_VOLATILE () {
+    cat > /tmp/pd$$.c << EOF
+f() { volatile char me=1; }
+EOF
+    LOGN "checking for \"volatile\" keyword"
+
+    if __MAKEDOTO /tmp/pd$$.c; then
+	rc=0
+    else
+	AC_DEFINE 'volatile' '/**/'
+	rc=1
+    fi
+    rm -f /tmp/pd$$.c
+    return $rc
+}
+
+
+#
+# AC_SCALAR_TYPES checks to see if the compiler can generate 2 and 4 byte ints.
+#
+AC_SCALAR_TYPES () {
+    cat > /tmp/pd$$.c << EOF
+#include <stdio.h>
+#include <string.h>
+
+int pound_define = 1;
+
+void
+say(char *w, char *v)
+{
+    printf(pound_define ? "#define %s %s\n"
+			: "s:@%s@:%s:g\n", w, v);
+}
+
+void
+main(argc, argv)
+char **argv;
+{
+    unsigned long v_long;
+    unsigned int v_int;
+    unsigned short v_short;
+
+    if ( argc > 1 && strcmp(argv[1], "sub") == 0 )
+	pound_define = 0;
+	
+    if (sizeof v_long == 4)
+	say("DWORD", "unsigned long");
+    else if (sizeof v_int == 4)
+	say("DWORD", "unsigned int");
+    else
+	exit(1);
+
+    if (sizeof v_int == 2)
+	say("WORD", "unsigned int");
+    else if (sizeof v_short == 2)
+	say("WORD", "unsigned short");
+    else
+	exit(2);
+    say("BYTE", "unsigned char");
+    exit(0);
+}
+EOF
+    rc=1
+    LOGN "defining WORD & DWORD scalar types"
+    if $AC_CC /tmp/pd$$.c -o /tmp/pd$$; then
+	while [ "$1" ]; do
+	    case "$1" in
+	    sub)if /tmp/pd$$ sub >> $__cwd/config.sub; then
+		    rc=0
+		fi;;
+	    *)  if /tmp/pd$$ >> $__cwd/config.h; then
+		    rc=0
+		fi ;;
+	    esac
+	    shift
+	done
+	if [ "$rc" != 0 ]; then
+	    if /tmp/pd$$ >> $__cwd/config.h; then
+		rc=1
+	    fi
+	fi
+    fi
+    case "$rc" in
+    0) TLOG "" ;;
+    *) AC_FAIL " ** FAILED **" ;;
+    esac
+    rm -f /tmp/pd$$ /tmp/pd$$.c
+}
+
+
+#
+# AC_OUTPUT generates makefiles from makefile.in's
+#
+AC_OUTPUT () {
+    cd $__cwd
+    AC_SUB 'LIBS'    "$AC_LIBS"
+    AC_SUB 'CONFIGURE_FILES' "$__config_files"
+    AC_SUB 'GENERATED_FILES' "$*"
+    AC_SUB 'CFLAGS'  "$AC_CFLAGS"
+    AC_SUB 'LDFLAGS' "$AC_LDFLAGS"
+    AC_SUB 'srcdir'  "$AC_SRCDIR"
+    AC_SUB 'prefix'  "$AC_PREFIX"
+    AC_SUB 'exedir'  "$AC_EXECDIR"
+    AC_SUB 'sbindir' "$AC_SBINDIR"
+    AC_SUB 'libdir'  "$AC_LIBDIR"
+    AC_SUB 'libexec' "$AC_LIBEXEC"
+    AC_SUB 'confdir' "$AC_CONFDIR"
+    AC_SUB 'mandir'  "$AC_MANDIR"
+
+    if echo "$__config_files" | grep -v librarian.sh >/dev/null; then
+	# write a librarian that works with static libraries
+	AC_PROG_LN_S
+	AC_PROG ar
+	AC_PROG ranlib
+	AC_SUB LD_LIBRARY_PATH HERE
+	AC
+	__config_files="$__config_files librarian.sh"
+	cat > librarian.sh << EOF
+#! /bin/sh
+#
+#  Build static libraries, hiding (some) ickiness from the makefile
+
+ACTION=\$1; shift
+LIBRARY=\$1; shift
+VERSION=\$1; shift
+
+case "\$ACTION" in
+make)   ${PROG_AR} crv \$LIBRARY.a "\$@"
+	${PROG_RANLIB} \$LIBRARY.a
+	rm -f \$LIBRARY
+	${PROG_LN_S} \$LIBRARY.a \$LIBRARY
+	;;
+files)  echo "\${LIBRARY}.a"
+	;;
+install)$PROG_INSTALL -m 644 \${LIBRARY}.a \$1
+	;;
+esac
+EOF
+    fi
+    chmod +x librarian.sh
+
+    if [ -r config.sub ]; then
+	test "$AC_SED_PROG" || AC_PROG_SED
+	test "$AC_SED_PROG" || return 1
+
+	echo                                   >> config.h
+	echo "#endif/* ${AC_CONFIGURE_FOR} */" >> config.h
+
+	rm -f config.cmd
+	Q=\'
+	cat - > config.cmd << EOF
+#! /bin/sh
+${CC:+CC=${Q}${CC}${Q}} ${CFLAGS:+CFLAGS=${Q}${CFLAGS}${Q}} $ac_progname $ac_configure_command
+EOF
+	chmod +x config.cmd
+
+	__d=$AC_SRCDIR
+	for makefile in $*;do
+	    if test -r $__d/${makefile}.in; then
+		LOG "generating $makefile"
+		./config.md `__ac_dirname ./$makefile` 2>/dev/null
+		$AC_SED_PROG -f config.sub < $__d/${makefile}.in > $makefile
+		__config_files="$__config_files $makefile"
+	    else
+		LOG "WARNING: ${makefile}.in does not exist!"
+	    fi
+	done
+	unset __d
+
+    else
+	echo 
+    fi
+}
+
+#
+# AC_CHECK_FLOCK checks to see if flock() exists and if the LOCK_NB argument
+# works properly.
+#
+AC_CHECK_FLOCK() {
+
+    AC_CHECK_HEADERS sys/types.h sys/file.h fcntl.h
+
+    cat << EOF > $$.c
+#include <stdio.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+main()
+{
+    int x = open("$$.c", O_RDWR, 0666);
+    int y = open("$$.c", O_RDWR, 0666);
+
+    if (flock(x, LOCK_EX) != 0)
+	exit(1);
+    if (flock(y, LOCK_EX|LOCK_NB) == 0)
+	exit(1);
+    exit(0);
+}
+EOF
+
+    LOGN "checking flock() sanity"
+    HAS_FLOCK=0
+    if $AC_CC -o flock $$.c ; then
+	if ./flock ; then
+	    LOG " (good)"
+	    HAS_FLOCK=1
+	    AC_DEFINE HAS_FLOCK
+	else
+	    LOG " (bad)"
+	fi
+    else
+	LOG " (not found)"
+    fi
+
+    rm -f flock $$.c
+
+    case "$HAS_FLOCK" in
+    0) return 1 ;;
+    *) return 0 ;;
+    esac
+}
+
+
+#
+# AC_CHECK_RESOLVER finds out whether the berkeley resolver is
+# present on this system.
+#
+AC_CHECK_RESOLVER () {
+    AC_PROG_CC
+
+    TLOGN "looking for the Berkeley resolver library"
+
+    __ACR_rc=0
+
+    cat > /tmp/ngc$$.c << EOF
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+
+main()
+{
+    char bfr[256];
+
+    res_init();
+    res_query("hello", C_IN, T_A, bfr, sizeof bfr);
+}
+EOF
+
+    if $AC_CC -o /tmp/ngc$$ /tmp/ngc$$.c; then
+	TLOG " (found)"
+    elif $AC_CC -o /tmp/ngc$$ /tmp/ngc$$.c -lresolv; then
+	TLOG " (found, needs -lresolv)"
+	AC_LIBS="$AC_LIBS -lresolv"
+    elif $AC_CC -DBIND_8_COMPAT -o /tmp/ngc$$ /tmp/ngc$$.c; then
+	TLOG " (found, needs BIND_8_COMPAT)"
+	AC_DEFINE BIND_8_COMPAT 1
+    elif $AC_CC -DBIND_8_COMPAT -o /tmp/ngc$$ /tmp/ngc$$.c -lresolv; then
+	TLOG " (found, needs BIND_8_COMPAT & -lresolv)"
+	AC_DEFINE BIND_8_COMPAT 1
+    else
+	TLOG " (not found)"
+	__ACR_rc=1
+    fi
+    rm -f /tmp/ngc$$.c
+    return $__ACR_rc
+}
+
+
+#
+# AC_CHECK_ALLOCA looks for alloca
+#
+AC_CHECK_ALLOCA () {
+
+    AC_PROG_CC
+    AC_CHECK_HEADERS stdlib.h
+
+    cat - > /tmp/ngc$$.c << EOF
+#if T
+# include <alloca.h>
+#else
+# include <stdlib.h>
+#endif
+main()
+{
+	alloca(10);
+}
+EOF
+
+    LOGN "looking for the alloca function"
+    if $AC_CC -DT /tmp/ngc$$.c -o /tmp/ngc$$; then
+	AC_DEFINE 'HAVE_ALLOCA_H' 1
+	status=0
+	TLOG " (found in alloca.h)"
+    elif $AC_CC /tmp/ngc$$.c -o /tmp/ngc$$; then
+	TLOG " (found)"
+	status=0
+    else
+	TLOG " (not found)"
+	status=1
+    fi
+    rm -f /tmp/ngc$$.c /tmp/ngc
+    return $status
+
+}
+
+
+#
+# AC_CHECK_BASENAME looks for a copy of basename that does NOT use
+# a local static buffer to hold results in.
+#
+AC_CHECK_BASENAME() {
+    TLOGN "looking for a reentrant basename "
+
+    cat > /tmp/ngc$$.c << EOF
+#include <string.h>
+
+main()
+{
+    char *a = basename("/a/test");
+    char *b = basename("/a/nother");
+
+    return (strcmp(a,b) != 0) ? 0 : 1;
+
+}
+EOF
+
+    if $AC_CC -o /tmp/ngc$$ /tmp/ngc$$.c $LIBS; then
+	if /tmp/ngc$$; then
+	    TLOG "(found)"
+	    AC_DEFINE 'HAVE_BASENAME' 1
+	    AC_CHECK_HEADERS libgen.h
+	else
+	    TLOG "(broken)"
+	fi
+    else
+	TLOG "(not found)"
+    fi
+    rm -f /tmp/ngc$$ /tmp/ngc$$.c
+}
+
+#
+# AC_COMPILER_PIC checks for the compiler option to produce position independent
+# code.  At the moment we assume gcc semantics.
+#
+AC_COMPILER_PIC () {
+    AC_PROG_CC
+
+    LOGN "checking for C compiler option to produce PIC "
+    echo "int some_variable = 0;" > /tmp/ngc$$.c 
+
+    if $AC_CC -c -fPIC -o /tmp/ngc$$ /tmp/ngc$$.c $LIBS; then
+	AC_CFLAGS="$AC_CFLAGS -fPIC"
+        LOG "(-fPIC)"
+	__rc=0
+    else
+        LOG "(none)"
+	__rc=1
+    fi
+    rm -f /tmp/ngc$$ /tmp/ngc$$.c
+    return $__rc
+}
+
+#
+# AC_CC_SHLIBS checks if the C compiler can produce shared libraries
+# and if it can writes a librarian that handles those libraries for us.
+#
+AC_CC_SHLIBS () {
+    AC_PROG_CC
+    AC_PROG_LN_S
+    AC_PROG_INSTALL
+    LOGN "checking whether the C compiler can build shared libraries "
+
+    echo "int some_variable = 0;" > /tmp/ngc$$.c 
+
+    if $AC_CC $AC_PICFLAG -shared -o /tmp/ngc$$.so /tmp/ngc$$.c; then
+	AC_SUB LD_LIBRARY_PATH LD_LIBRARY_PATH
+	# -Wl option probably works, but be paranoid anyway
+	_VFLAGS="$AC_PICFLAG -shared -Wl,-soname,ngc$$.so.1"
+	if $AC_CC $_VFLAGS -o /tmp/ngc$$.so /tmp/ngc$$.c; then
+	    USE_SONAME=T
+	fi
+	LDCONFIG=`AC_PATH=/sbin:/usr/sbin:/usr/local/sbin acLookFor ldconfig`
+	__config_files="$__config_files librarian.sh"
+	cat > librarian.sh << EOF
+#! /bin/sh
+#
+#  Build ELF shared libraries, hiding (some) ickiness from the makefile
+
+ACTION=\$1; shift
+LIBRARY=\$1; shift
+	
+eval \`awk -F. '{ printf "MAJOR=%d\n", \$1;
+		  printf "VERSION=%d.%d.%d\n", \$1, \$2, \$3; }' \$1\`
+shift
+
+LIBNAME=\$LIBRARY.so
+FULLNAME=\$LIBNAME.\$VERSION
+
+case "\$ACTION" in
+make)   FLAGS="$AC_CFLAGS -shared"
+	unset VFLAGS
+	test "$USE_SONAME" && VFLAGS="-Wl,-soname,\$LIBNAME.\$MAJOR"
+
+	rm -f \$LIBRARY \$LIBNAME \$LIBNAME.\$MAJOR
+	if $AC_CC \$FLAGS \$VFLAGS -o \$FULLNAME "\$@"; then
+	    $PROG_LN_S \$FULLNAME \$LIBRARY
+	    $PROG_LN_S \$FULLNAME \$LIBNAME
+	    $PROG_LN_S \$FULLNAME \$LIBNAME.\$MAJOR
+	fi
+	;;
+files)  echo "\$FULLNAME" "\$LIBNAME" "\$LIBNAME.\$MAJOR"
+	;;
+install)$PROG_INSTALL -c \$FULLNAME "\$1"
+	$PROG_LN_S -f \$FULLNAME \$1/\$LIBNAME.\$MAJOR
+	$PROG_LN_S -f \$FULLNAME \$1/\$LIBNAME
+	test "$LDCONFIG" && $LDCONFIG "\$1"
+	;;
+esac
+EOF
+	chmod +x librarian.sh
+        LOG "(yes; -shared)"
+	__rc=0
+    elif $AC_CC $AC_PICFLAG  -dynamiclib -o /tmp/ngc$$.so /tmp/ngc$$.c; then
+	# macosx
+	AC_SUB LD_LIBRARY_PATH DYLD_LIBRARY_PATH
+	__config_files="$__config_files librarian.sh"
+	cat > librarian.sh << EOF
+#! /bin/sh
+#
+#  Build MacOS shared libraries, hiding (some) ickiness from the makefile
+
+ACTION=\$1; shift
+LIBRARY=\$1; shift
+	
+eval \`awk -F. '{ printf "MAJOR=%d\n", \$1;
+		  printf "VERSION=%d.%d.%d\n", \$1, \$2, \$3; }' \$1\`
+shift
+
+LIBNAME=\$LIBRARY.dylib
+FULLNAME=\$LIBNAME
+
+case "\$ACTION" in
+make)   FLAGS="$AC_CFLAGS -dynamiclib"
+	VFLAGS="-current_version \$VERSION -compatibility_version \$MAJOR"
+
+	rm -f \$LIBRARY
+	if $AC_CC \$FLAGS \$VFLAGS -o \$FULLNAME "\$@"; then
+	    $PROG_LN_S \$FULLNAME \$LIBRARY
+	fi
+	;;
+files)  echo "\$FULLNAME"
+	;;
+install)$PROG_INSTALL -c \$FULLNAME "\$1"
+	;;
+esac
+EOF
+	chmod +x librarian.sh
+        LOG "(yes; macos dylib)"
+	__rc=0
+    else
+        LOG "(no)"
+	__rc=1
+    fi
+
+    rm -f /tmp/ngc$$.so /tmp/ngc$$.c
+
+    return $__rc
+}
+
+
+#
+# AC_PROG_INSTALL finds the install program and guesses whether it's a 
+# Berkeley or GNU install program
+#
+AC_PROG_INSTALL () {
+
+    DEST=`acLookFor install`
+
+    LOGN "looking for install"
+    unset IS_BSD
+    if [ "$DEST" ]; then
+	# BSD install or GNU install?  Let's find out...
+	touch /tmp/a$$
+
+	$DEST /tmp/a$$ /tmp/b$$
+
+	if test -r /tmp/a$$; then
+	    LOG " ($DEST)"
+	else
+	    IS_BSD=1
+	    LOG " ($DEST) bsd install"
+	fi
+	rm -f /tmp/a$$ /tmp/b$$
+    else
+	DEST=`acLookFor ginstall`
+	if [ "$DEST" ]; then
+	    LOG " ($DEST)"
+	else
+	    DEST="false"
+	    LOG " (not found)"
+	fi
+    fi
+
+    if [ "$IS_BSD" ]; then
+	PROG_INSTALL="$DEST -c"
+    else
+	PROG_INSTALL="$DEST"
+    fi
+
+    AC_SUB 'INSTALL' "$PROG_INSTALL"
+    AC_SUB 'INSTALL_PROGRAM' "$PROG_INSTALL -s -m 755"
+    AC_SUB 'INSTALL_DATA' "$PROG_INSTALL -m 444"
+
+    # finally build a little directory installer
+    # if mkdir -p works, use that, otherwise use install -d,
+    # otherwise build a script to do it by hand.
+    # in every case, test to see if the directory exists before
+    # making it.
+
+    if mkdir -p $$a/b; then
+	# I like this method best.
+	__mkdir="mkdir -p"
+	rmdir $$a/b
+	rmdir $$a
+    elif $PROG_INSTALL -d $$a/b; then
+	__mkdir="$PROG_INSTALL -d"
+	rmdir $$a/b
+	rmdir $$a
+    fi
+
+    __config_files="$__config_files config.md"
+    AC_SUB 'INSTALL_DIR' "$__cwd/config.md"
+    echo "#! /bin/sh"                                   > $__cwd/config.md
+    echo "# script generated" `date` "by configure.sh" >> $__cwd/config.md
+    echo                                               >> $__cwd/config.md
+    if [ "$__mkdir" ]; then
+	echo "test -d \"\$1\" || $__mkdir \"\$1\""     >> $__cwd/config.md
+	echo "exit $?"                                 >> $__cwd/config.md
+    else
+	cat - >> $__cwd/config.md << \EOD
+pieces=`IFS=/; for x in $1; do echo $x; done`
+dir=
+for x in $pieces; do
+    dir="$dir$x"
+    mkdir $dir || exit 1
+    dir="$dir/"
+done
+exit 0
+EOD
+    fi
+    chmod +x $__cwd/config.md
+}
+
+#
+# acCheckCPP is a local that runs a C preprocessor with a given set of
+# compiler options
+#
+acCheckCPP () {
+	cat > /tmp/ngc$$.c << EOF
+#define FOO BAR
+
+FOO
+EOF
+
+    if $1 $2 /tmp/ngc$$.c > /tmp/ngc$$.o; then
+	if grep -v '#define' /tmp/ngc$$.o | grep -s BAR >/dev/null; then
+	    echo "CPP=[$1], CPPFLAGS=[$2]"
+	    AC_SUB 'CPP' "$1"
+	    AC_SUB 'CPPFLAGS' "$2"
+	    rm /tmp/ngc$$.c /tmp/ngc$$.o
+	    return 0
+	fi
+    fi
+    rm /tmp/ngc$$.c /tmp/ngc$$.o
+    return 1
+}
+
+
+#
+# AC_PROG_CPP checks for cpp, then checks to see which CPPFLAGS are needed
+# to run it as a filter.
+#
+AC_PROG_CPP () {
+    if [ "$AC_CPP_PROG" ]; then
+	DEST=$AC_CPP_PROG
+    else
+	__ac_path="$AC_PATH"
+	AC_PATH="/lib:/usr/lib:${__ac_path:-$ac_default_path}"
+	DEST=`acLookFor cpp`
+	AC_PATH="$__ac_path"
+    fi
+
+    unset fail
+    LOGN "Looking for cpp"
+    if [ "$DEST" ]; then
+	TLOGN " ($DEST)"
+	acCheckCPP $DEST "$CPPFLAGS" || \
+		 acCheckCPP $DEST -traditional-cpp -E || \
+		 acCheckCPP $DEST -E || \
+		 acCheckCPP $DEST -traditional-cpp -pipe || \
+	         acCheckCPP $DEST -pipe || fail=1
+
+	if [ "$fail" ]; then
+	    AC_FAIL " (can't run cpp as a pipeline)"
+	else
+	    TLOG " ok"
+	    return 0
+	fi
+    fi
+    AC_FAIL " (not found)"
+}
+
+#
+# AC_FAIL spits out an error message, then __fail's 
+AC_FAIL() {
+    LOG "$*" 
+    $__fail 1
+}
+
+#
+# AC_SUB writes a substitution into config.sub
+AC_SUB() {
+    (   _subst=`echo $2 | sed -e 's/;/\\;/g'`
+	echo "s;@$1@;$_subst;g" ) >> $__cwd/config.sub
+}
+
+#
+# AC_MAK writes a define into config.mak
+AC_MAK() {
+    echo "HAVE_$1 = 1" >> $__cwd/config.mak
+}
+
+#
+# AC_DEFINE adds a #define to config.h
+AC_DEFINE() {
+    echo "#define $1 ${2:-1}" >> $__cwd/config.h
+}
+
+#
+# AC_INCLUDE adds a #include to config.h
+AC_INCLUDE() {
+    echo "#include \"$1\"" >> $__cwd/config.h
+}
+
+#
+# AC_CONFIG adds a configuration setting to all the config files
+AC_CONFIG() {
+    AC_DEFINE "PATH_$1" \""$2"\"
+    AC_MAK "$1"
+    AC_SUB "$1" "$2"
+}
+
+#
+# AC_QUIET does something quietly
+AC_QUIET() {
+    eval $* 5>/dev/null
+}
+    
+
+AC_TR=`acLookFor tr`
+if [ "$AC_TR" ]; then
+    # try posix-style tr
+    ABC=`echo abc | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ`
+    if [ "$ABC" = "ABC" ]; then
+	AC_UPPERCASE="$AC_TR abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	AC_UPPER_PAT="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+    else
+	ABC=`echo abc | tr a-z A-Z`
+	if [ "$ABC" = "ABC" ]; then
+	    AC_UPPERCASE="$AC_TR a-z A-Z"
+	    AC_UPPER_PAT="A-Z"
+	else
+	    ABC=`echo abc | tr '[a-z]' '[A-Z]'`
+	    if [ "$ABC" = "ABC" ]; then
+		AC_UPPERCASE="$AC_TR '[a-z]' '[A-Z]'"
+		AC_UPPER_PAT="'[A-Z]'"
+	    else
+		AC_FAIL "$AC_TR cannot translate lowercase to uppercase"
+		return 0
+	    fi
+	fi
+    fi
+else
+    AC_FAIL "configure requires a functional version of tr"
+fi
+
+while [ $# -gt 0 ]; do
+    unset matched
+
+    case X"$1" in
+    X--src|X--srcdir)
+	AC_SRCDIR=`__ac_dir "$2"`
+	_set_srcdir=1
+	shift 2;;
+
+    X--src=*|X--srcdir=*)
+	__d=`echo "$1" | sed -e 's/^[^=]*=//'`
+	AC_SRCDIR=`__ac_dir "$__d"`
+	_set_srcdir=1
+	shift 1 ;;
+
+    X--prefix)
+	AC_PREFIX=`__ac_dir "$2"`
+	_set_prefix=1
+	shift 2;;
+
+    X--prefix=*)
+	__d=`echo "$1"| sed -e 's/^[^=]*=//'`
+	AC_PREFIX=`__ac_dir "$__d"`
+	_set_prefix=1
+	shift 1;;
+
+    X--confdir)
+	AC_CONFDIR=`__ac_dir "$2"`
+	_set_confdir=1
+	shift 2;;
+
+    X--confdir=*)
+	__d=`echo "$1" | sed -e 's/^[^=]*=//'`
+	AC_CONFDIR=`__ac_dir "$__d"`
+	_set_confdir=1
+	shift 1;;
+
+    X--libexec|X--libexecdir)
+	AC_LIBEXEC=`__ac_dir "$2"`
+	_set_libexec=1
+	shift 2;;
+
+    X--libexec=*|X--libexecdir=*)
+	__d=`echo "$1" | sed -e 's/^[^=]*=//'`
+	AC_LIBEXEC=`__ac_dir "$__d"`
+	_set_libexec=1
+	shift 1;;
+
+    X--lib|X--libdir)
+	AC_LIBDIR=`__ac_dir "$2"`
+	_set_libdir=1
+	shift 2;;
+
+    X--lib=*|X--libdir=*)
+	__d=`echo "$1" | sed -e 's/^[^=]*=//'`
+	AC_LIBDIR=`__ac_dir "$__d"`
+	_set_libdir=1
+	shift 1;;
+
+    X--exec|X--execdir)
+	AC_EXECDIR=`__ac_dir "$2"`
+	_set_execdir=1
+	shift 2;;
+
+    X--exec=*|X--execdir=*)
+	__d=`echo "$1" | sed -e 's/^[^=]*=//'`
+	AC_EXECDIR=`__ac_dir "$__d"`
+	_set_execdir=1
+	shift 1;;
+
+    X--sbin|X--sbindir)
+	AC_SBINDIR=`__ac_dir "$2"`
+	_set_sbindir=1
+	shift 2;;
+
+    X--sbin=*|X--sbindir=*)
+	__d=`echo "$1" | sed -e 's/^[^=]*=//'`
+	AC_SBINDIR=`__ac_dir "$__d"`
+	_set_sbindir=1
+	shift 1;;
+
+    X--man|X--mandir)
+	AC_MANDIR=`__ac_dir "$2"`
+	_set_mandir=1
+	shift 2;;
+
+    X--man=*|X--mandir=*)
+	__d=`echo "$1" | sed -e 's/^[^=]*=//'`
+	AC_MANDIR=`__ac_dir "$__d"`
+	_set_mandir=1
+	shift 1;;
+
+    X--use-*=*)
+	_var=`echo "$1"| sed -n 's/^--use-\([A-Za-z][-A-Za-z0-9_]*\)=.*$/\1/p'`
+	if [ "$_var" ]; then
+	    _val=`echo "$1" | sed -e 's/^--use-[^=]*=\(.*\)$/\1/'`
+	    _v=`echo $_var | $AC_UPPERCASE | tr '-' '_'`
+	    case X"$_val" in
+	    X[Yy][Ee][Ss]|X[Tt][Rr][Uu][Ee]) eval USE_${_v}=T ;;
+	    X[Nn][Oo]|X[Ff][Aa][Ll][Ss][Ee]) eval unset USE_${_v} ;;
+	    *) echo "Bad value for --use-$_var ; must be yes or no"
+	       exit 1 ;;
+	    esac
+	else
+	    echo "Bad option $1.   Use --help to show options" 1>&2
+	    exit 1
+	fi
+	shift 1 ;;
+
+    X--use-*)
+        _var=`echo "$1"|sed -n 's/^--use-\([A-Za-z][-A-Za-z0-9_]*\)$/\1/p'`
+	_v=`echo $_var | $AC_UPPERCASE | tr '-' '_'`
+	eval USE_${_v}=T
+	shift 1;;
+
+    X--with-*=*)
+	_var=`echo "$1"| sed -n 's/^--with-\([A-Za-z][-A-Za-z0-9_]*\)=.*$/\1/p'`
+	if [ "$_var" ]; then
+	    _val=`echo "$1" | sed -e 's/^--with-[^=]*=\(.*\)$/\1/'`
+	    _v=`echo $_var | $AC_UPPERCASE | tr '-' '_'`
+	    eval WITH_${_v}=\"$_val\"
+	else
+	    echo "Bad option $1.   Use --help to show options" 1>&2
+	    exit 1
+	fi
+	shift 1 ;;
+
+    X--with-*)
+	_var=`echo "$1" | sed -n 's/^--with-\([A-Za-z][A-Za-z0-9_-]*\)$/\1/p'`
+	if [ "$_var" ]; then
+	    _v=`echo $_var | $AC_UPPERCASE | tr '-' '_'`
+	    eval WITH_${_v}=1
+	else
+	    echo "Bad option $1.   Use --help to show options" 1>&2
+	    exit 1
+	fi
+	shift 1 ;;
+
+    X--help)
+	echo "$ac_standard"
+	test "$ac_help" && echo "$ac_help"
+	exit 0;;
+
+    *)	if [ "$LOCAL_AC_OPTIONS" ]; then
+	    eval "$LOCAL_AC_OPTIONS"
+	else
+	    ac_error=T
+	fi
+	if [ "$ac_error" ]; then
+	    echo "Bad option $1.   Use --help to show options" 1>&2
+	    exit 1
+	fi ;;
+    esac
+done
+
diff --git a/configure.sh b/configure.sh
new file mode 100755
index 0000000..870181d
--- /dev/null
+++ b/configure.sh
@@ -0,0 +1,141 @@
+#! /bin/sh
+
+# local options:  ac_help is the help message that describes them
+# and LOCAL_AC_OPTIONS is the script that interprets them.  LOCAL_AC_OPTIONS
+# is a script that's processed with eval, so you need to be very careful to
+# make certain that what you quote is what you want to quote.
+
+# load in the configuration file
+#
+ac_help='--enable-amalloc	Enable memory allocation debugging
+--with-tabstops=N	Set tabstops to N characters (default is 4)
+--with-dl=X		Use Discount, Extra, or Both types of definition list
+--enable-all-features	Turn on all stable optional features
+--shared		Build shared libraries (default is static)'
+
+LOCAL_AC_OPTIONS='
+set=`locals $*`;
+if [ "$set" ]; then
+    eval $set
+    shift 1
+else
+    ac_error=T;
+fi'
+
+locals() {
+    K=`echo $1 | $AC_UPPERCASE`
+    case "$K" in
+    --SHARED)
+                echo TRY_SHARED=T
+                ;;
+    --ENABLE-ALL|--ENABLE-ALL-FEATURES)
+		echo WITH_AMALLOC=T
+		;;
+    --ENABLE-*)	enable=`echo $K | sed -e 's/--ENABLE-//' | tr '-' '_'`
+		echo WITH_${enable}=T ;;
+    esac
+}
+
+TARGET=markdown
+. ./configure.inc
+
+AC_INIT $TARGET
+
+__DL=`echo "$WITH_DL" | $AC_UPPERCASE`
+
+case "$__DL" in
+EXTRA)         AC_DEFINE 'USE_EXTRA_DL' 1 ;;
+DISCOUNT|1|"") AC_DEFINE 'USE_DISCOUNT_DL' 1 ;;
+BOTH)          AC_DEFINE 'USE_EXTRA_DL' 1
+	       AC_DEFINE 'USE_DISCOUNT_DL' 1 ;;
+*)             AC_FAIL "Unknown value <$WITH_DL> for --with-dl (want 'discount', 'extra', or 'both')" ;;
+esac
+
+AC_PROG_CC
+
+test "$TRY_SHARED" && AC_COMPILER_PIC && AC_CC_SHLIBS
+
+case "$AC_CC $AC_CFLAGS" in
+*-Wall*)    AC_DEFINE 'while(x)' 'while( (x) != 0 )'
+	    AC_DEFINE 'if(x)' 'if( (x) != 0 )' ;;
+esac
+
+AC_PROG ar || AC_FAIL "$TARGET requires ar"
+AC_PROG ranlib
+
+AC_C_VOLATILE
+AC_C_CONST
+AC_SCALAR_TYPES sub hdr
+AC_CHECK_BASENAME
+
+AC_CHECK_HEADERS sys/types.h pwd.h && AC_CHECK_FUNCS getpwuid
+
+if AC_CHECK_FUNCS srandom; then
+    AC_DEFINE 'INITRNG(x)' 'srandom((unsigned int)x)'
+elif AC_CHECK_FUNCS srand; then
+    AC_DEFINE 'INITRNG(x)' 'srand((unsigned int)x)'
+else
+    AC_DEFINE 'INITRNG(x)' '(void)1'
+fi
+
+if AC_CHECK_FUNCS 'bzero((char*)0,0)'; then
+    : # Yay
+elif AC_CHECK_FUNCS 'memset((char*)0,0,0)'; then
+    AC_DEFINE 'bzero(p,s)' 'memset(p,s,0)'
+else
+    AC_FAIL "$TARGET requires bzero or memset"
+fi
+
+if AC_CHECK_FUNCS random; then
+    AC_DEFINE 'COINTOSS()' '(random()&1)'
+elif AC_CHECK_FUNCS rand; then
+    AC_DEFINE 'COINTOSS()' '(rand()&1)'
+else
+    AC_DEFINE 'COINTOSS()' '1'
+fi
+
+if AC_CHECK_FUNCS strcasecmp; then
+    :
+elif AC_CHECK_FUNCS stricmp; then
+    AC_DEFINE strcasecmp stricmp
+else
+    AC_FAIL "$TARGET requires either strcasecmp() or stricmp()"
+fi
+
+if AC_CHECK_FUNCS strncasecmp; then
+    :
+elif AC_CHECK_FUNCS strnicmp; then
+    AC_DEFINE strncasecmp strnicmp
+else
+    AC_FAIL "$TARGET requires either strncasecmp() or strnicmp()"
+fi
+
+if AC_CHECK_FUNCS fchdir || AC_CHECK_FUNCS getcwd ; then
+    AC_SUB 'THEME' ''
+else
+    AC_SUB 'THEME' '#'
+fi
+
+if [ -z "$WITH_TABSTOPS" ]; then
+    TABSTOP=4
+elif [ "$WITH_TABSTOPS" -eq 1 ]; then
+    TABSTOP=8
+else
+    TABSTOP=$WITH_TABSTOPS
+fi
+AC_DEFINE 'TABSTOP' $TABSTOP
+AC_SUB    'TABSTOP' $TABSTOP
+
+
+if [ "$WITH_AMALLOC" ]; then
+    AC_DEFINE	'USE_AMALLOC'	1
+    AC_SUB	'AMALLOC'	'amalloc.o'
+else
+    AC_SUB	'AMALLOC'	''
+fi
+
+[ "$OS_FREEBSD" -o "$OS_DRAGONFLY" ] || AC_CHECK_HEADERS malloc.h
+
+[ "$WITH_PANDOC_HEADER" ] && AC_DEFINE 'PANDOC_HEADER' '1'
+
+AC_OUTPUT Makefile version.c mkdio.h
diff --git a/css.c b/css.c
new file mode 100644
index 0000000..3eb30b3
--- /dev/null
+++ b/css.c
@@ -0,0 +1,85 @@
+/* markdown: a C implementation of John Gruber's Markdown markup language.
+ *
+ * Copyright (C) 2009 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "config.h"
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+
+/*
+ * dump out stylesheet sections.
+ */
+static void
+stylesheets(Paragraph *p, Cstring *f)
+{
+    Line* q;
+
+    for ( ; p ; p = p->next ) {
+	if ( p->typ == STYLE ) {
+	    for ( q = p->text; q ; q = q->next ) {
+		Cswrite(f, T(q->text), S(q->text));
+		Csputc('\n', f);
+	    }
+	}
+	if ( p->down )
+	    stylesheets(p->down, f);
+    }
+}
+
+
+/* dump any embedded styles to a string
+ */
+int
+mkd_css(Document *d, char **res)
+{
+    Cstring f;
+    int size;
+
+    if ( res && d && d->compiled ) {
+	*res = 0;
+	CREATE(f);
+	RESERVE(f, 100);
+	stylesheets(d->code, &f);
+			
+	if ( (size = S(f)) > 0 ) {
+	    EXPAND(f) = 0;
+			/* HACK ALERT! HACK ALERT! HACK ALERT! */
+	    *res = T(f);/* we know that a T(Cstring) is a character pointer */
+			/* so we can simply pick it up and carry it away, */
+			/* leaving the husk of the Ctring on the stack */
+			/* END HACK ALERT */
+	}
+	else
+	    DELETE(f);
+	return size;
+    }
+    return EOF;
+}
+
+
+/* dump any embedded styles to a file
+ */
+int
+mkd_generatecss(Document *d, FILE *f)
+{
+    char *res;
+    int written = EOF, size = mkd_css(d, &res);
+
+    if ( size > 0 )
+	written = fwrite(res, 1, size, f);
+    if ( res )
+	free(res);
+    return (written == size) ? size : EOF;
+}
diff --git a/cstring.h b/cstring.h
new file mode 100644
index 0000000..96ad841
--- /dev/null
+++ b/cstring.h
@@ -0,0 +1,77 @@
+/* two template types:  STRING(t) which defines a pascal-style string
+ * of element (t) [STRING(char) is the closest to the pascal string],
+ * and ANCHOR(t) which defines a baseplate that a linked list can be
+ * built up from.   [The linked list /must/ contain a ->next pointer
+ * for linking the list together with.]
+ */
+#ifndef _CSTRING_D
+#define _CSTRING_D
+
+#include <string.h>
+#include <stdlib.h>
+
+#ifndef __WITHOUT_AMALLOC
+# include "amalloc.h"
+#endif
+
+/* expandable Pascal-style string.
+ */
+#define STRING(type)	struct { type *text; int size, alloc; }
+
+#define CREATE(x)	( (T(x) = (void*)0), (S(x) = (x).alloc = 0) )
+#define EXPAND(x)	(S(x)++)[(S(x) < (x).alloc) \
+			    ? (T(x)) \
+			    : (T(x) = T(x) ? realloc(T(x), sizeof T(x)[0] * ((x).alloc += 100)) \
+					   : malloc(sizeof T(x)[0] * ((x).alloc += 100)) )]
+
+#define DELETE(x)	ALLOCATED(x) ? (free(T(x)), S(x) = (x).alloc = 0) \
+				     : ( S(x) = 0 )
+#define CLIP(t,i,sz)	\
+	    ( ((i) >= 0) && ((sz) > 0) && (((i)+(sz)) <= S(t)) ) ? \
+	    (memmove(&T(t)[i], &T(t)[i+sz], (S(t)-(i+sz)+1)*sizeof(T(t)[0])), \
+		S(t) -= (sz)) : -1
+
+#define RESERVE(x, sz)	T(x) = ((x).alloc > S(x) + (sz) \
+			    ? T(x) \
+			    : T(x) \
+				? realloc(T(x), sizeof T(x)[0] * ((x).alloc = 100+(sz)+S(x))) \
+				: malloc(sizeof T(x)[0] * ((x).alloc = 100+(sz)+S(x))))
+#define SUFFIX(t,p,sz)	\
+	    memcpy(((S(t) += (sz)) - (sz)) + \
+		    (T(t) = T(t) ? realloc(T(t), sizeof T(t)[0] * ((t).alloc += sz)) \
+				 : malloc(sizeof T(t)[0] * ((t).alloc += sz))), \
+		    (p), sizeof(T(t)[0])*(sz))
+
+#define PREFIX(t,p,sz)	\
+	    RESERVE( (t), (sz) ); \
+	    if ( S(t) ) { memmove(T(t)+(sz), T(t), S(t)); } \
+	    memcpy( T(t), (p), (sz) ); \
+	    S(t) += (sz)
+
+/* reference-style links (and images) are stored in an array
+ */
+#define T(x)		(x).text
+#define S(x)		(x).size
+#define ALLOCATED(x)	(x).alloc
+
+/* abstract anchor type that defines a list base
+ * with a function that attaches an element to
+ * the end of the list.
+ *
+ * the list base field is named .text so that the T()
+ * macro will work with it.
+ */
+#define ANCHOR(t)	struct { t *text, *end; }
+#define E(t)		((t).end)
+
+#define ATTACH(t, p)	( T(t) ? ( (E(t)->next = (p)), (E(t) = (p)) ) \
+			       : ( (T(t) = E(t) = (p)) ) )
+
+typedef STRING(char) Cstring;
+
+extern void Csputc(int, Cstring *);
+extern int Csprintf(Cstring *, char *, ...);
+extern int Cswrite(Cstring *, char *, int);
+extern void Csreparse(Cstring *, char *, int, int);
+
+#endif/*_CSTRING_D*/
diff --git a/docheader.c b/docheader.c
new file mode 100644
index 0000000..073f6da
--- /dev/null
+++ b/docheader.c
@@ -0,0 +1,49 @@
+/*
+ * docheader -- get values from the document header
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+static char *
+onlyifset(Line *l)
+{
+    char *ret = T(l->text) + l->dle;
+
+    return ret[0] ? ret : 0;
+}
+
+char *
+mkd_doc_title(Document *doc)
+{
+    if ( doc && doc->title )
+	return onlyifset(doc->title);
+    return 0;
+}
+
+
+char *
+mkd_doc_author(Document *doc)
+{
+    if ( doc && doc->author )
+	return onlyifset(doc->author);
+    return 0;
+}
+
+
+char *
+mkd_doc_date(Document *doc)
+{
+    if ( doc && doc->date )
+	return onlyifset(doc->date);
+    return 0;
+}
diff --git a/dumptree.c b/dumptree.c
new file mode 100644
index 0000000..0680848
--- /dev/null
+++ b/dumptree.c
@@ -0,0 +1,152 @@
+/* markdown: a C implementation of John Gruber's Markdown markup language.
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include <stdio.h>
+#include "markdown.h"
+#include "cstring.h"
+#include "amalloc.h"
+
+struct frame {
+    int indent;
+    char c;
+};
+
+typedef STRING(struct frame) Stack;
+
+static char *
+Pptype(int typ)
+{
+    switch (typ) {
+    case WHITESPACE: return "whitespace";
+    case CODE      : return "code";
+    case QUOTE     : return "quote";
+    case MARKUP    : return "markup";
+    case HTML      : return "html";
+    case DL        : return "dl";
+    case UL        : return "ul";
+    case OL        : return "ol";
+    case LISTITEM  : return "item";
+    case HDR       : return "header";
+    case HR        : return "hr";
+    case TABLE     : return "table";
+    case SOURCE    : return "source";
+    case STYLE     : return "style";
+    default        : return "mystery node!";
+    }
+}
+
+static void
+pushpfx(int indent, char c, Stack *sp)
+{
+    struct frame *q = &EXPAND(*sp);
+
+    q->indent = indent;
+    q->c = c;
+}
+
+
+static void
+poppfx(Stack *sp)
+{
+    S(*sp)--;
+}
+
+
+static void
+changepfx(Stack *sp, char c)
+{
+    char ch;
+
+    if ( !S(*sp) ) return;
+
+    ch = T(*sp)[S(*sp)-1].c;
+
+    if ( ch == '+' || ch == '|' )
+	T(*sp)[S(*sp)-1].c = c;
+}
+
+
+static void
+printpfx(Stack *sp, FILE *f)
+{
+    int i;
+    char c;
+
+    if ( !S(*sp) ) return;
+
+    c = T(*sp)[S(*sp)-1].c;
+
+    if ( c == '+' || c == '-' ) {
+	fprintf(f, "--%c", c);
+	T(*sp)[S(*sp)-1].c = (c == '-') ? ' ' : '|';
+    }
+    else
+	for ( i=0; i < S(*sp); i++ ) {
+	    if ( i )
+		fprintf(f, "  ");
+	    fprintf(f, "%*s%c", T(*sp)[i].indent + 2, " ", T(*sp)[i].c);
+	    if ( T(*sp)[i].c == '`' )
+		T(*sp)[i].c = ' ';
+	}
+    fprintf(f, "--");
+}
+
+
+static void
+dumptree(Paragraph *pp, Stack *sp, FILE *f)
+{
+    int count;
+    Line *p;
+    int d;
+    static char *Begin[] = { 0, "P", "center" };
+
+    while ( pp ) {
+	if ( !pp->next )
+	    changepfx(sp, '`');
+	printpfx(sp, f);
+
+	d = fprintf(f, "[%s", Pptype(pp->typ));
+	if ( pp->ident )
+	    d += fprintf(f, " %s", pp->ident);
+	if ( pp->align )
+	    d += fprintf(f, ", <%s>", Begin[pp->align]);
+
+	for (count=0, p=pp->text; p; ++count, (p = p->next) )
+	    ;
+
+	if ( count )
+	    d += fprintf(f, ", %d line%s", count, (count==1)?"":"s");
+
+	d += fprintf(f, "]");
+
+	if ( pp->down ) {
+	    pushpfx(d, pp->down->next ? '+' : '-', sp);
+	    dumptree(pp->down, sp, f);
+	    poppfx(sp);
+	}
+	else fputc('\n', f);
+	pp = pp->next;
+    }
+}
+
+
+int
+mkd_dump(Document *doc, FILE *out, int flags, char *title)
+{
+    Stack stack;
+
+    if (mkd_compile(doc, flags) ) {
+
+	CREATE(stack);
+	pushpfx(fprintf(out, "%s", title), doc->code->next ? '+' : '-', &stack);
+	dumptree(doc->code, &stack, out);
+	DELETE(stack);
+
+	mkd_cleanup(doc);
+	return 0;
+    }
+    return -1;
+}
diff --git a/emmatch.c b/emmatch.c
new file mode 100644
index 0000000..8d362c5
--- /dev/null
+++ b/emmatch.c
@@ -0,0 +1,188 @@
+/* markdown: a C implementation of John Gruber's Markdown markup language.
+ *
+ * Copyright (C) 2010 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "config.h"
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+
+/* emmatch: the emphasis mangler that's run after a block
+ *          of html has been generated.
+ *
+ *          It should create MarkdownTest_1.0 (and _1.0.3)
+ *          compatable emphasis for non-pathological cases
+ *          and it should fail in a standards-compliant way
+ *          when someone attempts to feed it junk.
+ *
+ *          Emmatching is done after the input has been 
+ *          processed into a STRING (f->Q) of text and
+ *          emphasis blocks.   After ___mkd_emblock() finishes,
+ *          it truncates f->Q and leaves the rendered paragraph
+ *          if f->out.
+ */
+
+
+/* empair() -- find the NEAREST matching emphasis token (or
+ *             subtoken of a 3+ long emphasis token.
+ */
+static int
+empair(MMIOT *f, int first, int last, int match)
+{
+    
+    int i;
+    block *begin, *p;
+
+    begin = &T(f->Q)[first];
+
+    for (i=first+1; i <= last; i++) {
+	p = &T(f->Q)[i];
+
+	if ( (p->b_type != bTEXT) && (p->b_count <= 0) )
+	    continue; /* break? */
+	
+	if ( p->b_type == begin->b_type ) {
+	    if ( p->b_count == match )	/* exact match */
+		return i;
+
+	    if ( p->b_count > 2 )	/* fuzzy match */
+		return i;
+	}
+    }
+    return 0;
+} /* empair */
+
+
+/* emfill() -- if an emphasis token has leftover stars or underscores,
+ *             convert them back into character and append them to b_text.
+ */
+static void
+emfill(block *p)
+{
+    int j;
+
+    if ( p->b_type == bTEXT )
+	return;
+	
+    for (j=0; j < p->b_count; j++)
+	  EXPAND(p->b_text) = p->b_char;
+    p->b_count = 0;
+} /* emfill */
+
+
+static void
+emclose(MMIOT *f, int first, int last)
+{
+    int j;
+
+    for (j=first+1; j<last-1; j++)
+	emfill(&T(f->Q)[j]);
+}
+
+
+static struct emtags {
+    char open[10];
+    char close[10];
+    int size;
+} emtags[] = {  { "<em>" , "</em>", 5 }, { "<strong>", "</strong>", 9 } };
+
+
+static void emblock(MMIOT*,int,int);
+
+
+/* emmatch() -- match emphasis for a single emphasis token.
+ */
+static void
+emmatch(MMIOT *f, int first, int last)
+{
+    block *start = &T(f->Q)[first];
+    int e, e2, match;
+
+    switch (start->b_count) {
+    case 2: if ( e = empair(f,first,last,match=2) )
+		break;
+    case 1: e = empair(f,first,last,match=1);
+	    break;
+    case 0: return;
+    default:
+	    e = empair(f,first,last,1);
+	    e2= empair(f,first,last,2);
+
+	    if ( e2 >= e ) {
+		e = e2;
+		match = 2;
+	    } 
+	    else
+		match = 1;
+	    break;
+    }
+
+    if ( e ) {
+	/* if we found emphasis to match, match it, recursively call
+	 * emblock to match emphasis inside the new html block, add
+	 * the emphasis markers for the block, then (tail) recursively
+	 * call ourself to match any remaining emphasis on this token.
+	 */
+	block *end = &T(f->Q)[e];
+
+	end->b_count -= match;
+	start->b_count -= match;
+
+	emblock(f, first, e);
+
+	PREFIX(start->b_text, emtags[match-1].open, emtags[match-1].size-1);
+	SUFFIX(end->b_post, emtags[match-1].close, emtags[match-1].size);
+
+	emmatch(f, first, last);
+    }
+} /* emmatch */
+
+
+/* emblock() -- walk a blocklist, attempting to match emphasis
+ */
+static void
+emblock(MMIOT *f, int first, int last)
+{
+    int i;
+    
+    for ( i = first; i <= last; i++ )
+	if ( T(f->Q)[i].b_type != bTEXT )
+	    emmatch(f, i, last);
+    emclose(f, first, last);
+} /* emblock */
+
+
+/* ___mkd_emblock() -- emblock a string of blocks, then concatenate the
+ *                     resulting text onto f->out.
+ */
+void
+___mkd_emblock(MMIOT *f)
+{
+    int i;
+    block *p;
+
+    emblock(f, 0, S(f->Q)-1);
+    
+    for (i=0; i < S(f->Q); i++) {
+	p = &T(f->Q)[i];
+	emfill(p);
+	
+	if ( S(p->b_post) ) { SUFFIX(f->out, T(p->b_post), S(p->b_post));
+			      DELETE(p->b_post); }
+	if ( S(p->b_text) ) { SUFFIX(f->out, T(p->b_text), S(p->b_text));
+			      DELETE(p->b_text); }
+    }
+    
+    S(f->Q) = 0;
+} /* ___mkd_emblock */
diff --git a/flags.c b/flags.c
new file mode 100644
index 0000000..4021a8b
--- /dev/null
+++ b/flags.c
@@ -0,0 +1,83 @@
+#include <stdio.h>
+#include "markdown.h"
+
+struct flagnames {
+    DWORD flag;
+    char *name;
+};
+
+static struct flagnames flagnames[] = {
+    { MKD_NOLINKS,        "!LINKS" },
+    { MKD_NOIMAGE,        "!IMAGE" },
+    { MKD_NOPANTS,        "!PANTS" },
+    { MKD_NOHTML,         "!HTML" },
+    { MKD_STRICT,         "STRICT" },
+    { MKD_TAGTEXT,        "TAGTEXT" },
+    { MKD_NO_EXT,         "!EXT" },
+    { MKD_CDATA,          "CDATA" },
+    { MKD_NOSUPERSCRIPT,  "!SUPERSCRIPT" },
+    { MKD_NORELAXED,      "!RELAXED" },
+    { MKD_NOTABLES,       "!TABLES" },
+    { MKD_NOSTRIKETHROUGH,"!STRIKETHROUGH" },
+    { MKD_TOC,            "TOC" },
+    { MKD_1_COMPAT,       "MKD_1_COMPAT" },
+    { MKD_AUTOLINK,       "AUTOLINK" },
+    { MKD_SAFELINK,       "SAFELINK" },
+    { MKD_NOHEADER,       "!HEADER" },
+    { MKD_TABSTOP,        "TABSTOP" },
+    { MKD_NODIVQUOTE,     "!DIVQUOTE" },
+    { MKD_NOALPHALIST,    "!ALPHALIST" },
+    { MKD_NODLIST,        "!DLIST" },
+};
+#define NR(x)	(sizeof x/sizeof x[0])
+
+
+void
+mkd_flags_are(FILE *f, DWORD flags, int htmlplease)
+{
+    int i;
+    int not, set, even=1;
+    char *name;
+
+    if ( htmlplease )
+	fprintf(f, "<table class=\"mkd_flags_are\">\n");
+    for (i=0; i < NR(flagnames); i++) {
+	set = flags & flagnames[i].flag;
+	name = flagnames[i].name;
+	if ( not = (*name == '!') ) {
+	    ++name;
+	    set = !set;
+	}
+
+	if ( htmlplease ) {
+	    if ( even ) fprintf(f, " <tr>");
+	    fprintf(f, "<td>");
+	}
+	else
+	    fputc(' ', f);
+
+	if ( !set )
+	    fprintf(f, htmlplease ? "<s>" : "!");
+
+	fprintf(f, "%s", name);
+
+	if ( htmlplease ) {
+	    if ( !set )
+		fprintf(f, "</s>");
+	    fprintf(f, "</td>");
+	    if ( !even ) fprintf(f, "</tr>\n");
+	}
+	even = !even;
+    }
+    if ( htmlplease ) {
+	if ( even ) fprintf(f, "</tr>\n");
+	fprintf(f, "</table>\n");
+    }
+}
+
+void
+mkd_mmiot_flags(FILE *f, MMIOT *m, int htmlplease)
+{
+    if ( m )
+	mkd_flags_are(f, m->flags, htmlplease);
+}
diff --git a/generate.c b/generate.c
new file mode 100644
index 0000000..f39dcc9
--- /dev/null
+++ b/generate.c
@@ -0,0 +1,1643 @@
+/* markdown: a C implementation of John Gruber's Markdown markup language.
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "config.h"
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+typedef int (*stfu)(const void*,const void*);
+typedef void (*spanhandler)(MMIOT*,int);
+
+/* forward declarations */
+static void text(MMIOT *f);
+static Paragraph *display(Paragraph*, MMIOT*);
+
+/* externals from markdown.c */
+int __mkd_footsort(Footnote *, Footnote *);
+
+/*
+ * push text into the generator input buffer
+ */
+static void
+push(char *bfr, int size, MMIOT *f)
+{
+    while ( size-- > 0 )
+	EXPAND(f->in) = *bfr++;
+}
+
+
+/* look <i> characters ahead of the cursor.
+ */
+static int
+peek(MMIOT *f, int i)
+{
+
+    i += (f->isp-1);
+
+    return (i >= 0) && (i < S(f->in)) ? T(f->in)[i] : EOF;
+}
+
+
+/* pull a byte from the input buffer
+ */
+static int
+pull(MMIOT *f)
+{
+    return ( f->isp < S(f->in) ) ? T(f->in)[f->isp++] : EOF;
+}
+
+
+/* return a pointer to the current position in the input buffer.
+ */
+static char*
+cursor(MMIOT *f)
+{
+    return T(f->in) + f->isp;
+}
+
+
+static int
+isthisspace(MMIOT *f, int i)
+{
+    int c = peek(f, i);
+
+    return isspace(c) || (c == EOF);
+}
+
+
+static int
+isthisalnum(MMIOT *f, int i)
+{
+    int c = peek(f, i);
+
+    return (c != EOF) && isalnum(c);
+}
+
+
+static int
+isthisnonword(MMIOT *f, int i)
+{
+    return isthisspace(f, i) || ispunct(peek(f,i));
+}
+
+
+/* return/set the current cursor position
+ */
+#define mmiotseek(f,x)	(f->isp = x)
+#define mmiottell(f)	(f->isp)
+
+
+/* move n characters forward ( or -n characters backward) in the input buffer.
+ */
+static void
+shift(MMIOT *f, int i)
+{
+    if (f->isp + i >= 0 )
+	f->isp += i;
+}
+
+
+/* Qchar()
+ */
+static void
+Qchar(int c, MMIOT *f)
+{
+    block *cur;
+    
+    if ( S(f->Q) == 0 ) {
+	cur = &EXPAND(f->Q);
+	memset(cur, 0, sizeof *cur);
+	cur->b_type = bTEXT;
+    }
+    else
+	cur = &T(f->Q)[S(f->Q)-1];
+
+    EXPAND(cur->b_text) = c;
+    
+}
+
+
+/* Qstring()
+ */
+static void
+Qstring(char *s, MMIOT *f)
+{
+    while (*s)
+	Qchar(*s++, f);
+}
+
+
+/* Qwrite()
+ */
+static void
+Qwrite(char *s, int size, MMIOT *f)
+{
+    while (size-- > 0)
+	Qchar(*s++, f);
+}
+
+
+/* Qprintf()
+ */
+static void
+Qprintf(MMIOT *f, char *fmt, ...)
+{
+    char bfr[80];
+    va_list ptr;
+
+    va_start(ptr,fmt);
+    vsnprintf(bfr, sizeof bfr, fmt, ptr);
+    va_end(ptr);
+    Qstring(bfr, f);
+}
+
+
+/* Qem()
+ */
+static void
+Qem(MMIOT *f, char c, int count)
+{
+    block *p = &EXPAND(f->Q);
+
+    memset(p, 0, sizeof *p);
+    p->b_type = (c == '*') ? bSTAR : bUNDER;
+    p->b_char = c;
+    p->b_count = count;
+
+    memset(&EXPAND(f->Q), 0, sizeof(block));
+}
+
+
+/* generate html from a markup fragment
+ */
+void
+___mkd_reparse(char *bfr, int size, int flags, MMIOT *f)
+{
+    MMIOT sub;
+
+    ___mkd_initmmiot(&sub, f->footnotes);
+    
+    sub.flags = f->flags | flags;
+    sub.cb = f->cb;
+
+    push(bfr, size, &sub);
+    EXPAND(sub.in) = 0;
+    S(sub.in)--;
+    
+    text(&sub);
+    ___mkd_emblock(&sub);
+    
+    Qwrite(T(sub.out), S(sub.out), f);
+
+    ___mkd_freemmiot(&sub, f->footnotes);
+}
+
+
+/*
+ * write out a url, escaping problematic characters
+ */
+static void
+puturl(char *s, int size, MMIOT *f, int display)
+{
+    unsigned char c;
+
+    while ( size-- > 0 ) {
+	c = *s++;
+
+	if ( c == '\\' && size-- > 0 ) {
+	    c = *s++;
+
+	    if ( !( ispunct(c) || isspace(c) ) )
+		Qchar('\\', f);
+	}
+	
+	if ( c == '&' )
+	    Qstring("&", f);
+	else if ( c == '<' )
+	    Qstring("<", f);
+	else if ( c == '"' )
+	    Qstring("%22", f);
+	else if ( isalnum(c) || ispunct(c) || (display && isspace(c)) )
+	    Qchar(c, f);
+	else if ( c == 003 )	/* untokenize ^C */
+	    Qstring("  ", f);
+	else
+	    Qprintf(f, "%%%02X", c);
+    }
+}
+
+
+/* advance forward until the next character is not whitespace
+ */
+static int
+eatspace(MMIOT *f)
+{
+    int c;
+
+    for ( ; ((c=peek(f, 1)) != EOF) && isspace(c); pull(f) )
+	;
+    return c;
+}
+
+
+/* (match (a (nested (parenthetical (string.)))))
+ */
+static int
+parenthetical(int in, int out, MMIOT *f)
+{
+    int size, indent, c;
+
+    for ( indent=1,size=0; indent; size++ ) {
+	if ( (c = pull(f)) == EOF )
+	    return EOF;
+	else if ( (c == '\\') && (peek(f,1) == out || peek(f,1) == in) ) {
+	    ++size;
+	    pull(f);
+	}
+	else if ( c == in )
+	    ++indent;
+	else if ( c == out )
+	    --indent;
+    }
+    return size ? (size-1) : 0;
+}
+
+
+/* extract a []-delimited label from the input stream.
+ */
+static int
+linkylabel(MMIOT *f, Cstring *res)
+{
+    char *ptr = cursor(f);
+    int size;
+
+    if ( (size = parenthetical('[',']',f)) != EOF ) {
+	T(*res) = ptr;
+	S(*res) = size;
+	return 1;
+    }
+    return 0;
+}
+
+
+/* see if the quote-prefixed linky segment is actually a title.
+ */
+static int
+linkytitle(MMIOT *f, char quote, Footnote *ref)
+{
+    int whence = mmiottell(f);
+    char *title = cursor(f);
+    char *e;
+    register int c;
+
+    while ( (c = pull(f)) != EOF ) {
+	e = cursor(f);
+	if ( c == quote ) {
+	    if ( (c = eatspace(f)) == ')' ) {
+		T(ref->title) = 1+title;
+		S(ref->title) = (e-title)-2;
+		return 1;
+	    }
+	}
+    }
+    mmiotseek(f, whence);
+    return 0;
+}
+
+
+/* extract a =HHHxWWW size from the input stream
+ */
+static int
+linkysize(MMIOT *f, Footnote *ref)
+{
+    int height=0, width=0;
+    int whence = mmiottell(f);
+    int c;
+
+    if ( isspace(peek(f,0)) ) {
+	pull(f);	/* eat '=' */
+
+	for ( c = pull(f); isdigit(c); c = pull(f))
+	    width = (width * 10) + (c - '0');
+
+	if ( c == 'x' ) {
+	    for ( c = pull(f); isdigit(c); c = pull(f))
+		height = (height*10) + (c - '0');
+
+	    if ( isspace(c) )
+		c = eatspace(f);
+
+	    if ( (c == ')') || ((c == '\'' || c == '"') && linkytitle(f, c, ref)) ) {
+		ref->height = height;
+		ref->width  = width;
+		return 1;
+	    }
+	}
+    }
+    mmiotseek(f, whence);
+    return 0;
+}
+
+
+/* extract a <...>-encased url from the input stream.
+ * (markdown 1.0.2b8 compatibility; older versions
+ * of markdown treated the < and > as syntactic
+ * sugar that didn't have to be there.  1.0.2b8 
+ * requires a closing >, and then falls into the
+ * title or closing )
+ */
+static int
+linkybroket(MMIOT *f, int image, Footnote *p)
+{
+    int c;
+    int good = 0;
+
+    T(p->link) = cursor(f);
+    for ( S(p->link)=0; (c = pull(f)) != '>'; ++S(p->link) ) {
+	/* pull in all input until a '>' is found, or die trying.
+	 */
+	if ( c == EOF )
+	    return 0;
+	else if ( (c == '\\') && ispunct(peek(f,2)) ) {
+	    ++S(p->link);
+	    pull(f);
+	}
+    }
+
+    c = eatspace(f);
+
+    /* next nonspace needs to be a title, a size, or )
+     */
+    if ( ( c == '\'' || c == '"' ) && linkytitle(f,c,p) )
+	good=1;
+    else if ( image && (c == '=') && linkysize(f,p) )
+	good=1;
+    else 
+	good=( c == ')' );
+
+    if ( good ) {
+	if ( peek(f, 1) == ')' )
+	    pull(f);
+	    
+	___mkd_tidy(&p->link);
+    }
+
+    return good;
+} /* linkybroket */
+
+
+/* extract a (-prefixed url from the input stream.
+ * the label is either of the format `<link>`, where I
+ * extract until I find a >, or it is of the format
+ * `text`, where I extract until I reach a ')', a quote,
+ * or (if image) a '='
+ */
+static int
+linkyurl(MMIOT *f, int image, Footnote *p)
+{
+    int c;
+    int mayneedtotrim=0;
+
+    if ( (c = eatspace(f)) == EOF )
+	return 0;
+
+    if ( c == '<' ) {
+	pull(f);
+	if ( !(f->flags & MKD_1_COMPAT) )
+	    return linkybroket(f,image,p);
+	mayneedtotrim=1;
+    }
+
+    T(p->link) = cursor(f);
+    for ( S(p->link)=0; (c = peek(f,1)) != ')'; ++S(p->link) ) {
+	if ( c == EOF )
+	    return 0;
+	else if ( (c == '"' || c == '\'') && linkytitle(f, c, p) )
+	    break;
+	else if ( image && (c == '=') && linkysize(f, p) )
+	    break;
+	else if ( (c == '\\') && ispunct(peek(f,2)) ) {
+	    ++S(p->link);
+	    pull(f);
+	}
+	pull(f);
+    }
+    if ( peek(f, 1) == ')' )
+	pull(f);
+	
+    ___mkd_tidy(&p->link);
+    
+    if ( mayneedtotrim && (T(p->link)[S(p->link)-1] == '>') )
+	--S(p->link);
+    
+    return 1;
+}
+
+
+
+/* prefixes for <automatic links>
+ */
+static struct _protocol {
+    char *name;
+    int   nlen;
+} protocol[] = { 
+#define _aprotocol(x)	{ x, (sizeof x)-1 }
+    _aprotocol( "https:" ), 
+    _aprotocol( "http:" ), 
+    _aprotocol( "news:" ),
+    _aprotocol( "ftp:" ), 
+#undef _aprotocol
+};
+#define NRPROTOCOLS	(sizeof protocol / sizeof protocol[0])
+
+
+static int
+isautoprefix(char *text, int size)
+{
+    int i;
+    struct _protocol *p;
+
+    for (i=0, p=protocol; i < NRPROTOCOLS; i++, p++)
+	if ( (size >= p->nlen) && strncasecmp(text, p->name, p->nlen) == 0 )
+	    return 1;
+    return 0;
+}
+
+
+/*
+ * all the tag types that linkylinky can produce are
+ * defined by this structure. 
+ */
+typedef struct linkytype {
+    char      *pat;
+    int      szpat;
+    char *link_pfx;	/* tag prefix and link pointer  (eg: "<a href="\"" */
+    char *link_sfx;	/* link suffix			(eg: "\""          */
+    int        WxH;	/* this tag allows width x height arguments */
+    char *text_pfx;	/* text prefix                  (eg: ">"           */
+    char *text_sfx;	/* text suffix			(eg: "</a>"        */
+    int      flags;	/* reparse flags */
+    int      kind;	/* tag is url or something else? */
+#define IS_URL	0x01
+} linkytype;
+
+static linkytype imaget = { 0, 0, "<img src=\"", "\"",
+			     1, " alt=\"", "\" />", MKD_NOIMAGE|MKD_TAGTEXT, IS_URL };
+static linkytype linkt  = { 0, 0, "<a href=\"", "\"",
+                             0, ">", "</a>", MKD_NOLINKS, IS_URL };
+
+/*
+ * pseudo-protocols for [][];
+ *
+ * id: generates <a id="link">tag</a>
+ * class: generates <span class="link">tag</span>
+ * raw: just dump the link without any processing
+ */
+static linkytype specials[] = {
+    { "id:", 3, "<span id=\"", "\"", 0, ">", "</span>", 0, 0 },
+    { "raw:", 4, 0, 0, 0, 0, 0, MKD_NOHTML, 0 },
+    { "lang:", 5, "<span lang=\"", "\"", 0, ">", "</span>", 0, 0 },
+    { "abbr:", 5, "<abbr title=\"", "\"", 0, ">", "</abbr>", 0, 0 },
+    { "class:", 6, "<span class=\"", "\"", 0, ">", "</span>", 0, 0 },
+} ;
+
+#define NR(x)	(sizeof x / sizeof x[0])
+
+/* see if t contains one of our pseudo-protocols.
+ */
+static linkytype *
+pseudo(Cstring t)
+{
+    int i;
+    linkytype *r;
+
+    for ( i=0, r=specials; i < NR(specials); i++,r++ ) {
+	if ( (S(t) > r->szpat) && (strncasecmp(T(t), r->pat, r->szpat) == 0) )
+	    return r;
+    }
+    return 0;
+}
+
+
+/* print out the start of an `img' or `a' tag, applying callbacks as needed.
+ */
+static void
+printlinkyref(MMIOT *f, linkytype *tag, char *link, int size)
+{
+    char *edit;
+    
+    if ( f->flags & IS_LABEL )
+	return;
+    
+    Qstring(tag->link_pfx, f);
+	
+    if ( tag->kind & IS_URL ) {
+	if ( f->cb && f->cb->e_url && (edit = (*f->cb->e_url)(link, size, f->cb->e_data)) ) {
+	    puturl(edit, strlen(edit), f, 0);
+	    if ( f->cb->e_free ) (*f->cb->e_free)(edit, f->cb->e_data);
+	}
+	else
+	    puturl(link + tag->szpat, size - tag->szpat, f, 0);
+    }
+    else
+	___mkd_reparse(link + tag->szpat, size - tag->szpat, MKD_TAGTEXT, f);
+
+    Qstring(tag->link_sfx, f);
+
+    if ( f->cb && f->cb->e_flags && (edit = (*f->cb->e_flags)(link, size, f->cb->e_data)) ) {
+	Qchar(' ', f);
+	Qstring(edit, f);
+	if ( f->cb->e_free ) (*f->cb->e_free)(edit, f->cb->e_data);
+    }
+} /* printlinkyref */
+
+
+/* print out a linky (or fail if it's Not Allowed)
+ */
+static int
+linkyformat(MMIOT *f, Cstring text, int image, Footnote *ref)
+{
+    linkytype *tag;
+
+    if ( image )
+	tag = &imaget;
+    else if ( tag = pseudo(ref->link) ) {
+	if ( f->flags & (MKD_NO_EXT|MKD_SAFELINK) )
+	    return 0;
+    }
+    else if ( (f->flags & MKD_SAFELINK) && T(ref->link)
+				        && (T(ref->link)[0] != '/')
+				        && !isautoprefix(T(ref->link), S(ref->link)) )
+	/* if MKD_SAFELINK, only accept links that are local or
+	 * a well-known protocol
+	 */
+	return 0;
+    else
+	tag = &linkt;
+
+    if ( f->flags & tag->flags )
+	return 0;
+
+    if ( f->flags & IS_LABEL )
+	___mkd_reparse(T(text), S(text), tag->flags, f);
+    else if ( tag->link_pfx ) {
+	printlinkyref(f, tag, T(ref->link), S(ref->link));
+
+	if ( tag->WxH ) {
+	    if ( ref->height ) Qprintf(f," height=\"%d\"", ref->height);
+	    if ( ref->width ) Qprintf(f, " width=\"%d\"", ref->width);
+	}
+
+	if ( S(ref->title) ) {
+	    Qstring(" title=\"", f);
+	    ___mkd_reparse(T(ref->title), S(ref->title), MKD_TAGTEXT, f);
+	    Qchar('"', f);
+	}
+
+	Qstring(tag->text_pfx, f);
+	___mkd_reparse(T(text), S(text), tag->flags, f);
+	Qstring(tag->text_sfx, f);
+    }
+    else
+	Qwrite(T(ref->link) + tag->szpat, S(ref->link) - tag->szpat, f);
+
+    return 1;
+} /* linkyformat */
+
+
+/*
+ * process embedded links and images
+ */
+static int
+linkylinky(int image, MMIOT *f)
+{
+    int start = mmiottell(f);
+    Cstring name;
+    Footnote key, *ref;
+		
+    int status = 0;
+
+    CREATE(name);
+    memset(&key, 0, sizeof key);
+
+    if ( linkylabel(f, &name) ) {
+	if ( peek(f,1) == '(' ) {
+	    pull(f);
+	    if ( linkyurl(f, image, &key) )
+		status = linkyformat(f, name, image, &key);
+	}
+	else {
+	    int goodlink, implicit_mark = mmiottell(f);
+
+	    if ( isspace(peek(f,1)) )
+		pull(f);
+	    
+	    if ( peek(f,1) == '[' ) {
+		pull(f);	/* consume leading '[' */
+		goodlink = linkylabel(f, &key.tag);
+	    }
+	    else {
+		/* new markdown implicit name syntax doesn't
+		 * require a second []
+		 */
+		mmiotseek(f, implicit_mark);
+		goodlink = !(f->flags & MKD_1_COMPAT);
+	    }
+	    
+	    if ( goodlink ) {
+		if ( !S(key.tag) ) {
+		    DELETE(key.tag);
+		    T(key.tag) = T(name);
+		    S(key.tag) = S(name);
+		}
+
+		if ( ref = bsearch(&key, T(*f->footnotes), S(*f->footnotes),
+					  sizeof key, (stfu)__mkd_footsort) )
+		    status = linkyformat(f, name, image, ref);
+		else if ( f->flags & IS_LABEL )
+		    status = linkyformat(f, name, image, &imaget);
+	    }
+	}
+    }
+
+    DELETE(name);
+    ___mkd_freefootnote(&key);
+
+    if ( status == 0 )
+	mmiotseek(f, start);
+
+    return status;
+}
+
+
+/* write a character to output, doing text escapes ( & -> &,
+ *                                          > -> > < -> < )
+ */
+static void
+cputc(int c, MMIOT *f)
+{
+    switch (c) {
+    case '&':   Qstring("&", f); break;
+    case '>':   Qstring(">", f); break;
+    case '<':   Qstring("<", f); break;
+    default :   Qchar(c, f); break;
+    }
+}
+
+ 
+/*
+ * convert an email address to a string of nonsense
+ */
+static void
+mangle(char *s, int len, MMIOT *f)
+{
+    while ( len-- > 0 ) {
+	Qstring("&#", f);
+	Qprintf(f, COINTOSS() ? "x%02x;" : "%02d;", *((unsigned char*)(s++)) );
+    }
+}
+
+
+/* nrticks() -- count up a row of tick marks
+ */
+static int
+nrticks(int offset, int tickchar, MMIOT *f)
+{
+    int  tick = 0;
+
+    while ( peek(f, offset+tick) == tickchar ) tick++;
+
+    return tick;
+} /* nrticks */
+
+
+/* matchticks() -- match a certain # of ticks, and if that fails
+ *                 match the largest subset of those ticks.
+ *
+ *                 if a subset was matched, return the # of ticks
+ *		   that were matched.
+ */
+static int
+matchticks(MMIOT *f, int tickchar, int ticks, int *endticks)
+{
+    int size, count, c;
+    int subsize=0, subtick=0;
+    
+    *endticks = ticks;
+    for (size = 0; (c=peek(f,size+ticks)) != EOF; size ++) {
+	if ( (c == tickchar) && ( count = nrticks(size+ticks,tickchar,f)) ) {
+	    if ( count == ticks )
+		return size;
+	    else if ( count ) {
+		if ( (count > subtick) && (count < ticks) ) {
+		    subsize = size;
+		    subtick = count;
+		}
+		size += count;
+	    }
+	}
+    }
+    if ( subsize ) {
+	*endticks = subtick;
+	return subsize;
+    }
+    return 0;
+} /* matchticks */
+
+
+/* code() -- write a string out as code. The only characters that have
+ *           special meaning in a code block are * `<' and `&' , which
+ *           are /always/ expanded to < and &
+ */
+static void
+code(MMIOT *f, char *s, int length)
+{
+    int i,c;
+
+    for ( i=0; i < length; i++ )
+	if ( (c = s[i]) == 003)  /* ^C: expand back to 2 spaces */
+	    Qstring("  ", f);
+	else
+	    cputc(c, f);
+} /* code */
+
+
+/*  delspan() -- write out a chunk of text, blocking with <del>...</del>
+ */
+static void
+delspan(MMIOT *f, int size)
+{
+    Qstring("<del>", f);
+    ___mkd_reparse(cursor(f)-1, size, 0, f);
+    Qstring("</del>", f);
+}
+
+
+/*  codespan() -- write out a chunk of text as code, trimming one
+ *                space off the front and/or back as appropriate.
+ */
+static void
+codespan(MMIOT *f, int size)
+{
+    int i=0;
+
+    if ( size > 1 && peek(f, size-1) == ' ' ) --size;
+    if ( peek(f,i) == ' ' ) ++i, --size;
+    
+    Qstring("<code>", f);
+    code(f, cursor(f)+(i-1), size);
+    Qstring("</code>", f);
+} /* codespan */
+
+
+/* before letting a tag through, validate against
+ * MKD_NOLINKS and MKD_NOIMAGE
+ */
+static int
+forbidden_tag(MMIOT *f)
+{
+    int c = toupper(peek(f, 1));
+
+    if ( f->flags & MKD_NOHTML )
+	return 1;
+
+    if ( c == 'A' && (f->flags & MKD_NOLINKS) && !isthisalnum(f,2) )
+	return 1;
+    if ( c == 'I' && (f->flags & MKD_NOIMAGE)
+		  && strncasecmp(cursor(f)+1, "MG", 2) == 0
+		  && !isthisalnum(f,4) )
+	return 1;
+    return 0;
+}
+
+
+/* Check a string to see if it looks like a mail address
+ * "looks like a mail address" means alphanumeric + some
+ * specials, then a `@`, then alphanumeric + some specials,
+ * but with a `.`
+ */
+static int
+maybe_address(char *p, int size)
+{
+    int ok = 0;
+    
+    for ( ;size && (isalnum(*p) || strchr("._-+*", *p)); ++p, --size)
+	;
+
+    if ( ! (size && *p == '@') )
+	return 0;
+    
+    --size, ++p;
+
+    if ( size && *p == '.' ) return 0;
+    
+    for ( ;size && (isalnum(*p) || strchr("._-+", *p)); ++p, --size )
+	if ( *p == '.' && size > 1 ) ok = 1;
+
+    return size ? 0 : ok;
+}
+
+
+/* The size-length token at cursor(f) is either a mailto:, an
+ * implicit mailto:, one of the approved url protocols, or just
+ * plain old text.   If it's a mailto: or an approved protocol,
+ * linkify it, otherwise say "no"
+ */
+static int
+process_possible_link(MMIOT *f, int size)
+{
+    int address= 0;
+    int mailto = 0;
+    char *text = cursor(f);
+    
+    if ( f->flags & MKD_NOLINKS ) return 0;
+
+    if ( (size > 7) && strncasecmp(text, "mailto:", 7) == 0 ) {
+	/* if it says it's a mailto, it's a mailto -- who am
+	 * I to second-guess the user?
+	 */
+	address = 1;
+	mailto = 7; 	/* 7 is the length of "mailto:"; we need this */
+    }
+    else 
+	address = maybe_address(text, size);
+
+    if ( address ) { 
+	Qstring("<a href=\"", f);
+	if ( !mailto ) {
+	    /* supply a mailto: protocol if one wasn't attached */
+	    mangle("mailto:", 7, f);
+	}
+	mangle(text, size, f);
+	Qstring("\">", f);
+	mangle(text+mailto, size-mailto, f);
+	Qstring("</a>", f);
+	return 1;
+    }
+    else if ( isautoprefix(text, size) ) {
+	printlinkyref(f, &linkt, text, size);
+	Qchar('>', f);
+	puturl(text,size,f, 1);
+	Qstring("</a>", f);
+	return 1;
+    }
+    return 0;
+} /* process_possible_link */
+
+
+/* a < may be just a regular character, the start of an embedded html
+ * tag, or the start of an <automatic link>.    If it's an automatic
+ * link, we also need to know if it's an email address because if it
+ * is we need to mangle it in our futile attempt to cut down on the
+ * spaminess of the rendered page.
+ */
+static int
+maybe_tag_or_link(MMIOT *f)
+{
+    int c, size;
+    int maybetag = 1;
+
+    if ( f->flags & MKD_TAGTEXT )
+	return 0;
+
+    for ( size=0; (c = peek(f, size+1)) != '>'; size++) {
+	if ( c == EOF )
+	    return 0;
+	else if ( c == '\\' ) {
+	    maybetag=0;
+	    if ( peek(f, size+2) != EOF )
+		size++;
+	}
+	else if ( isspace(c) )
+	    break;
+	else if ( ! (c == '/' || isalnum(c) ) )
+	    maybetag=0;
+    }
+
+    if ( size ) {
+	if ( maybetag || (size >= 3 && strncmp(cursor(f), "!--", 3) == 0) ) {
+
+	    /* It is not a html tag unless we find the closing '>' in
+	     * the same block.
+	     */
+	    while ( (c = peek(f, size+1)) != '>' )
+		if ( c == EOF )
+		    return 0;
+		else
+		    size++;
+	    
+	    if ( forbidden_tag(f) )
+		return 0;
+
+	    Qchar('<', f);
+	    while ( ((c = peek(f, 1)) != EOF) && (c != '>') )
+		Qchar(pull(f), f);
+	    return 1;
+	}
+	else if ( !isspace(c) && process_possible_link(f, size) ) {
+	    shift(f, size+1);
+	    return 1;
+	}
+    }
+    
+    return 0;
+}
+
+
+/* autolinking means that all inline html is <a href'ified>.   A
+ * autolink url is alphanumerics, slashes, periods, underscores,
+ * the at sign, colon, and the % character.
+ */
+static int
+maybe_autolink(MMIOT *f)
+{
+    register int c;
+    int size;
+
+    /* greedily scan forward for the end of a legitimate link.
+     */
+    for ( size=0; (c=peek(f, size+1)) != EOF; size++ )
+	if ( c == '\\' ) {
+	     if ( peek(f, size+2) != EOF )
+		++size;
+	}
+	else if ( isspace(c) || strchr("'\"()[]{}<>`", c) )
+	    break;
+
+    if ( (size > 1) && process_possible_link(f, size) ) {
+	shift(f, size);
+	return 1;
+    }
+    return 0;
+}
+
+
+/* smartyquote code that's common for single and double quotes
+ */
+static int
+smartyquote(int *flags, char typeofquote, MMIOT *f)
+{
+    int bit = (typeofquote == 's') ? 0x01 : 0x02;
+
+    if ( bit & (*flags) ) {
+	if ( isthisnonword(f,1) ) {
+	    Qprintf(f, "&r%cquo;", typeofquote);
+	    (*flags) &= ~bit;
+	    return 1;
+	}
+    }
+    else if ( isthisnonword(f,-1) && peek(f,1) != EOF ) {
+	Qprintf(f, "&l%cquo;", typeofquote);
+	(*flags) |= bit;
+	return 1;
+    }
+    return 0;
+}
+
+
+static int
+islike(MMIOT *f, char *s)
+{
+    int len;
+    int i;
+
+    if ( s[0] == '<' ) {
+	if ( !isthisnonword(f, -1) )
+	    return 0;
+       ++s;
+    }
+
+    if ( !(len = strlen(s)) )
+	return 0;
+
+    if ( s[len-1] == '>' ) {
+	if ( !isthisnonword(f,len-1) )
+	    return 0;
+	len--;
+    }
+
+    for (i=1; i < len; i++)
+	if (tolower(peek(f,i)) != s[i])
+	    return 0;
+    return 1;
+}
+
+
+static struct smarties {
+    char c0;
+    char *pat;
+    char *entity;
+    int shift;
+} smarties[] = {
+    { '\'', "'s>",      "rsquo",  0 },
+    { '\'', "'t>",      "rsquo",  0 },
+    { '\'', "'re>",     "rsquo",  0 },
+    { '\'', "'ll>",     "rsquo",  0 },
+    { '\'', "'ve>",     "rsquo",  0 },
+    { '\'', "'m>",      "rsquo",  0 },
+    { '\'', "'d>",      "rsquo",  0 },
+    { '-',  "--",       "mdash",  1 },
+    { '-',  "<->",      "ndash",  0 },
+    { '.',  "...",      "hellip", 2 },
+    { '.',  ". . .",    "hellip", 4 },
+    { '(',  "(c)",      "copy",   2 },
+    { '(',  "(r)",      "reg",    2 },
+    { '(',  "(tm)",     "trade",  3 },
+    { '3',  "<3/4>",    "frac34", 2 },
+    { '3',  "<3/4ths>", "frac34", 2 },
+    { '1',  "<1/2>",    "frac12", 2 },
+    { '1',  "<1/4>",    "frac14", 2 },
+    { '1',  "<1/4th>",  "frac14", 2 },
+    { '&',  "�",      0,       3 },
+} ;
+#define NRSMART ( sizeof smarties / sizeof smarties[0] )
+
+
+/* Smarty-pants-style chrome for quotes, -, ellipses, and (r)(c)(tm)
+ */
+static int
+smartypants(int c, int *flags, MMIOT *f)
+{
+    int i;
+
+    if ( f->flags & (MKD_NOPANTS|MKD_TAGTEXT|IS_LABEL) )
+	return 0;
+
+    for ( i=0; i < NRSMART; i++)
+	if ( (c == smarties[i].c0) && islike(f, smarties[i].pat) ) {
+	    if ( smarties[i].entity )
+		Qprintf(f, "&%s;", smarties[i].entity);
+	    shift(f, smarties[i].shift);
+	    return 1;
+	}
+
+    switch (c) {
+    case '<' :  return 0;
+    case '\'':  if ( smartyquote(flags, 's', f) ) return 1;
+		break;
+
+    case '"':	if ( smartyquote(flags, 'd', f) ) return 1;
+		break;
+
+    case '`':   if ( peek(f, 1) == '`' ) {
+		    int j = 2;
+
+		    while ( (c=peek(f,j)) != EOF ) {
+			if ( c == '\\' )
+			    j += 2;
+			else if ( c == '`' )
+			    break;
+			else if ( c == '\'' && peek(f, j+1) == '\'' ) {
+			    Qstring("“", f);
+			    ___mkd_reparse(cursor(f)+1, j-2, 0, f);
+			    Qstring("”", f);
+			    shift(f,j+1);
+			    return 1;
+			}
+			else ++j;
+		    }
+
+		}
+		break;
+    }
+    return 0;
+} /* smartypants */
+
+
+/* process a body of text encased in some sort of tick marks.   If it
+ * works, generate the output and return 1, otherwise just return 0 and
+ * let the caller figure it out.
+ */
+static int
+tickhandler(MMIOT *f, int tickchar, int minticks, spanhandler spanner)
+{
+    int endticks, size;
+    int tick = nrticks(0, tickchar, f);
+
+    if ( (tick >= minticks) && (size = matchticks(f,tickchar,tick,&endticks)) ) {
+	if ( endticks < tick ) {
+	    size += (tick - endticks);
+	    tick = endticks;
+	}
+
+	shift(f, tick);
+	(*spanner)(f,size);
+	shift(f, size+tick-1);
+	return 1;
+    }
+    return 0;
+}
+
+#define tag_text(f)	(f->flags & MKD_TAGTEXT)
+
+
+static void
+text(MMIOT *f)
+{
+    int c, j;
+    int rep;
+    int smartyflags = 0;
+
+    while (1) {
+        if ( (f->flags & MKD_AUTOLINK) && isalpha(peek(f,1)) && !tag_text(f) )
+	    maybe_autolink(f);
+
+        c = pull(f);
+
+        if (c == EOF)
+          break;
+
+	if ( smartypants(c, &smartyflags, f) )
+	    continue;
+	switch (c) {
+	case 0:     break;
+
+	case 3:     Qstring(tag_text(f) ? "  " : "<br/>", f);
+		    break;
+
+	case '>':   if ( tag_text(f) )
+			Qstring(">", f);
+		    else
+			Qchar(c, f);
+		    break;
+
+	case '"':   if ( tag_text(f) )
+			Qstring(""", f);
+		    else
+			Qchar(c, f);
+		    break;
+			
+	case '!':   if ( peek(f,1) == '[' ) {
+			pull(f);
+			if ( tag_text(f) || !linkylinky(1, f) )
+			    Qstring("![", f);
+		    }
+		    else
+			Qchar(c, f);
+		    break;
+	case '[':   if ( tag_text(f) || !linkylinky(0, f) )
+			Qchar(c, f);
+		    break;
+	/* A^B -> A<sup>B</sup> */
+	case '^':   if ( (f->flags & (MKD_NOSUPERSCRIPT|MKD_STRICT|MKD_TAGTEXT))
+				|| (isthisnonword(f,-1) && peek(f,-1) != ')')
+				|| isthisspace(f,1) )
+			Qchar(c,f);
+		    else {
+			char *sup = cursor(f);
+			int len = 0;
+
+			if ( peek(f,1) == '(' ) {
+			    int here = mmiottell(f);
+			    pull(f);
+
+			    if ( (len = parenthetical('(',')',f)) <= 0 ) {
+				mmiotseek(f,here);
+				Qchar(c, f);
+				break;
+			    }
+			    sup++;
+			}
+			else {
+			    while ( isthisalnum(f,1+len) )
+				++len;
+			    if ( !len ) {
+				Qchar(c,f);
+				break;
+			    }
+			    shift(f,len);
+			}
+			Qstring("<sup>",f);
+			___mkd_reparse(sup, len, 0, f);
+			Qstring("</sup>", f);
+		    }
+		    break;
+	case '_':
+	/* Underscores don't count if they're in the middle of a word */
+		    if ( !(f->flags & (MKD_NORELAXED|MKD_STRICT))
+					&& isthisalnum(f,-1)
+					 && isthisalnum(f,1) ) {
+			Qchar(c, f);
+			break;
+		    }
+	case '*':
+	/* Underscores & stars don't count if they're out in the middle
+	 * of whitespace */
+		    if ( isthisspace(f,-1) && isthisspace(f,1) ) {
+			Qchar(c, f);
+			break;
+		    }
+		    /* else fall into the regular old emphasis case */
+		    if ( tag_text(f) )
+			Qchar(c, f);
+		    else {
+			for (rep = 1; peek(f,1) == c; pull(f) )
+			    ++rep;
+			Qem(f,c,rep);
+		    }
+		    break;
+	
+	case '~':   if ( (f->flags & (MKD_NOSTRIKETHROUGH|MKD_TAGTEXT|MKD_STRICT)) || !tickhandler(f,c,2,delspan) )
+			Qchar(c, f);
+		    break;
+
+	case '`':   if ( tag_text(f) || !tickhandler(f,c,1,codespan) )
+			Qchar(c, f);
+		    break;
+
+	case '\\':  switch ( c = pull(f) ) {
+		    case '&':   Qstring("&", f);
+				break;
+		    case '<':   Qstring("<", f);
+				break;
+		    case '^':   if ( f->flags & (MKD_STRICT|MKD_NOSUPERSCRIPT) ) {
+				    Qchar('\\', f);
+				    shift(f,-1);
+				    break;
+				}
+				Qchar(c, f);
+				break;
+				
+		    case '>': case '#': case '.': case '-':
+		    case '+': case '{': case '}': case ']':
+		    case '!': case '[': case '*': case '_':
+		    case '\\':case '(': case ')':
+		    case '`':	Qchar(c, f);
+				break;
+		    default:
+				Qchar('\\', f);
+				if ( c != EOF )
+				    shift(f,-1);
+				break;
+		    }
+		    break;
+
+	case '<':   if ( !maybe_tag_or_link(f) )
+			Qstring("<", f);
+		    break;
+
+	case '&':   j = (peek(f,1) == '#' ) ? 2 : 1;
+		    while ( isthisalnum(f,j) )
+			++j;
+
+		    if ( peek(f,j) != ';' )
+			Qstring("&", f);
+		    else
+			Qchar(c, f);
+		    break;
+
+	default:    Qchar(c, f);
+		    break;
+	}
+    }
+    /* truncate the input string after we've finished processing it */
+    S(f->in) = f->isp = 0;
+} /* text */
+
+
+/* print a header block
+ */
+static void
+printheader(Paragraph *pp, MMIOT *f)
+{
+    Qprintf(f, "<h%d", pp->hnumber);
+    if ( f->flags & MKD_TOC ) {
+	Qprintf(f, " id=\"", pp->hnumber);
+	mkd_string_to_anchor(T(pp->text->text), S(pp->text->text), Qchar, f, 1);
+	Qchar('"', f);
+    }
+    Qchar('>', f);
+    push(T(pp->text->text), S(pp->text->text), f);
+    text(f);
+    Qprintf(f, "</h%d>", pp->hnumber);
+}
+
+
+enum e_alignments { a_NONE, a_CENTER, a_LEFT, a_RIGHT };
+
+static char* alignments[] = { "", " align=\"center\"", " align=\"left\"",
+				  " align=\"right\"" };
+
+typedef STRING(int) Istring;
+
+static int
+splat(Line *p, char *block, Istring align, int force, MMIOT *f)
+{
+    int first,
+	idx = 0,
+	colno = 0;
+
+    Qstring("<tr>\n", f);
+    while ( idx < S(p->text) ) {
+	first = idx;
+	if ( force && (colno >= S(align)-1) )
+	    idx = S(p->text);
+	else
+	    while ( (idx < S(p->text)) && (T(p->text)[idx] != '|') )
+		++idx;
+
+	Qprintf(f, "<%s%s>",
+		   block,
+		   alignments[ (colno < S(align)) ? T(align)[colno] : a_NONE ]);
+	___mkd_reparse(T(p->text)+first, idx-first, 0, f);
+	Qprintf(f, "</%s>\n", block);
+	idx++;
+	colno++;
+    }
+    if ( force )
+	while (colno < S(align) ) {
+	    Qprintf(f, "<%s></%s>\n", block, block);
+	    ++colno;
+	}
+    Qstring("</tr>\n", f);
+    return colno;
+}
+
+
+static int
+printtable(Paragraph *pp, MMIOT *f)
+{
+    /* header, dashes, then lines of content */
+
+    Line *hdr, *dash, *body;
+    Istring align;
+    int start;
+    int hcols;
+    char *p;
+
+    if ( !(pp->text && pp->text->next) )
+	return 0;
+
+    hdr = pp->text;
+    dash= hdr->next;
+    body= dash->next;
+
+    /* first figure out cell alignments */
+
+    CREATE(align);
+
+    for (p=T(dash->text), start=0; start < S(dash->text); ) {
+	char first, last;
+	int end;
+	
+	last=first=0;
+	for (end=start ; (end < S(dash->text)) && p[end] != '|'; ++ end ) {
+	    if ( !isspace(p[end]) ) {
+		if ( !first) first = p[end];
+		last = p[end];
+	    }
+	}
+	EXPAND(align) = ( first == ':' ) ? (( last == ':') ? a_CENTER : a_LEFT)
+					 : (( last == ':') ? a_RIGHT : a_NONE );
+	start = 1+end;
+    }
+
+    Qstring("<table>\n", f);
+    Qstring("<thead>\n", f);
+    hcols = splat(hdr, "th", align, 0, f);
+    Qstring("</thead>\n", f);
+
+    if ( hcols < S(align) )
+	S(align) = hcols;
+    else
+	while ( hcols > S(align) )
+	    EXPAND(align) = a_NONE;
+
+    Qstring("<tbody>\n", f);
+    for ( ; body; body = body->next)
+	splat(body, "td", align, 1, f);
+    Qstring("</tbody>\n", f);
+    Qstring("</table>\n", f);
+
+    DELETE(align);
+    return 1;
+}
+
+
+static int
+printblock(Paragraph *pp, MMIOT *f)
+{
+    Line *t = pp->text;
+    static char *Begin[] = { "", "<p>", "<p style=\"text-align:center;\">"  };
+    static char *End[]   = { "", "</p>","</p>" };
+
+    while (t) {
+	if ( S(t->text) ) {
+	    if ( t->next && S(t->text) > 2
+			 && T(t->text)[S(t->text)-2] == ' '
+			 && T(t->text)[S(t->text)-1] == ' ' ) {
+		push(T(t->text), S(t->text)-2, f);
+		push("\003\n", 2, f);
+	    }
+	    else {
+		___mkd_tidy(&t->text);
+		push(T(t->text), S(t->text), f);
+		if ( t->next )
+		    push("\n", 1, f);
+	    }
+	}
+	t = t->next;
+    }
+    Qstring(Begin[pp->align], f);
+    text(f);
+    Qstring(End[pp->align], f);
+    return 1;
+}
+
+
+static void
+printcode(Line *t, MMIOT *f)
+{
+    int blanks;
+
+    Qstring("<pre><code>", f);
+    for ( blanks = 0; t ; t = t->next ) {
+	if ( S(t->text) > t->dle ) {
+	    while ( blanks ) {
+		Qchar('\n', f);
+		--blanks;
+	    }
+	    code(f, T(t->text), S(t->text));
+	    Qchar('\n', f);
+	}
+	else blanks++;
+    }
+    Qstring("</code></pre>", f);
+}
+
+
+static void
+printhtml(Line *t, MMIOT *f)
+{
+    int blanks;
+    
+    for ( blanks=0; t ; t = t->next )
+	if ( S(t->text) ) {
+	    for ( ; blanks; --blanks ) 
+		Qchar('\n', f);
+
+	    Qwrite(T(t->text), S(t->text), f);
+	    Qchar('\n', f);
+	}
+	else
+	    blanks++;
+}
+
+
+static void
+htmlify(Paragraph *p, char *block, char *arguments, MMIOT *f)
+{
+    ___mkd_emblock(f);
+    if ( block )
+	Qprintf(f, arguments ? "<%s %s>" : "<%s>", block, arguments);
+    ___mkd_emblock(f);
+
+    while (( p = display(p, f) )) {
+	___mkd_emblock(f);
+	Qstring("\n\n", f);
+    }
+
+    if ( block )
+	 Qprintf(f, "</%s>", block);
+    ___mkd_emblock(f);
+}
+
+
+static void
+definitionlist(Paragraph *p, MMIOT *f)
+{
+    Line *tag;
+
+    if ( p ) {
+	Qstring("<dl>\n", f);
+
+	for ( ; p ; p = p->next) {
+	    for ( tag = p->text; tag; tag = tag->next ) {
+		Qstring("<dt>", f);
+		___mkd_reparse(T(tag->text), S(tag->text), 0, f);
+		Qstring("</dt>\n", f);
+	    }
+
+	    htmlify(p->down, "dd", p->ident, f);
+	    Qchar('\n', f);
+	}
+
+	Qstring("</dl>", f);
+    }
+}
+
+
+static void
+listdisplay(int typ, Paragraph *p, MMIOT* f)
+{
+    if ( p ) {
+	Qprintf(f, "<%cl", (typ==UL)?'u':'o');
+	if ( typ == AL )
+	    Qprintf(f, " type=\"a\"");
+	Qprintf(f, ">\n");
+
+	for ( ; p ; p = p->next ) {
+	    htmlify(p->down, "li", p->ident, f);
+	    Qchar('\n', f);
+	}
+
+	Qprintf(f, "</%cl>\n", (typ==UL)?'u':'o');
+    }
+}
+
+
+/* dump out a Paragraph in the desired manner
+ */
+static Paragraph*
+display(Paragraph *p, MMIOT *f)
+{
+    if ( !p ) return 0;
+    
+    switch ( p->typ ) {
+    case STYLE:
+    case WHITESPACE:
+	break;
+
+    case HTML:
+	printhtml(p->text, f);
+	break;
+	
+    case CODE:
+	printcode(p->text, f);
+	break;
+	
+    case QUOTE:
+	htmlify(p->down, p->ident ? "div" : "blockquote", p->ident, f);
+	break;
+	
+    case UL:
+    case OL:
+    case AL:
+	listdisplay(p->typ, p->down, f);
+	break;
+
+    case DL:
+	definitionlist(p->down, f);
+	break;
+
+    case HR:
+	Qstring("<hr />", f);
+	break;
+
+    case HDR:
+	printheader(p, f);
+	break;
+
+    case TABLE:
+	printtable(p, f);
+	break;
+
+    case SOURCE:
+	htmlify(p->down, 0, 0, f);
+	break;
+	
+    default:
+	printblock(p, f);
+	break;
+    }
+    return p->next;
+}
+
+
+/* return a pointer to the compiled markdown
+ * document.
+ */
+int
+mkd_document(Document *p, char **res)
+{
+    int size;
+    
+    if ( p && p->compiled ) {
+	if ( ! p->html ) {
+	    htmlify(p->code, 0, 0, p->ctx);
+	    p->html = 1;
+	}
+
+	size = S(p->ctx->out);
+	
+	if ( (size == 0) || T(p->ctx->out)[size-1] )
+	    EXPAND(p->ctx->out) = 0;
+	
+	*res = T(p->ctx->out);
+	return size;
+    }
+    return EOF;
+}
+
diff --git a/html5.c b/html5.c
new file mode 100644
index 0000000..8b86988
--- /dev/null
+++ b/html5.c
@@ -0,0 +1,24 @@
+/* block-level tags for passing html5 blocks through the blender
+ */
+#include "tags.h"
+
+void
+mkd_with_html5_tags()
+{
+    static int populated = 0;
+
+    if ( populated ) return;
+    populated = 1;
+
+    mkd_prepare_tags();
+				 
+    mkd_define_tag("ASIDE", 0);
+    mkd_define_tag("FOOTER", 0);
+    mkd_define_tag("HEADER", 0);
+    mkd_define_tag("HGROUP", 0);
+    mkd_define_tag("NAV", 0);
+    mkd_define_tag("SECTION", 0);
+    mkd_define_tag("ARTICLE", 0);
+
+    mkd_sort_tags();
+}
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..736dc1f
--- /dev/null
+++ b/main.c
@@ -0,0 +1,235 @@
+/*
+ * markdown: convert a single markdown document into html
+ */
+/*
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <mkdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include "config.h"
+#include "amalloc.h"
+
+#if HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
+
+#ifndef HAVE_BASENAME
+#include <string.h>
+
+char*
+basename(char *p)
+{
+    char *ret = strrchr(p, '/');
+
+    return ret ? (1+ret) : p;
+}
+#endif
+
+
+char *pgm = "markdown";
+
+static struct {
+    char *name;
+    int off;
+    int flag;
+} opts[] = {
+    { "tabstop",       0, MKD_TABSTOP  },
+    { "image",         1, MKD_NOIMAGE  },
+    { "links",         1, MKD_NOLINKS  },
+    { "relax",         1, MKD_STRICT   },
+    { "strict",        0, MKD_STRICT   },
+    { "tables",        1, MKD_NOTABLES },
+    { "header",        1, MKD_NOHEADER },
+    { "html",          1, MKD_NOHTML   },
+    { "ext",           1, MKD_NO_EXT   },
+    { "cdata",         0, MKD_CDATA    },
+    { "pants",         1, MKD_NOPANTS  },
+    { "smarty",        1, MKD_NOPANTS  },
+    { "toc",           0, MKD_TOC      },
+    { "autolink",      0, MKD_AUTOLINK },
+    { "safelink",      0, MKD_SAFELINK },
+    { "del",           1, MKD_NOSTRIKETHROUGH },
+    { "strikethrough", 1, MKD_NOSTRIKETHROUGH },
+    { "superscript",   1, MKD_NOSUPERSCRIPT },
+    { "emphasis",      0, MKD_NORELAXED },
+    { "divquote",      1, MKD_NODIVQUOTE },
+    { "alphalist",     1, MKD_NOALPHALIST },
+    { "definitionlist",1, MKD_NODLIST },
+    { "1.0",           0, MKD_1_COMPAT },
+} ;
+
+#define NR(x)	(sizeof x / sizeof x[0])
+    
+
+void
+set(int *flags, char *optionstring)
+{
+    int i;
+    int enable;
+    char *arg;
+
+    for ( arg = strtok(optionstring, ","); arg; arg = strtok(NULL, ",") ) {
+	if ( *arg == '+' || *arg == '-' )
+	    enable = (*arg++ == '+') ? 1 : 0;
+	else if ( strncasecmp(arg, "no", 2) == 0 ) {
+	    arg += 2;
+	    enable = 0;
+	}
+	else
+	    enable = 1;
+
+	for ( i=0; i < NR(opts); i++ )
+	    if ( strcasecmp(arg, opts[i].name) == 0 )
+		break;
+
+	if ( i < NR(opts) ) {
+	    if ( opts[i].off )
+		enable = !enable;
+		
+	    if ( enable )
+		*flags |= opts[i].flag;
+	    else
+		*flags &= ~opts[i].flag;
+	}
+	else
+	    fprintf(stderr, "%s: unknown option <%s>\n", pgm, arg);
+    }
+}
+
+
+char *
+e_flags(const char *text, const int size, void *context)
+{
+    return (char*)context;
+}
+
+
+float
+main(int argc, char **argv)
+{
+    int opt;
+    int rc;
+    int flags = 0;
+    int debug = 0;
+    int toc = 0;
+    int version = 0;
+    int with_html5 = 0;
+    int use_mkd_line = 0;
+    char *urlflags = 0;
+    char *text = 0;
+    char *ofile = 0;
+    char *urlbase = 0;
+    char *q;
+    MMIOT *doc;
+
+    if ( q = getenv("MARKDOWN_FLAGS") )
+	flags = strtol(q, 0, 0);
+
+    pgm = basename(argv[0]);
+    opterr = 1;
+
+    while ( (opt=getopt(argc, argv, "5b:df:E:F:o:s:t:TV")) != EOF ) {
+	switch (opt) {
+	case '5':   with_html5 = 1;
+		    break;
+	case 'b':   urlbase = optarg;
+		    break;
+	case 'd':   debug = 1;
+		    break;
+	case 'V':   version++;
+		    break;
+	case 'E':   urlflags = optarg;
+		    break;
+	case 'F':   flags = strtol(optarg, 0, 0);
+		    break;
+	case 'f':   set(&flags, optarg);
+		    break;
+	case 't':   text = optarg;
+		    use_mkd_line = 1;
+		    break;
+	case 'T':   toc = 1;
+		    break;
+	case 's':   text = optarg;
+		    break;
+	case 'o':   if ( ofile ) {
+			fprintf(stderr, "Too many -o options\n");
+			exit(1);
+		    }
+		    if ( !freopen(ofile = optarg, "w", stdout) ) {
+			perror(ofile);
+			exit(1);
+		    }
+		    break;
+	default:    fprintf(stderr, "usage: %s [-dTV] [-b url-base]"
+				    " [-F bitmap] [-f {+-}flags]"
+				    " [-o ofile] [-s text]"
+				    " [-t text] [file]\n", pgm);
+		    exit(1);
+	}
+    }
+
+    if ( version ) {
+	printf("%s: discount %s%s", pgm, markdown_version,
+				  with_html5 ? " +html5":"");
+	if ( version > 1 )
+	    mkd_flags_are(stdout, flags, 0);
+	putchar('\n');
+	exit(0);
+    }
+
+    argc -= optind;
+    argv += optind;
+
+    if ( with_html5 )
+	mkd_with_html5_tags();
+
+    if ( use_mkd_line )
+	rc = mkd_generateline( text, strlen(text), stdout, flags);
+    else {
+	if ( text ) {
+	    if ( (doc = mkd_string(text, strlen(text), flags)) == 0 ) {
+		perror(text);
+		exit(1);
+	    }
+	}
+	else {
+	    if ( argc && !freopen(argv[0], "r", stdin) ) {
+		perror(argv[0]);
+		exit(1);
+	    }
+	    if ( (doc = mkd_in(stdin,flags)) == 0 ) {
+		perror(argc ? argv[0] : "stdin");
+		exit(1);
+	    }
+	}
+	if ( urlbase )
+	    mkd_basename(doc, urlbase);
+	if ( urlflags ) {
+	    mkd_e_data(doc, urlflags);
+	    mkd_e_flags(doc, e_flags);
+	}
+
+	if ( debug )
+	    rc = mkd_dump(doc, stdout, 0, argc ? basename(argv[0]) : "stdin");
+	else {
+	    rc = 1;
+	    if ( mkd_compile(doc, flags) ) {
+		rc = 0;
+		if ( toc )
+		    mkd_generatetoc(doc, stdout);
+		mkd_generatehtml(doc, stdout);
+		mkd_cleanup(doc);
+	    }
+	}
+    }
+    adump();
+    exit( (rc == 0) ? 0 : errno );
+}
diff --git a/makepage.1 b/makepage.1
new file mode 100644
index 0000000..0a78018
--- /dev/null
+++ b/makepage.1
@@ -0,0 +1,34 @@
+.\"     %A%
+.\"
+.Dd January 10, 2010
+.Dt MAKEPAGE 1
+.Os MASTODON
+.Sh NAME
+.Nm makepage
+.Nd convert markdown input to a fully-formed xhtml page
+.Sh SYNOPSIS
+.Nm
+.Op Pa file
+.Sh DESCRIPTION
+The
+.Nm
+utility parses a
+.Xr markdown 7 Ns -formatted
+.Pa textfile
+.Pq or stdin if not specified,
+compiles it, then prints a fully-formed xhtml page to stdout.
+.Pp
+.Nm
+is part of discount.
+.Sh RETURN VALUES
+The
+.Nm
+utility exits 0 on success, and >0 if an error occurs.
+.Sh SEE ALSO
+.Xr markdown 1 ,
+.Xr markdown 3 ,
+.Xr markdown 7 ,
+.Xr mkd-extensions 7 .
+.Sh AUTHOR
+.An David Parsons
+.Pq Li orc at pell.chi.il.us
diff --git a/makepage.c b/makepage.c
new file mode 100644
index 0000000..8fa55ac
--- /dev/null
+++ b/makepage.c
@@ -0,0 +1,27 @@
+/*
+ * makepage: Use mkd_xhtmlpage() to convert markdown input to a
+ *           fully-formed xhtml page.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <mkdio.h>
+
+float
+main(argc, argv)
+int argc;
+char **argv;
+{
+    MMIOT *doc;
+    
+    if ( (argc > 1) && !freopen(argv[1], "r", stdin) ) {
+	perror(argv[1]);
+	exit(1);
+    }
+
+    if ( (doc = mkd_in(stdin, 0)) == 0 ) {
+	perror( (argc > 1) ? argv[1] : "stdin" );
+	exit(1);
+    }
+
+    exit(mkd_xhtmlpage(doc, 0, stdout));
+}
diff --git a/markdown.1 b/markdown.1
new file mode 100644
index 0000000..f5e3949
--- /dev/null
+++ b/markdown.1
@@ -0,0 +1,164 @@
+.\"     %A%
+.\"
+.Dd January 7, 2008
+.Dt MARKDOWN 1
+.Os MASTODON
+.Sh NAME
+.Nm markdown
+.Nd text to html conversion tool
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl T
+.Op Fl V
+.Op Fl b Ar url-base
+.Op Fl F Pa bitmap
+.Op Fl f Ar flags
+.Op Fl o Pa file
+.Op Fl s Pa text
+.Op Fl t Pa text
+.Op Pa textfile
+.Sh DESCRIPTION
+The
+.Nm
+utility reads the
+.Xr markdown 7 Ns -formatted
+.Pa textfile
+.Pq or stdin if not specified,
+compiles it, and writes the html output
+to stdout.
+.Pp
+The options are as follows:
+.Bl -tag -width "-o file"
+.It Fl b Ar url-base
+Links in source begining with / will be prefixed with
+.Ar url-base
+in the output.
+.It Fl d
+Instead of writing the html file, dump a parse
+tree to stdout.
+.It Fl f Ar flags
+Set or clear various translation flags.   The flags
+are in a comma-delimited list, with an optional
+.Ar +
+(enable),
+.Ar -
+(disable), or
+.Ar no
+(disable) lprefix on each flag.
+.Bl -tag -width "definitionlist"
+.It Ar links
+Allow links.
+.It Ar image
+Allow images.
+.It Ar smarty
+Enable smartypants.
+.It Ar pants
+Enable smartypants.
+.It Ar html
+Allow raw html.
+.It Ar strict
+Disable superscript, strikethrough & relaxed emphasis.
+.It Ar ext
+Enable pseudo-protocols.
+.It Ar cdata
+Generate code for xml 
+.Em ![CDATA[...]] .
+.It Ar superscript
+Enable superscript processing.
+.It Ar emphasis
+Emphasis happens 
+.Em everywhere .
+.It Ar tables
+Don't process PHP Markdown Extra tables.
+.It Ar del
+Enable
+.Em ~~strikethrough~~ .
+.It Ar strikethrough
+Enable 
+.Em ~~strikethrough~~ .
+.It Ar toc
+Enable table-of-contents processing.
+.It Ar 1.0
+Compatibility with MarkdownTest_1.0
+.It Ar autolink
+Make
+.Pa http://foo.com
+a link even without
+.Em <> .
+.It Ar safelink
+Paranoid check for link protocol.
+.It Ar header
+Process pandoc-style header blocks.
+.It Ar tabstop
+Expand tabs to 4 spaces.
+.It Ar divquote
+Allow
+.Pa >%class%
+blocks.
+.It Ar alphalist
+Allow alphabetic lists.
+.It Ar definitionlist
+Allow definition lists.
+.El
+.Pp
+As an example, the option
+.Fl f Ar nolinks,smarty
+tells
+.Nm
+to not allow \<a tags, and to do smarty
+pants processing.
+.It Fl F Ar bitmap
+Set translation flags.
+.Ar Bitmap
+is a bit map of the various configuration options
+described in
+.Xr markdown 3 
+(the flag values are defined in
+.Pa mkdio.h )
+.It Fl V
+Show the version# and compile-time configuration data.
+.Pp
+If the version includes the string
+.Em DEBUG ,
+.Nm
+was configured with memory allocation debugging.
+.Pp
+If the version includes the string
+.Em TAB ,
+.Nm
+was configured to use the specified tabstop.
+.It Fl VV
+Show the version#, the compile-time configuration, and the
+run-time configuration.
+.It Fl o Pa file
+Write the generated html to 
+.Pa file .
+.It Fl t Ar text
+Use
+.Xr mkd_text 3
+to format 
+.Ar text
+instead of processing stdin with the
+.Xr markdown 3
+function.
+.It Fl T
+If run with the table-of-content flag on, dump the
+table of contents before the formatted text.
+.It Fl s Ar text
+Use the
+.Xr markdown 3
+function to format
+.Ar text .
+.El
+.Sh RETURN VALUES
+The
+.Nm
+utility exits 0 on success, and >0 if an error occurs.
+.Sh SEE ALSO
+.Xr markdown 3 ,
+.Xr markdown 7 ,
+.Xr mkd-extensions 7 .
+.Sh AUTHOR
+.An David Parsons
+.Pq Li orc at pell.chi.il.us
diff --git a/markdown.3 b/markdown.3
new file mode 100644
index 0000000..4a23ac8
--- /dev/null
+++ b/markdown.3
@@ -0,0 +1,136 @@
+.\"
+.Dd December 20, 2007
+.Dt MARKDOWN 3
+.Os Mastodon
+.Sh NAME
+.Nm markdown
+.Nd process Markdown documents
+.Sh LIBRARY
+Markdown 
+.Pq libmarkdown , -lmarkdown
+.Sh SYNOPSIS
+.Fd #include <mkdio.h>
+.Ft MMIOT
+.Fn *mkd_in "FILE *input" "int flags"
+.Ft MMIOT
+.Fn *mkd_string "char *string" "int size" "int flags"
+.Ft int
+.Fn markdown "MMIOT *doc" "FILE *output" "int flags"
+.Sh DESCRIPTION
+These functions
+convert
+.Em Markdown
+documents and strings into HTML.
+.Fn markdown
+processes an entire document, while
+.Fn mkd_text
+processes a single string.
+.Pp
+To process a file, you pass a FILE* to
+.Fn mkd_in ,
+and if it returns a nonzero value you pass that in to 
+.Fn markdown ,
+which then writes the converted document to the specified
+.Em FILE* .
+If your input has already been written into a string (generated
+input or a file opened 
+with 
+.Xr mmap 2 )
+you can feed that string to 
+.Fn mkd_string
+and pass its return value to
+.Fn markdown.
+.Pp
+.Fn Markdown
+accepts the following flag values (or-ed together if needed)
+to restrict how it processes input:
+.Bl -tag -width MKD_NOSTRIKETHROUGH -compact
+.It Ar MKD_NOLINKS
+Don't do link processing, block 
+.Em <a>
+tags.
+.It Ar MKD_NOIMAGE
+Don't do image processing, block
+.Em <img> .
+.It Ar MKD_NOPANTS
+Don't run 
+.Em smartypants() .
+.It Ar MKD_NOHTML
+Don't allow raw html through AT ALL
+.It Ar MKD_STRICT
+Disable 
+superscript and relaxed emphasis.
+.It Ar MKD_TAGTEXT
+Process text inside an html tag; no 
+.Em <em> ,
+no 
+.Em <bold> ,
+no html or
+.Em []
+expansion.
+.It Ar MKD_NO_EXT
+Don't allow pseudo-protocols.
+.It Ar MKD_CDATA
+Generate code for xml 
+.Em ![CDATA[...]] .
+.It Ar MKD_NOSUPERSCRIPT
+Don't generate superscripts.
+Emphasis happens _everywhere_
+.It Ar MKD_NOTABLES
+Disallow tables.
+.It Ar MKD_NOSTRIKETHROUGH
+Forbid 
+.Em ~~strikethrough~~ .
+.It Ar MKD_TOC
+Do table-of-contents processing.
+.It Ar MKD_1_COMPAT
+Compatibility with MarkdownTest_1.0
+.It Ar MKD_AUTOLINK
+Make 
+.Em http://foo.com
+into a link even without
+.Em <> s.
+.It Ar MKD_SAFELINK
+Paranoid check for link protocol.
+.It Ar MKD_NOHEADER
+Don't process header blocks.
+.It Ar MKD_TABSTOP
+Expand tabs to 4 spaces.
+.It Ar MKD_NODIVQUOTE
+Forbid 
+.Em >%class%
+blocks.
+.It Ar MKD_NOALPHALIST
+Forbid alphabetic lists.
+.It Ar MKD_NODLIST
+Forbid definition lists.
+.El
+.Sh RETURN VALUES
+.Fn markdown
+returns 0 on success, 1 on failure.
+The
+.Fn mkd_in
+and
+.Fn mkd_string
+functions return a MMIOT* on success, null on failure.
+.Sh SEE ALSO
+.Xr markdown 1 ,
+.Xr mkd-callbacks 3 ,
+.Xr mkd-functions 3 ,
+.Xr mkd-line 3 ,
+.Xr markdown 7 ,
+.Xr mkd-extensions 7 ,
+.Xr mmap 2 .
+.Pp
+http://daringfireball.net/projects/markdown/syntax
+.Sh BUGS
+Error handling is minimal at best.
+.Pp
+The
+.Ar MMIOT
+created by
+.Fn mkd_string
+is deleted by the
+.Nm
+function.
+
diff --git a/markdown.7 b/markdown.7
new file mode 100644
index 0000000..7c76a2c
--- /dev/null
+++ b/markdown.7
@@ -0,0 +1,1020 @@
+.\"
+.Dd Dec 22, 2007
+.Dt MARKDOWN 7
+.Os MASTODON
+.Sh NAME
+.Nm Markdown
+.Nd The Markdown text formatting syntax
+.Sh DESCRIPTION
+.Ss Philosophy
+.Nm Markdown
+is intended to be as easy-to-read and easy-to-write as is feasible.
+.Pp
+Readability, however, is emphasized above all else. A Markdown-formatted
+document should be publishable as-is, as plain text, without looking
+like it's been marked up with tags or formatting instructions. While
+Markdown's syntax has been influenced by several existing text-to-HTML
+filters -- including
+.Em Setext ,
+.Em atx ,
+.Em Textile ,
+.Em reStructuredText ,
+.Em Grutatext ,
+and
+.Em EtText
+\-\- the single biggest source of
+inspiration for
+Markdown's
+syntax is the format of plain text email.
+.Pp
+To this end, Markdown's syntax is comprised entirely of punctuation
+characters, which punctuation characters have been carefully chosen so
+as to look like what they mean. E.g., asterisks around a word actually
+look like *emphasis*. Markdown lists look like, well, lists. Even
+blockquotes look like quoted passages of text, assuming you've ever
+used email.
+.Ss Inline HTML
+Markdown's syntax is intended for one purpose: to be used as a
+format for
+.Em writing
+for the web.
+.Pp
+.Nm
+is not a replacement for HTML, or even close to it. Its
+syntax is very small, corresponding only to a very small subset of
+HTML tags. The idea is
+.Em not
+to create a syntax that makes it easier
+to insert HTML tags. In my opinion, HTML tags are already easy to
+insert. The idea for Markdown is to make it easy to read, write, and
+edit prose. HTML is a
+.Em publishing
+format; Markdown is a
+.Em writing
+format. Thus, Markdown's formatting syntax only addresses issues that
+can be conveyed in plain text.
+.Pp
+For any markup that is not covered by Markdown's syntax, you simply
+use HTML itself. There's no need to preface it or delimit it to
+indicate that you're switching from Markdown to HTML; you just use
+the tags.
+.Pp
+The only restrictions are that block-level HTML elements -- e.g.
+.Li \<div> ,
+.Li \<table> ,
+.Li \<pre> ,
+.Li \<p> ,
+etc. -- must be separated from surrounding
+content by blank lines, and the start and end tags of the block should
+not be indented with tabs or spaces. Markdown is smart enough not
+to add extra (unwanted)
+.Li \<p>
+tags around HTML block-level tags.
+.Pp
+For example, to add an HTML table to a Markdown article:
+.Bd -literal -offset indent
+    This is a regular paragraph.
+
+    <table>
+        <tr>
+            <td>Foo</td>
+        </tr>
+    </table>
+
+    This is another regular paragraph.
+.Ed
+.Pp
+Note that Markdown formatting syntax is not processed within block-level
+HTML tags. E.g., you can't use Markdown-style 
+.Li *emphasis*
+inside an HTML block.
+.Pp
+Span-level HTML tags -- e.g. 
+.Li \<span> ,
+.Li \<cite> ,
+or
+.Li \<del>
+\-\- can be
+used anywhere in a Markdown paragraph, list item, or header. If you
+want, you can even use HTML tags instead of Markdown formatting; e.g. if
+you'd prefer to use HTML 
+.Li \<a>
+or
+.Li \<img>
+tags instead of Markdown's
+link or image syntax, go right ahead.
+.Pp
+Unlike block-level HTML tags, Markdown syntax *is* processed within
+span-level tags.
+.Ss Automatic Escaping for Special Characters
+In HTML, there are two characters that demand special treatment: `<`
+and `&`. Left angle brackets are used to start tags; ampersands are
+used to denote HTML entities. If you want to use them as literal
+characters, you must escape them as entities, e.g. `<`, and
+`&`.
+.Pp
+Ampersands in particular are bedeviling for web writers. If you want to
+write about 'AT&T', you need to write '`AT&T`'. You even need to
+escape ampersands within URLs. Thus, if you want to link to:
+.Bd -literal -offset indent
+    http://images.google.com/images?num=30&q=larry+bird
+.Ed
+.Pp
+you need to encode the URL as:
+.Bd -literal -offset indent
+    http://images.google.com/images?num=30&q=larry+bird
+.Ed
+.Pp
+in your anchor tag `href` attribute. Needless to say, this is easy to
+forget, and is probably the single most common source of HTML validation
+errors in otherwise well-marked-up web sites.
+.Pp
+.Nm
+allows you to use these characters naturally, taking care of
+all the necessary escaping for you. If you use an ampersand as part of
+an HTML entity, it remains unchanged; otherwise it will be translated
+into `&`.
+.Pp
+So, if you want to include a copyright symbol in your article, you can write:
+.Bd -literal -offset indent
+    ©
+.Ed
+.Pp
+and Markdown will leave it alone. But if you write:
+.Bd -literal -offset indent
+    AT&T
+.Ed
+.Pp
+.Nm
+will translate it to:
+.Bd -literal -offset indent
+    AT&T
+.Ed
+.Pp
+Similarly, because Markdown supports inline HTML, if you use
+angle brackets as delimiters for HTML tags, Markdown will treat them as
+such. But if you write:
+.Bd -literal -offset indent
+    4 < 5
+.Ed
+.Pp
+.Nm
+will translate it to:
+.Bd -literal -offset indent
+    4 < 5
+.Ed
+.Pp
+However, inside Markdown code spans and blocks, angle brackets and
+ampersands are *always* encoded automatically. This makes it easy to use
+Markdown to write about HTML code. (As opposed to raw HTML, which is a
+terrible format for writing about HTML syntax, because every single `<`
+and `&` in your example code needs to be escaped.)
+.Sh Block Elements
+.Ss Paragraphs and Line Breaks
+.Pp
+A paragraph is simply one or more consecutive lines of text, separated
+by one or more blank lines. (A blank line is any line that looks like a
+blank line -- a line containing nothing but spaces or tabs is considered
+blank.) Normal paragraphs should not be indented with spaces or tabs.
+.Pp
+The implication of the
+.Qq one or more consecutive lines of text
+rule is
+that Markdown supports
+.Qq hard-wrapped
+Dtext paragraphs. This differs
+significantly from most other text-to-HTML formatters (including Movable
+Type's
+.Qq Convert Line Breaks
+option) which translate every line break
+character in a paragraph into a `<br />` tag.
+.Pp
+When you *do* want to insert a `<br />` break tag using Markdown, you
+end a line with two or more spaces, then type return.
+.Pp
+Yes, this takes a tad more effort to create a `<br />`, but a simplistic
+"every line break is a `<br />`" rule wouldn't work for Markdown.
+Markdown's email-style
+.Sx  blockquoting
+ and multi-paragraph
+.Sx  list items
+work best -- and look better -- when you format them with hard breaks.
+.Ss Headers
+.Nm
+supports two styles of headers, 
+.Em Setext
+and
+.Em atx .
+.Pp
+Setext-style headers are
+.Sq underlined
+using equal signs (for first-level
+headers) and dashes (for second-level headers). For example:
+.Bd -literal -offset indent
+    This is an H1
+    =============
+
+    This is an H2
+    -------------
+.Ed
+.Pp
+Any number of underlining `=`'s or `-`'s will work.
+.Pp
+Atx-style headers use 1-6 hash characters at the start of the line,
+corresponding to header levels 1-6. For example:
+.Bd -literal -offset indent
+    # This is an H1
+
+    ## This is an H2
+
+    ###### This is an H6
+.Ed
+.Pp
+Optionally, you may 
+.Qq close
+atx-style headers. This is purely
+cosmetic -- you can use this if you think it looks better. The
+closing hashes don't even need to match the number of hashes
+used to open the header. (The number of opening hashes
+determines the header level.) :
+.Bd -literal -offset indent
+    # This is an H1 #
+
+    ## This is an H2 ##
+
+    ### This is an H3 ######
+.Ed
+.Pp
+.Ss Blockquotes
+.Nm
+uses email-style `>` characters for blockquoting. If you're
+familiar with quoting passages of text in an email message, then you
+know how to create a blockquote in Markdown. It looks best if you hard
+wrap the text and put a `>` before every line:
+.Bd -literal -offset indent
+    > This is a blockquote with two paragraphs. Lorem ipsum
+    > dolor sit amet, consectetuer adipiscing elit. Aliquam
+    > hendrerit mi posuere lectus. Vestibulum enim wisi,
+    > viverra nec, fringilla in, laoreet vitae, risus.
+    > 
+    > Donec sit amet nisl. Aliquam semper ipsum sit amet
+    > velit. Suspendisse id sem consectetuer libero luctus
+    > adipiscing.
+.Ed
+.Pp
+.Nm
+allows you to be lazy and only put the `>` before the first
+line of a hard-wrapped paragraph:
+.Bd -literal -offset indent
+    > This is a blockquote with two paragraphs. Lorem ipsum
+    dolor sit amet, consectetuer adipiscing elit. Aliquam
+    hendrerit mi posuere lectus. Vestibulum enim wisi,
+    viverra nec, fringilla in, laoreet vitae, risus.
+
+    > Donec sit amet nisl. Aliquam semper ipsum sit amet
+     velit. Suspendisse id sem consectetuer libero luctus
+      adipiscing.
+.Ed
+.Pp
+Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
+adding additional levels of `>`:
+.Bd -literal -offset indent
+    > This is the first level of quoting.
+    >
+    > > This is nested blockquote.
+    >
+    > Back to the first level.
+.Ed
+.Pp
+Blockquotes can contain other Markdown elements, including headers, lists,
+and code blocks:
+.Bd -literal -offset indent
+	> ## This is a header.
+	> 
+	> 1.   This is the first list item.
+	> 2.   This is the second list item.
+	> 
+	> Here's some example code:
+	> 
+	>     return shell_exec("echo $input | $markdown_script");
+.Ed
+.Pp
+Any decent text editor should make email-style quoting easy. For
+example, with BBEdit, you can make a selection and choose Increase
+Quote Level from the Text menu.
+.Ss Lists
+.Nm
+supports ordered (numbered) and unordered (bulleted) lists.
+.Pp
+Unordered lists use asterisks, pluses, and hyphens -- interchangably
+\-- as list markers:
+.Bd -literal -offset indent
+    *   Red
+    *   Green
+    *   Blue
+.Ed
+.Pp
+is equivalent to:
+.Bd -literal -offset indent
+    +   Red
+    +   Green
+    +   Blue
+.Ed
+.Pp
+and:
+.Bd -literal -offset indent
+    -   Red
+    -   Green
+    -   Blue
+.Ed
+.Pp
+Ordered lists use numbers followed by periods:
+.Bd -literal -offset indent
+    1.  Bird
+    2.  McHale
+    3.  Parish
+.Ed
+.Pp
+It's important to note that the actual numbers you use to mark the
+list have no effect on the HTML output Markdown produces. The HTML
+Markdown produces from the above list is:
+.Bd -literal -offset indent
+    <ol>
+    <li>Bird</li>
+    <li>McHale</li>
+    <li>Parish</li>
+    </ol>
+.Ed
+.Pp
+If you instead wrote the list in Markdown like this:
+.Bd -literal -offset indent
+    1.  Bird
+    1.  McHale
+    1.  Parish
+.Ed
+.Pp
+or even:
+.Bd -literal -offset indent
+    3. Bird
+    1. McHale
+    8. Parish
+.Ed
+.Pp
+you'd get the exact same HTML output. The point is, if you want to,
+you can use ordinal numbers in your ordered Markdown lists, so that
+the numbers in your source match the numbers in your published HTML.
+But if you want to be lazy, you don't have to.
+.Pp
+If you do use lazy list numbering, however, you should still start the
+list with the number 1. At some point in the future, Markdown may support
+starting ordered lists at an arbitrary number.
+.Pp
+List markers typically start at the left margin, but may be indented by
+up to three spaces. List markers must be followed by one or more spaces
+or a tab.
+.Pp
+To make lists look nice, you can wrap items with hanging indents:
+.Bd -literal -offset indent
+    *   Lorem ipsum dolor sit amet, consectetuer adipiscing
+	elit. Aliquam hendrerit mi posuere lectus. Vestibulum
+	enim wisi, viverra nec, fringilla in, laoreet vitae,
+	risus.
+    *   Donec sit amet nisl. Aliquam semper ipsum sit amet
+	velit. Suspendisse id sem consectetuer libero luctus
+	adipiscing.
+.Ed
+.Pp
+But if you want to be lazy, you don't have to:
+.Bd -literal -offset indent
+    *   Lorem ipsum dolor sit amet, consectetuer adipiscing
+    elit. Aliquam hendrerit mi posuere lectus. Vestibulum
+    enim wisi, viverra nec, fringilla in, laoreet vitae,
+    risus.
+    *   Donec sit amet nisl. Aliquam semper ipsum sit amet
+    velit. Suspendisse id sem consectetuer libero luctus
+    adipiscing.
+.Ed
+.Pp
+If list items are separated by blank lines, Markdown will wrap the
+items in `<p>` tags in the HTML output. For example, this input:
+.Bd -literal -offset indent
+    *   Bird
+    *   Magic
+.Ed
+.Pp
+will turn into:
+.Bd -literal -offset indent
+    <ul>
+    <li>Bird</li>
+    <li>Magic</li>
+    </ul>
+.Ed
+.Pp
+But this:
+.Bd -literal -offset indent
+    *   Bird
+
+    *   Magic
+.Ed
+.Pp
+will turn into:
+.Bd -literal -offset indent
+    <ul>
+    <li><p>Bird</p></li>
+    <li><p>Magic</p></li>
+    </ul>
+.Ed
+.Pp
+List items may consist of multiple paragraphs. Each subsequent
+paragraph in a list item must be intended by either 4 spaces
+or one tab:
+.Bd -literal -offset indent
+    1.  This is a list item with two paragraphs. Lorem ipsum
+	dolor sit amet, consectetuer adipiscing elit. Aliquam
+	hendrerit mi posuere lectus.
+
+        Vestibulum enim wisi, viverra nec, fringilla in,
+	laoreet vitae, risus. Donec sit amet nisl. Aliquam
+	semper ipsum sit amet velit.
+
+    2.  Suspendisse id sem consectetuer libero luctus
+	adipiscing.
+.Ed
+.Pp
+It looks nice if you indent every line of the subsequent
+paragraphs, but here again, Markdown will allow you to be
+lazy:
+.Bd -literal -offset indent
+    *   This is a list item with two paragraphs.
+
+        This is the second paragraph in the list item.
+	You're only required to indent the first line. Lorem
+	ipsum dolor sit amet, consectetuer adipiscing elit.
+
+    *   Another item in the same list.
+.Ed
+.Pp
+To put a blockquote within a list item, the blockquote's `>`
+delimiters need to be indented:
+.Bd -literal -offset indent
+    *   A list item with a blockquote:
+
+        > This is a blockquote
+        > inside a list item.
+.Ed
+.Pp
+To put a code block within a list item, the code block needs
+to be indented *twice* -- 8 spaces or two tabs:
+.Bd -literal -offset indent
+    *   A list item with a code block:
+
+            <code goes here>
+.Ed
+.Pp
+It's worth noting that it's possible to trigger an ordered list by
+accident, by writing something like this:
+.Bd -literal -offset indent
+    1986. What a great season.
+.Ed
+.Pp
+In other words, a *number-period-space* sequence at the beginning of a
+line. To avoid this, you can backslash-escape the period:
+.Bd -literal -offset indent
+    1986\\. What a great season.
+.Ed
+.Pp
+.Ss Code Blocks
+Pre-formatted code blocks are used for writing about programming or
+markup source code. Rather than forming normal paragraphs, the lines
+of a code block are interpreted literally. Markdown wraps a code block
+in both `<pre>` and `<code>` tags.
+.Pp
+To produce a code block in Markdown, simply indent every line of the
+block by at least 4 spaces or 1 tab. For example, given this input:
+.Bd -literal -offset indent
+    This is a normal paragraph:
+
+        This is a code block.
+.Ed
+.Pp
+.Nm
+will generate:
+.Bd -literal -offset indent
+    <p>This is a normal paragraph:</p>
+
+    <pre><code>This is a code block.
+    </code></pre>
+.Ed
+.Pp
+One level of indentation -- 4 spaces or 1 tab -- is removed from each
+line of the code block. For example, this:
+.Bd -literal -offset indent
+    Here is an example of AppleScript:
+
+        tell application "Foo"
+            beep
+        end tell
+.Ed
+.Pp
+will turn into:
+.Bd -literal -offset indent
+    <p>Here is an example of AppleScript:</p>
+
+    <pre><code>tell application "Foo"
+        beep
+    end tell
+    </code></pre>
+.Ed
+.Pp
+A code block continues until it reaches a line that is not indented
+(or the end of the article).
+.Pp
+Within a code block, ampersands (`&`) and angle brackets (`<` and `>`)
+are automatically converted into HTML entities. This makes it very
+easy to include example HTML source code using Markdown -- just paste
+it and indent it, and Markdown will handle the hassle of encoding the
+ampersands and angle brackets. For example, this:
+.Bd -literal -offset indent
+        <div class="footer">
+            © 2004 Foo Corporation
+        </div>
+.Ed
+.Pp
+will turn into:
+.Bd -literal -offset indent
+    <pre><code><div class="footer">
+        &copy; 2004 Foo Corporation
+    </div>
+    </code></pre>
+.Ed
+.Pp
+Regular Markdown syntax is not processed within code blocks. E.g.,
+asterisks are just literal asterisks within a code block. This means
+it's also easy to use Markdown to write about Markdown's own syntax.
+.Ss Horizontal Rules
+You can produce a horizontal rule tag (`<hr />`) by placing three or
+more hyphens, asterisks, or underscores on a line by themselves. If you
+wish, you may use spaces between the hyphens or asterisks. Each of the
+following lines will produce a horizontal rule:
+.Bd -literal -offset indent
+    * * *
+
+    ***
+
+    *****
+
+    - - -
+
+    ---------------------------------------
+.Ed
+.Pp
+.Sh Span Elements
+.Ss Links
+.Nm
+supports two style of links:
+.Em inline
+and
+.Em reference .
+.Pp
+In both styles, the link text is delimited by [square brackets].
+.Pp
+To create an inline link, use a set of regular parentheses immediately
+after the link text's closing square bracket. Inside the parentheses,
+put the URL where you want the link to point, along with an *optional*
+title for the link, surrounded in quotes. For example:
+.Bd -literal -offset indent
+    This is [an example](http://example.com/ "Title") inline link.
+
+    [This link](http://example.net/) has no title attribute.
+.Ed
+.Pp
+Will produce:
+.Bd -literal -offset indent
+    <p>This is <a href="http://example.com/" title="Title">
+    an example</a> inline link.</p>
+
+    <p><a href="http://example.net/">This link</a> has no
+    title attribute.</p>
+.Ed
+.Pp
+If you're referring to a local resource on the same server, you can
+use relative paths:
+.Bd -literal -offset indent
+    See my [About](/about/) page for details.   
+.Ed
+.Pp
+Reference-style links use a second set of square brackets, inside
+which you place a label of your choosing to identify the link:
+.Bd -literal -offset indent
+    This is [an example][id] reference-style link.
+.Ed
+.Pp
+You can optionally use a space to separate the sets of brackets:
+.Bd -literal -offset indent
+    This is [an example] [id] reference-style link.
+.Ed
+.Pp
+Then, anywhere in the document, you define your link label like this,
+on a line by itself:
+.Bd -literal -offset indent
+    [id]: http://example.com/  "Optional Title Here"
+.Ed
+.Pp
+That is:
+.Bl -bullet
+.It
+Square brackets containing the link identifier (optionally
+indented from the left margin using up to three spaces);
+.It
+followed by a colon;
+.It
+followed by one or more spaces (or tabs);
+.It
+followed by the URL for the link;
+.It
+optionally followed by a title attribute for the link, enclosed
+in double or single quotes, or enclosed in parentheses.
+.El
+.Pp
+The following three link definitions are equivalent:
+.Bd -literal -offset indent
+	[foo]: http://example.com/  "Optional Title Here"
+	[foo]: http://example.com/  'Optional Title Here'
+	[foo]: http://example.com/  (Optional Title Here)
+.Ed
+.Pp
+.Em Note :
+There is a known bug in Markdown.pl 1.0.1 which prevents
+single quotes from being used to delimit link titles.
+.Pp
+The link URL may, optionally, be surrounded by angle brackets:
+.Bd -literal -offset indent
+    [id]: <http://example.com/>  "Optional Title Here"
+.Ed
+.Pp
+You can put the title attribute on the next line and use extra spaces
+or tabs for padding, which tends to look better with longer URLs:
+.Bd -literal -offset indent
+    [id]: http://example.com/longish/path/to/resource/here
+        "Optional Title Here"
+.Ed
+.Pp
+Link definitions are only used for creating links during Markdown
+processing, and are stripped from your document in the HTML output.
+.Pp
+Link definition names may constist of letters, numbers, spaces, and
+punctuation -- but they are
+.Em not
+case sensitive. E.g. these two
+links:
+.Bd -literal -offset indent
+	[link text][a]
+	[link text][A]
+.Ed
+.Pp
+are equivalent.
+.Pp
+The
+.Em implicit link name
+shortcut allows you to omit the name of the
+link, in which case the link text itself is used as the name.
+Just use an empty set of square brackets -- e.g., to link the word
+.Qq Google
+to the google.com web site, you could simply write:
+.Bd -literal -offset indent
+	[Google][]
+.Ed
+.Pp
+And then define the link:
+.Bd -literal -offset indent
+	[Google]: http://google.com/
+.Ed
+.Pp
+Because link names may contain spaces, this shortcut even works for
+multiple words in the link text:
+.Bd -literal -offset indent
+	Visit [Daring Fireball][] for more information.
+.Ed
+.Pp
+And then define the link:
+.Bd -literal -offset indent
+	[Daring Fireball]: http://daringfireball.net/
+.Ed
+.Pp
+Link definitions can be placed anywhere in your Markdown document. I
+tend to put them immediately after each paragraph in which they're
+used, but if you want, you can put them all at the end of your
+document, sort of like footnotes.
+.Pp
+Here's an example of reference links in action:
+.Bd -literal -offset indent
+    I get 10 times more traffic from [Google] [1] than from
+    [Yahoo] [2] or [MSN] [3].
+
+      [1]: http://google.com/        "Google"
+      [2]: http://search.yahoo.com/  "Yahoo Search"
+      [3]: http://search.msn.com/    "MSN Search"
+.Ed
+.Pp
+Using the implicit link name shortcut, you could instead write:
+.Bd -literal -offset indent
+    I get 10 times more traffic from [Google][] than from
+    [Yahoo][] or [MSN][].
+
+      [google]: http://google.com/        "Google"
+      [yahoo]:  http://search.yahoo.com/  "Yahoo Search"
+      [msn]:    http://search.msn.com/    "MSN Search"
+.Ed
+.Pp
+Both of the above examples will produce the following HTML output:
+.Bd -literal -offset indent
+    <p>I get 10 times more traffic from <a href="http://google.com/"
+    title="Google">Google</a> than from
+    <a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a>
+    or
+    <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>
+.Ed
+.Pp
+For comparison, here is the same paragraph written using
+Markdown's inline link style:
+.Bd -literal -offset indent
+    I get 10 times more traffic from
+    [Google](http://google.com/ "Google") than from
+    [Yahoo](http://search.yahoo.com/ "Yahoo Search") or
+    [MSN](http://search.msn.com/ "MSN Search").
+.Ed
+.Pp
+The point of reference-style links is not that they're easier to
+write. The point is that with reference-style links, your document
+source is vastly more readable. Compare the above examples: using
+reference-style links, the paragraph itself is only 81 characters
+long; with inline-style links, it's 176 characters; and as raw HTML,
+it's 234 characters. In the raw HTML, there's more markup than there
+is text.
+.Pp
+With Markdown's reference-style links, a source document much more
+closely resembles the final output, as rendered in a browser. By
+allowing you to move the markup-related metadata out of the paragraph,
+you can add links without interrupting the narrative flow of your
+prose.
+.Ss Emphasis
+Markdown treats asterisks (`*`) and underscores (`_`) as indicators of
+emphasis. Text wrapped with one `*` or `_` will be wrapped with an
+HTML `<em>` tag; double `*`'s or `_`'s will be wrapped with an HTML
+`<strong>` tag. E.g., this input:
+.Bd -literal -offset indent
+    *single asterisks*
+
+    _single underscores_
+
+    **double asterisks**
+
+    __double underscores__
+.Ed
+.Pp
+will produce:
+.Bd -literal -offset indent
+    <em>single asterisks</em>
+
+    <em>single underscores</em>
+
+    <strong>double asterisks</strong>
+
+    <strong>double underscores</strong>
+.Ed
+.Pp
+You can use whichever style you prefer; the lone restriction is that
+the same character must be used to open and close an emphasis span.
+.Pp
+Emphasis can be used in the middle of a word:
+.Bd -literal -offset indent
+    un*fucking*believable
+.Ed
+.Pp
+But if you surround an `*` or `_` with spaces, it'll be treated as a
+literal asterisk or underscore.
+.Pp
+To produce a literal asterisk or underscore at a position where it
+would otherwise be used as an emphasis delimiter, you can backslash
+escape it:
+.Bd -literal -offset indent
+    \\*this text is surrounded by literal asterisks\\*
+.Ed
+.Pp
+.Ss Code
+To indicate a span of code, wrap it with backtick quotes (`` ` ``).
+Unlike a pre-formatted code block, a code span indicates code within a
+normal paragraph. For example:
+.Bd -literal -offset indent
+    Use the `printf()` function.
+.Ed
+.Pp
+will produce:
+.Bd -literal -offset indent
+    <p>Use the <code>printf()</code> function.</p>
+.Ed
+.Pp
+To include a literal backtick character within a code span, you can use
+multiple backticks as the opening and closing delimiters:
+.Bd -literal -offset indent
+    ``There is a literal backtick (`) here.``
+.Ed
+.Pp
+which will produce this:
+.Bd -literal -offset indent
+    <p><code>There is a literal backtick (`) here.</code></p>
+.Ed
+.Pp
+The backtick delimiters surrounding a code span may include spaces --
+one after the opening, one before the closing. This allows you to place
+literal backtick characters at the beginning or end of a code span:
+.Bd -literal -offset indent
+	A single backtick in a code span: `` ` ``
+	
+	A backtick-delimited string in a code span: `` `foo` ``
+.Ed
+.Pp
+will produce:
+.Bd -literal -offset indent
+	<p>A single backtick in a code span: <code>`</code></p>
+	
+	<p>A backtick-delimited string in a code span: <code>`foo`</code></p>
+.Ed
+.Pp
+With a code span, ampersands and angle brackets are encoded as HTML
+entities automatically, which makes it easy to include example HTML
+tags. Markdown will turn this:
+.Bd -literal -offset indent
+    Please don't use any `<blink>` tags.
+.Ed
+.Pp
+into:
+.Bd -literal -offset indent
+    <p>Please don't use any <code><blink></code> tags.</p>
+.Ed
+.Pp
+You can write this:
+.Bd -literal -offset indent
+    `—` is the decimal-encoded equivalent of `—`.
+.Ed
+.Pp
+to produce:
+.Bd -literal -offset indent
+    <p><code>&#8212;</code> is the decimal-encoded
+    equivalent of <code>&mdash;</code>.</p>
+.Ed
+.Pp
+.Ss Images
+Admittedly, it's fairly difficult to devise a
+.Qq natural
+syntax for placing images into a plain text document format.
+.Pp
+Markdown uses an image syntax that is intended to resemble the syntax
+for links, allowing for two styles:
+.Em inline
+and
+.Em reference .
+.Pp
+Inline image syntax looks like this:
+.Bd -literal -offset indent
+    ![Alt text](/path/to/img.jpg)
+
+    ![Alt text](/path/to/img.jpg =Optional size "Optional title")
+.Ed
+.Pp
+That is:
+.Bl -bullet
+.It
+An exclamation mark: `!`;
+.It
+followed by a set of square brackets, containing the `alt`
+attribute text for the image;
+.It
+followed by a set of parentheses, containing the URL or path to
+the image, an optional `size` attribute (in
+.Ar width Li c Ar height
+format) prefixed with a `=`,
+and an optional `title` attribute enclosed in double
+or single quotes.
+.El
+.Pp
+Reference-style image syntax looks like this:
+.Bd -literal -offset indent
+    ![Alt text][id]
+.Ed
+.Pp
+Where
+.Qq id
+is the name of a defined image reference. Image references
+are defined using syntax identical to link references:
+.Bd -literal -offset indent
+    [id]: url/to/image  =Optional size "Optional title attribute"
+.Ed
+.Pp
+.Sh Miscellaneous
+.Ss Automatic Links
+.Nm
+supports a shortcut style for creating
+.Qq automatic
+links for URLs and email addresses: simply surround the URL or email
+address with angle brackets. What this means is that if you want to
+ show the actual text of a URL or email address, and also have it be
+  a clickable link, you can do this:
+.Bd -literal -offset indent
+    <http://example.com/>
+.Ed
+.Pp
+.Nm
+will turn this into:
+.Bd -literal -offset indent
+    <a href="http://example.com/">http://example.com/</a>
+.Ed
+.Pp
+Automatic links for email addresses work similarly, except that
+Markdown will also perform a bit of randomized decimal and hex
+entity-encoding to help obscure your address from address-harvesting
+spambots. For example, Markdown will turn this:
+.Bd -literal -offset indent
+    <address at example.com>
+.Ed
+.Pp
+into something like this:
+.Bd -literal -offset indent
+    <a href="&#x6D;&#x61;i&#x6C;&#x74;&#x6F;:&#x61;&#x64;&#x64;&#x72;&#x65;
+    ss@ex&#x61;m&#x70;&#x6C;e&#x2E;co
+    m">&#x61;&#x64;&#x64;&#x72;&#x65;ss@ex&#x61;
+    m&#x70;&#x6C;e&#x2E;com</a>
+.Ed
+.Pp
+which will render in a browser as a clickable link to
+.Qq address at example.com .
+.Pp
+(This sort of entity-encoding trick will indeed fool many, if not
+most, address-harvesting bots, but it definitely won't fool all of
+them. It's better than nothing, but an address published in this way
+will probably eventually start receiving spam.)
+.Ss Backslash Escapes
+.Nm
+allows you to use backslash escapes to generate literal
+characters which would otherwise have special meaning in Markdown's
+formatting syntax. For example, if you wanted to surround a word with
+literal asterisks (instead of an HTML `<em>` tag), you add backslashes
+before the asterisks, like this:
+.Bd -literal -offset indent
+    \\*literal asterisks\\*
+.Ed
+.Pp
+.Nm
+provides backslash escapes for the following characters:
+.Bl -tag -compact
+.It \&\
+backslash
+.It \`
+backtick
+.It *
+asterisk
+.It _
+underscore
+.It \{\}
+curly braces
+.It []
+square brackets
+.It ()
+parentheses
+.It #
+hash mark
+.It +
+plus sign
+.It \-
+minus sign (hyphen)
+.It \.
+dot
+.It \!
+exclamation mark
+.El
+.Sh BUGS
+.Nm
+assumes that tabs are set to 4 spaces.
+.Sh AUTHOR
+John Gruber
+.%T http://daringfireball.net/
+.Sh SEE ALSO
+.Xr markdown 1 ,
+.Xr markdown 3 ,
+.Xr mkd-callbacks 3 ,
+.Xr mkd-functions 3 ,
+.Xr mkd-extensions 7 .
+.Pp
+.%T http://daringfireball.net/projects/markdown
+.br
+.%T http://docutils.sourceforge.net/mirror/setext.html
+.br
+.%T http://www.aaronsw.com/2002/atx/
+.br
+.%T http://textism.com/tools/textile/
+.br
+.%T http://docutils.sourceforge.net/rst.html
+.br
+.%T http://www.triptico.com/software/grutatxt.html
+.br
+.%T http://ettext.taint.org/doc/
diff --git a/markdown.c b/markdown.c
new file mode 100644
index 0000000..b239265
--- /dev/null
+++ b/markdown.c
@@ -0,0 +1,1215 @@
+/* markdown: a C implementation of John Gruber's Markdown markup language.
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+#include "tags.h"
+
+typedef int (*stfu)(const void*,const void*);
+
+typedef ANCHOR(Paragraph) ParagraphRoot;
+
+/* case insensitive string sort for Footnote tags.
+ */
+int
+__mkd_footsort(Footnote *a, Footnote *b)
+{
+    int i;
+    char ac, bc;
+
+    if ( S(a->tag) != S(b->tag) )
+	return S(a->tag) - S(b->tag);
+
+    for ( i=0; i < S(a->tag); i++) {
+	ac = tolower(T(a->tag)[i]);
+	bc = tolower(T(b->tag)[i]);
+
+	if ( isspace(ac) && isspace(bc) )
+	    continue;
+	if ( ac != bc )
+	    return ac - bc;
+    }
+    return 0;
+}
+
+
+/* find the first blank character after position <i>
+ */
+static int
+nextblank(Line *t, int i)
+{
+    while ( (i < S(t->text)) && !isspace(T(t->text)[i]) )
+	++i;
+    return i;
+}
+
+
+/* find the next nonblank character after position <i>
+ */
+static int
+nextnonblank(Line *t, int i)
+{
+    while ( (i < S(t->text)) && isspace(T(t->text)[i]) )
+	++i;
+    return i;
+}
+
+
+/* find the first nonblank character on the Line.
+ */
+int
+mkd_firstnonblank(Line *p)
+{
+    return nextnonblank(p,0);
+}
+
+
+static int
+blankline(Line *p)
+{
+    return ! (p && (S(p->text) > p->dle) );
+}
+
+
+static Line *
+skipempty(Line *p)
+{
+    while ( p && (p->dle == S(p->text)) )
+	p = p->next;
+    return p;
+}
+
+
+void
+___mkd_tidy(Cstring *t)
+{
+    while ( S(*t) && isspace(T(*t)[S(*t)-1]) )
+	--S(*t);
+}
+
+
+static struct kw comment = { "!--", 3, 0 };
+
+static struct kw *
+isopentag(Line *p)
+{
+    int i=0, len;
+    char *line;
+
+    if ( !p ) return 0;
+
+    line = T(p->text);
+    len = S(p->text);
+
+    if ( len < 3 || line[0] != '<' )
+	return 0;
+
+    if ( line[1] == '!' && line[2] == '-' && line[3] == '-' )
+	/* comments need special case handling, because
+	 * the !-- doesn't need to end in a whitespace
+	 */
+	return &comment;
+    
+    /* find how long the tag is so we can check to see if
+     * it's a block-level tag
+     */
+    for ( i=1; i < len && T(p->text)[i] != '>' 
+		       && T(p->text)[i] != '/'
+		       && !isspace(T(p->text)[i]); ++i )
+	;
+
+
+    return mkd_search_tags(T(p->text)+1, i-1);
+}
+
+
+typedef struct _flo {
+    Line *t;
+    int i;
+} FLO;
+
+#define floindex(x) (x.i)
+
+
+static int
+flogetc(FLO *f)
+{
+    if ( f && f->t ) {
+	if ( f->i < S(f->t->text) )
+	    return T(f->t->text)[f->i++];
+	f->t = f->t->next;
+	f->i = 0;
+	return flogetc(f);
+    }
+    return EOF;
+}
+
+
+static void
+splitline(Line *t, int cutpoint)
+{
+    if ( t && (cutpoint < S(t->text)) ) {
+	Line *tmp = calloc(1, sizeof *tmp);
+
+	tmp->next = t->next;
+	t->next = tmp;
+
+	tmp->dle = t->dle;
+	SUFFIX(tmp->text, T(t->text)+cutpoint, S(t->text)-cutpoint);
+	S(t->text) = cutpoint;
+    }
+}
+
+
+static Line *
+commentblock(Paragraph *p, int *unclosed)
+{
+    Line *t, *ret;
+    char *end;
+
+    for ( t = p->text; t ; t = t->next) {
+	if ( end = strstr(T(t->text), "-->") ) {
+	    splitline(t, 3 + (end - T(t->text)) );
+	    ret = t->next;
+	    t->next = 0;
+	    return ret;
+	}
+    }
+    *unclosed = 1;
+    return t;
+
+}
+
+
+static Line *
+htmlblock(Paragraph *p, struct kw *tag, int *unclosed)
+{
+    Line *ret;
+    FLO f = { p->text, 0 };
+    int c;
+    int i, closing, depth=0;
+
+    *unclosed = 0;
+    
+    if ( tag == &comment )
+	return commentblock(p, unclosed);
+    
+    if ( tag->selfclose ) {
+	ret = f.t->next;
+	f.t->next = 0;
+	return ret;
+    }
+
+    while ( (c = flogetc(&f)) != EOF ) {
+	if ( c == '<' ) {
+	    /* tag? */
+	    c = flogetc(&f);
+	    if ( c == '!' ) { /* comment? */
+		if ( flogetc(&f) == '-' && flogetc(&f) == '-' ) {
+		    /* yes */
+		    while ( (c = flogetc(&f)) != EOF ) {
+			if ( c == '-' && flogetc(&f) == '-'
+				      && flogetc(&f) == '>')
+			      /* consumed whole comment */
+			      break;
+		    }
+		}
+	    }
+	    else { 
+		if ( closing = (c == '/') ) c = flogetc(&f);
+
+		for ( i=0; i < tag->size; c=flogetc(&f) ) {
+		    if ( tag->id[i++] != toupper(c) )
+			break;
+		}
+
+		if ( (i == tag->size) && !isalnum(c) ) {
+		    depth = depth + (closing ? -1 : 1);
+		    if ( depth == 0 ) {
+			while ( c != EOF && c != '>' ) {
+			    /* consume trailing gunk in close tag */
+			    c = flogetc(&f);
+			}
+			if ( c == EOF )
+			    break;
+			if ( !f.t )
+			    return 0;
+			splitline(f.t, floindex(f));
+			ret = f.t->next;
+			f.t->next = 0;
+			return ret;
+		    }
+		}
+	    }
+	}
+    }
+    *unclosed = 1;
+    return 0;
+}
+
+
+/* tables look like
+ *   header|header{|header}
+ *   ------|------{|......}
+ *   {body lines}
+ */
+static int
+istable(Line *t)
+{
+    char *p;
+    Line *dashes = t->next;
+    int contains = 0;	/* found character bits; 0x01 is |, 0x02 is - */
+    
+    /* two lines, first must contain | */
+    if ( !(dashes && memchr(T(t->text), '|', S(t->text))) )
+	return 0;
+
+    /* second line must contain - or | and nothing
+     * else except for whitespace or :
+     */
+    for ( p = T(dashes->text)+S(dashes->text)-1; p >= T(dashes->text); --p)
+	if ( *p == '|' )
+	    contains |= 0x01;
+	else if ( *p == '-' )
+	    contains |= 0x02;
+	else if ( ! ((*p == ':') || isspace(*p)) )
+	    return 0;
+
+    return (contains & 0x03);
+}
+
+
+/* footnotes look like ^<whitespace>{0,3}[stuff]: <content>$
+ */
+static int
+isfootnote(Line *t)
+{
+    int i;
+
+    if ( ( (i = t->dle) > 3) || (T(t->text)[i] != '[') )
+	return 0;
+
+    for ( ++i; i < S(t->text) ; ++i ) {
+	if ( T(t->text)[i] == '[' )
+	    return 0;
+	else if ( T(t->text)[i] == ']' )
+	    return ( T(t->text)[i+1] == ':' ) ;
+    }
+    return 0;
+}
+
+
+static int
+isquote(Line *t)
+{
+    int j;
+
+    for ( j=0; j < 4; j++ )
+	if ( T(t->text)[j] == '>' )
+	    return 1;
+	else if ( !isspace(T(t->text)[j]) )
+	    return 0;
+    return 0;
+}
+
+
+static int
+dashchar(char c)
+{
+    return (c == '*') || (c == '-') || (c == '_');
+}
+
+
+static int
+iscode(Line *t)
+{
+    return (t->dle >= 4);
+}
+
+
+static int
+ishr(Line *t)
+{
+    int i, count=0;
+    char dash = 0;
+    char c;
+
+    if ( iscode(t) ) return 0;
+
+    for ( i = 0; i < S(t->text); i++) {
+	c = T(t->text)[i];
+	if ( (dash == 0) && dashchar(c) )
+	    dash = c;
+
+	if ( c == dash ) ++count;
+	else if ( !isspace(c) )
+	    return 0;
+    }
+    return (count >= 3);
+}
+
+
+static int
+issetext(Line *t, int *htyp)
+{
+    int i;
+    /* then check for setext-style HEADER
+     *                             ======
+     */
+
+    if ( t->next ) {
+	char *q = T(t->next->text);
+	int last = S(t->next->text);
+
+	if ( (*q == '=') || (*q == '-') ) {
+	    /* ignore trailing whitespace */
+	    while ( (last > 1) && isspace(q[last-1]) )
+		--last;
+
+	    for (i=1; i < last; i++)
+		if ( q[0] != q[i] )
+		    return 0;
+	    *htyp = SETEXT;
+	    return 1;
+	}
+    }
+    return 0;
+}
+
+
+static int
+ishdr(Line *t, int *htyp)
+{
+    int i;
+
+
+    /* first check for etx-style ###HEADER###
+     */
+
+    /* leading run of `#`'s ?
+     */
+    for ( i=0; T(t->text)[i] == '#'; ++i)
+	;
+
+    /* ANY leading `#`'s make this into an ETX header
+     */
+    if ( i && (i < S(t->text) || i > 1) ) {
+	*htyp = ETX;
+	return 1;
+    }
+
+    return issetext(t, htyp);
+}
+
+
+static Line*
+is_discount_dt(Line *t, int *clip)
+{
+#if USE_DISCOUNT_DL
+    if ( t && t->next
+	   && (S(t->text) > 2)
+	   && (t->dle == 0)
+	   && (T(t->text)[0] == '=')
+	   && (T(t->text)[S(t->text)-1] == '=') ) {
+	if ( t->next->dle >= 4 ) {
+	    *clip = 4;
+	    return t;
+	}
+	else
+	    return is_discount_dt(t->next, clip);
+    }
+#endif
+    return 0;
+}
+
+
+static int
+is_extra_dd(Line *t)
+{
+    return (t->dle < 4) && (T(t->text)[t->dle] == ':')
+			&& isspace(T(t->text)[t->dle+1]);
+}
+
+
+static Line*
+is_extra_dt(Line *t, int *clip)
+{
+#if USE_EXTRA_DL
+    int i;
+    
+    if ( t && t->next && T(t->text)[0] != '='
+		      && T(t->text)[S(t->text)-1] != '=') {
+	Line *x;
+    
+	if ( iscode(t) || blankline(t) || ishdr(t,&i) || ishr(t) )
+	    return 0;
+
+	if ( (x = skipempty(t->next)) && is_extra_dd(x) ) {
+	    *clip = x->dle+2;
+	    return t;
+	}
+	
+	if ( x=is_extra_dt(t->next, clip) )
+	    return x;
+    }
+#endif
+    return 0;
+}
+
+
+static Line*
+isdefinition(Line *t, int *clip, int *kind)
+{
+    Line *ret;
+
+    *kind = 1;
+    if ( ret = is_discount_dt(t,clip) )
+	return ret;
+
+    *kind=2;
+    return is_extra_dt(t,clip);
+}
+
+
+static int
+islist(Line *t, int *clip, DWORD flags, int *list_type)
+{
+    int i, j;
+    char *q;
+    
+    if ( /*iscode(t) ||*/ blankline(t) || ishdr(t,&i) || ishr(t) )
+	return 0;
+
+    if ( !(flags & (MKD_NODLIST|MKD_STRICT)) && isdefinition(t,clip,list_type) )
+	return DL;
+
+    if ( strchr("*-+", T(t->text)[t->dle]) && isspace(T(t->text)[t->dle+1]) ) {
+	i = nextnonblank(t, t->dle+1);
+	*clip = (i > 4) ? 4 : i;
+	*list_type = UL;
+	return AL;
+    }
+
+    if ( (j = nextblank(t,t->dle)) > t->dle ) {
+	if ( T(t->text)[j-1] == '.' ) {
+
+	    if ( !(flags & (MKD_NOALPHALIST|MKD_STRICT))
+				    && (j == t->dle + 2)
+			  && isalpha(T(t->text)[t->dle]) ) {
+		j = nextnonblank(t,j);
+		*clip = (j > 4) ? 4 : j;
+		*list_type = AL;
+		return AL;
+	    }
+
+	    strtoul(T(t->text)+t->dle, &q, 10);
+	    if ( (q > T(t->text)+t->dle) && (q == T(t->text) + (j-1)) ) {
+		j = nextnonblank(t,j);
+		*clip = (j > 4) ? 4 : j;
+		*list_type = OL;
+		return AL;
+	    }
+	}
+    }
+    return 0;
+}
+
+
+static Line *
+headerblock(Paragraph *pp, int htyp)
+{
+    Line *ret = 0;
+    Line *p = pp->text;
+    int i, j;
+
+    switch (htyp) {
+    case SETEXT:
+	    /* p->text is header, p->next->text is -'s or ='s
+	     */
+	    pp->hnumber = (T(p->next->text)[0] == '=') ? 1 : 2;
+	    
+	    ret = p->next->next;
+	    ___mkd_freeLine(p->next);
+	    p->next = 0;
+	    break;
+
+    case ETX:
+	    /* p->text is ###header###, so we need to trim off
+	     * the leading and trailing `#`'s
+	     */
+
+	    for (i=0; (T(p->text)[i] == T(p->text)[0]) && (i < S(p->text)-1)
+						       && (i < 6); i++)
+		;
+
+	    pp->hnumber = i;
+
+	    while ( (i < S(p->text)) && isspace(T(p->text)[i]) )
+		++i;
+
+	    CLIP(p->text, 0, i);
+
+	    for (j=S(p->text); (j > 1) && (T(p->text)[j-1] == '#'); --j)
+		;
+
+	    while ( j && isspace(T(p->text)[j-1]) )
+		--j;
+
+	    S(p->text) = j;
+
+	    ret = p->next;
+	    p->next = 0;
+	    break;
+    }
+    return ret;
+}
+
+
+static Line *
+codeblock(Paragraph *p)
+{
+    Line *t = p->text, *r;
+
+    for ( ; t; t = r ) {
+	CLIP(t->text,0,4);
+	t->dle = mkd_firstnonblank(t);
+
+	if ( !( (r = skipempty(t->next)) && iscode(r)) ) {
+	    ___mkd_freeLineRange(t,r);
+	    t->next = 0;
+	    return r;
+	}
+    }
+    return t;
+}
+
+
+static int
+centered(Line *first, Line *last)
+{
+
+    if ( first&&last ) {
+	int len = S(last->text);
+
+	if ( (len > 2) && (strncmp(T(first->text), "->", 2) == 0)
+		       && (strncmp(T(last->text)+len-2, "<-", 2) == 0) ) {
+	    CLIP(first->text, 0, 2);
+	    S(last->text) -= 2;
+	    return CENTER;
+	}
+    }
+    return 0;
+}
+
+
+static int
+endoftextblock(Line *t, int toplevelblock, DWORD flags)
+{
+    int z;
+
+    if ( blankline(t)||isquote(t)||ishdr(t,&z)||ishr(t) )
+	return 1;
+
+    /* HORRIBLE STANDARDS KLUDGE: non-toplevel paragraphs absorb adjacent
+     * code blocks
+     */
+    if ( toplevelblock && iscode(t) )
+	return 1;
+
+    /* HORRIBLE STANDARDS KLUDGE: Toplevel paragraphs eat absorb adjacent
+     * list items, but sublevel blocks behave properly.
+     */
+    return toplevelblock ? 0 : islist(t,&z,flags, &z);
+}
+
+
+static Line *
+textblock(Paragraph *p, int toplevel, DWORD flags)
+{
+    Line *t, *next;
+
+    for ( t = p->text; t ; t = next ) {
+	if ( ((next = t->next) == 0) || endoftextblock(next, toplevel, flags) ) {
+	    p->align = centered(p->text, t);
+	    t->next = 0;
+	    return next;
+	}
+    }
+    return t;
+}
+
+
+/* length of the id: or class: kind in a special div-not-quote block
+ */
+static int
+szmarkerclass(char *p)
+{
+    if ( strncasecmp(p, "id:", 3) == 0 )
+	return 3;
+    if ( strncasecmp(p, "class:", 6) == 0 )
+	return 6;
+    return 0;
+}
+
+
+/*
+ * check if the first line of a quoted block is the special div-not-quote
+ * marker %[kind:]name%
+ */
+static int
+isdivmarker(Line *p, int start, DWORD flags)
+{
+    char *s;
+    int len, i;
+
+    if ( flags & (MKD_NODIVQUOTE|MKD_STRICT) )
+	return 0;
+
+    len = S(p->text);
+    s   = T(p->text);
+
+    if ( !(len && s[start] == '%' && s[len-1] == '%') ) return 0;
+
+    i = szmarkerclass(s+start+1)+start;
+    len -= start+1;
+
+    while ( ++i < len )
+	if ( !isalnum(s[i]) )
+	    return 0;
+
+    return 1;
+}
+
+
+/*
+ * accumulate a blockquote.
+ *
+ * one sick horrible thing about blockquotes is that even though
+ * it just takes ^> to start a quote, following lines, if quoted,
+ * assume that the prefix is ``>''.   This means that code needs
+ * to be indented *5* spaces from the leading '>', but *4* spaces
+ * from the start of the line.   This does not appear to be 
+ * documented in the reference implementation, but it's the
+ * way the markdown sample web form at Daring Fireball works.
+ */
+static Line *
+quoteblock(Paragraph *p, DWORD flags)
+{
+    Line *t, *q;
+    int qp;
+
+    for ( t = p->text; t ; t = q ) {
+	if ( isquote(t) ) {
+	    /* clip leading spaces */
+	    for (qp = 0; T(t->text)[qp] != '>'; qp ++)
+		/* assert: the first nonblank character on this line
+		 * will be a >
+		 */;
+	    /* clip '>' */
+	    qp++;
+	    /* clip next space, if any */
+	    if ( T(t->text)[qp] == ' ' )
+		qp++;
+	    CLIP(t->text, 0, qp);
+	    t->dle = mkd_firstnonblank(t);
+	}
+
+	q = skipempty(t->next);
+
+	if ( (q == 0) || ((q != t->next) && (!isquote(q) || isdivmarker(q,1,flags))) ) {
+	    ___mkd_freeLineRange(t, q);
+	    t = q;
+	    break;
+	}
+    }
+    if ( isdivmarker(p->text,0,flags) ) {
+	char *prefix = "class";
+	int i;
+	
+	q = p->text;
+	p->text = p->text->next;
+
+	if ( (i = szmarkerclass(1+T(q->text))) == 3 )
+	    /* and this would be an "%id:" prefix */
+	    prefix="id";
+	    
+	if ( p->ident = malloc(4+strlen(prefix)+S(q->text)) )
+	    sprintf(p->ident, "%s=\"%.*s\"", prefix, S(q->text)-(i+2),
+						     T(q->text)+(i+1) );
+
+	___mkd_freeLine(q);
+    }
+    return t;
+}
+
+
+/*
+ * A table block starts with a table header (see istable()), and continues
+ * until EOF or a line that /doesn't/ contain a |.
+ */
+static Line *
+tableblock(Paragraph *p)
+{
+    Line *t, *q;
+
+    for ( t = p->text; t && (q = t->next); t = t->next ) {
+	if ( !memchr(T(q->text), '|', S(q->text)) ) {
+	    t->next = 0;
+	    return q;
+	}
+    }
+    return 0;
+}
+
+
+static Paragraph *Pp(ParagraphRoot *, Line *, int);
+static Paragraph *compile(Line *, int, MMIOT *);
+
+typedef int (*linefn)(Line *);
+
+
+/*
+ * pull in a list block.  A list block starts with a list marker and
+ * runs until the next list marker, the next non-indented paragraph,
+ * or EOF.   You do not have to indent nonblank lines after the list
+ * marker, but multiple paragraphs need to start with a 4-space indent.
+ */
+static Line *
+listitem(Paragraph *p, int indent, DWORD flags, linefn check)
+{
+    Line *t, *q;
+    int clip = indent;
+    int z;
+
+    for ( t = p->text; t ; t = q) {
+	CLIP(t->text, 0, clip);
+	t->dle = mkd_firstnonblank(t);
+
+	if ( (q = skipempty(t->next)) == 0 ) {
+	    ___mkd_freeLineRange(t,q);
+	    return 0;
+	}
+
+	/* after a blank line, the next block needs to start with a line
+	 * that's indented 4(? -- reference implementation allows a 1
+	 * character indent, but that has unfortunate side effects here)
+	 * spaces, but after that the line doesn't need any indentation
+	 */
+	if ( q != t->next ) {
+	    if (q->dle < indent) {
+		q = t->next;
+		t->next = 0;
+		return q;
+	    }
+	    /* indent at least 2, and at most as
+	     * as far as the initial line was indented. */
+	    indent = clip ? clip : 2;
+	}
+
+	if ( (q->dle < indent) && (ishr(q) || islist(q,&z,flags,&z)
+					   || (check && (*check)(q)))
+			       && !issetext(q,&z) ) {
+	    q = t->next;
+	    t->next = 0;
+	    return q;
+	}
+
+	clip = (q->dle > indent) ? indent : q->dle;
+    }
+    return t;
+}
+
+
+static Line *
+definition_block(Paragraph *top, int clip, MMIOT *f, int kind)
+{
+    ParagraphRoot d = { 0, 0 };
+    Paragraph *p;
+    Line *q = top->text, *text = 0, *labels; 
+    int z, para;
+
+    while (( labels = q )) {
+
+	if ( (q = isdefinition(labels, &z, &kind)) == 0 )
+	    break;
+
+	if ( (text = skipempty(q->next)) == 0 )
+	    break;
+
+	if (( para = (text != q->next) ))
+	    ___mkd_freeLineRange(q, text);
+	
+	q->next = 0; 
+	if ( kind == 1 /* discount dl */ )
+	    for ( q = labels; q; q = q->next ) {
+		CLIP(q->text, 0, 1);
+		S(q->text)--;
+	    }
+
+    dd_block:
+	p = Pp(&d, text, LISTITEM);
+
+	text = listitem(p, clip, f->flags, (kind==2) ? is_extra_dd : 0);
+	p->down = compile(p->text, 0, f);
+	p->text = labels; labels = 0;
+
+	if ( para && p->down ) p->down->align = PARA;
+
+	if ( (q = skipempty(text)) == 0 )
+	    break;
+
+	if (( para = (q != text) )) {
+	    Line anchor;
+
+	    anchor.next = text;
+	    ___mkd_freeLineRange(&anchor,q);
+	    text = q;
+	    
+	}
+
+	if ( kind == 2 && is_extra_dd(q) )
+	    goto dd_block;
+    }
+    top->text = 0;
+    top->down = T(d);
+    return text;
+}
+
+
+static Line *
+enumerated_block(Paragraph *top, int clip, MMIOT *f, int list_class)
+{
+    ParagraphRoot d = { 0, 0 };
+    Paragraph *p;
+    Line *q = top->text, *text;
+    int para = 0, z;
+
+    while (( text = q )) {
+	
+	p = Pp(&d, text, LISTITEM);
+	text = listitem(p, clip, f->flags, 0);
+
+	p->down = compile(p->text, 0, f);
+	p->text = 0;
+
+	if ( para && p->down ) p->down->align = PARA;
+
+	if ( (q = skipempty(text)) == 0
+			     || islist(q, &clip, f->flags, &z) != list_class )
+	    break;
+
+	if ( para = (q != text) ) {
+	    Line anchor;
+
+	    anchor.next = text;
+	    ___mkd_freeLineRange(&anchor, q);
+
+	    if ( p->down ) p->down->align = PARA;
+	}
+    }
+    top->text = 0;
+    top->down = T(d);
+    return text;
+}
+
+
+static int
+tgood(char c)
+{
+    switch (c) {
+    case '\'':
+    case '"': return c;
+    case '(': return ')';
+    }
+    return 0;
+}
+
+
+/*
+ * add a new (image or link) footnote to the footnote table
+ */
+static Line*
+addfootnote(Line *p, MMIOT* f)
+{
+    int j, i;
+    int c;
+    Line *np = p->next;
+
+    Footnote *foot = &EXPAND(*f->footnotes);
+    
+    CREATE(foot->tag);
+    CREATE(foot->link);
+    CREATE(foot->title);
+    foot->height = foot->width = 0;
+
+    for (j=i=p->dle+1; T(p->text)[j] != ']'; j++)
+	EXPAND(foot->tag) = T(p->text)[j];
+
+    EXPAND(foot->tag) = 0;
+    S(foot->tag)--;
+    j = nextnonblank(p, j+2);
+
+    while ( (j < S(p->text)) && !isspace(T(p->text)[j]) )
+	EXPAND(foot->link) = T(p->text)[j++];
+    EXPAND(foot->link) = 0;
+    S(foot->link)--;
+    j = nextnonblank(p,j);
+
+    if ( T(p->text)[j] == '=' ) {
+	sscanf(T(p->text)+j, "=%dx%d", &foot->width, &foot->height);
+	while ( (j < S(p->text)) && !isspace(T(p->text)[j]) )
+	    ++j;
+	j = nextnonblank(p,j);
+    }
+
+
+    if ( (j >= S(p->text)) && np && np->dle && tgood(T(np->text)[np->dle]) ) {
+	___mkd_freeLine(p);
+	p = np;
+	np = p->next;
+	j = p->dle;
+    }
+
+    if ( (c = tgood(T(p->text)[j])) ) {
+	/* Try to take the rest of the line as a comment; read to
+	 * EOL, then shrink the string back to before the final
+	 * quote.
+	 */
+	++j;	/* skip leading quote */
+
+	while ( j < S(p->text) )
+	    EXPAND(foot->title) = T(p->text)[j++];
+
+	while ( S(foot->title) && T(foot->title)[S(foot->title)-1] != c )
+	    --S(foot->title);
+	if ( S(foot->title) )	/* skip trailing quote */
+	    --S(foot->title);
+	EXPAND(foot->title) = 0;
+	--S(foot->title);
+    }
+
+    ___mkd_freeLine(p);
+    return np;
+}
+
+
+/*
+ * allocate a paragraph header, link it to the
+ * tail of the current document
+ */
+static Paragraph *
+Pp(ParagraphRoot *d, Line *ptr, int typ)
+{
+    Paragraph *ret = calloc(sizeof *ret, 1);
+
+    ret->text = ptr;
+    ret->typ = typ;
+
+    return ATTACH(*d, ret);
+}
+
+
+
+static Line*
+consume(Line *ptr, int *eaten)
+{
+    Line *next;
+    int blanks=0;
+
+    for (; ptr && blankline(ptr); ptr = next, blanks++ ) {
+	next = ptr->next;
+	___mkd_freeLine(ptr);
+    }
+    if ( ptr ) *eaten = blanks;
+    return ptr;
+}
+
+
+/*
+ * top-level compilation; break the document into
+ * style, html, and source blocks with footnote links
+ * weeded out.
+ */
+static Paragraph *
+compile_document(Line *ptr, MMIOT *f)
+{
+    ParagraphRoot d = { 0, 0 };
+    ANCHOR(Line) source = { 0, 0 };
+    Paragraph *p = 0;
+    struct kw *tag;
+    int eaten, unclosed;
+
+    while ( ptr ) {
+	if ( !(f->flags & MKD_NOHTML) && (tag = isopentag(ptr)) ) {
+	    /* If we encounter a html/style block, compile and save all
+	     * of the cached source BEFORE processing the html/style.
+	     */
+	    if ( T(source) ) {
+		E(source)->next = 0;
+		p = Pp(&d, 0, SOURCE);
+		p->down = compile(T(source), 1, f);
+		T(source) = E(source) = 0;
+	    }
+	    p = Pp(&d, ptr, strcmp(tag->id, "STYLE") == 0 ? STYLE : HTML);
+	    ptr = htmlblock(p, tag, &unclosed);
+	    if ( unclosed ) {
+		p->typ = SOURCE;
+		p->down = compile(p->text, 1, f);
+		p->text = 0;
+	    }
+	}
+	else if ( isfootnote(ptr) ) {
+	    /* footnotes, like cats, sleep anywhere; pull them
+	     * out of the input stream and file them away for
+	     * later processing
+	     */
+	    ptr = consume(addfootnote(ptr, f), &eaten);
+	}
+	else {
+	    /* source; cache it up to wait for eof or the
+	     * next html/style block
+	     */
+	    ATTACH(source,ptr);
+	    ptr = ptr->next;
+	}
+    }
+    if ( T(source) ) {
+	/* if there's any cached source at EOF, compile
+	 * it now.
+	 */
+	E(source)->next = 0;
+	p = Pp(&d, 0, SOURCE);
+	p->down = compile(T(source), 1, f);
+    }
+    return T(d);
+}
+
+
+/*
+ * break a collection of markdown input into
+ * blocks of lists, code, html, and text to
+ * be marked up.
+ */
+static Paragraph *
+compile(Line *ptr, int toplevel, MMIOT *f)
+{
+    ParagraphRoot d = { 0, 0 };
+    Paragraph *p = 0;
+    Line *r;
+    int para = toplevel;
+    int blocks = 0;
+    int hdr_type, list_type, list_class, indent;
+
+    ptr = consume(ptr, &para);
+
+    while ( ptr ) {
+	if ( iscode(ptr) ) {
+	    p = Pp(&d, ptr, CODE);
+	    
+	    if ( f->flags & MKD_1_COMPAT) {
+		/* HORRIBLE STANDARDS KLUDGE: the first line of every block
+		 * has trailing whitespace trimmed off.
+		 */
+		___mkd_tidy(&p->text->text);
+	    }
+	    
+	    ptr = codeblock(p);
+	}
+	else if ( ishr(ptr) ) {
+	    p = Pp(&d, 0, HR);
+	    r = ptr;
+	    ptr = ptr->next;
+	    ___mkd_freeLine(r);
+	}
+	else if (( list_class = islist(ptr, &indent, f->flags, &list_type) )) {
+	    if ( list_class == DL ) {
+		p = Pp(&d, ptr, DL);
+		ptr = definition_block(p, indent, f, list_type);
+	    }
+	    else {
+		p = Pp(&d, ptr, list_type);
+		ptr = enumerated_block(p, indent, f, list_class);
+	    }
+	}
+	else if ( isquote(ptr) ) {
+	    p = Pp(&d, ptr, QUOTE);
+	    ptr = quoteblock(p, f->flags);
+	    p->down = compile(p->text, 1, f);
+	    p->text = 0;
+	}
+	else if ( ishdr(ptr, &hdr_type) ) {
+	    p = Pp(&d, ptr, HDR);
+	    ptr = headerblock(p, hdr_type);
+	}
+	else if ( istable(ptr) && !(f->flags & (MKD_STRICT|MKD_NOTABLES)) ) {
+	    p = Pp(&d, ptr, TABLE);
+	    ptr = tableblock(p);
+	}
+	else {
+	    p = Pp(&d, ptr, MARKUP);
+	    ptr = textblock(p, toplevel, f->flags);
+	}
+
+	if ( (para||toplevel) && !p->align )
+	    p->align = PARA;
+
+	blocks++;
+	para = toplevel || (blocks > 1);
+	ptr = consume(ptr, &para);
+
+	if ( para && !p->align )
+	    p->align = PARA;
+
+    }
+    return T(d);
+}
+
+
+/*
+ * the guts of the markdown() function, ripped out so I can do
+ * debugging.
+ */
+
+/*
+ * prepare and compile `text`, returning a Paragraph tree.
+ */
+int
+mkd_compile(Document *doc, DWORD flags)
+{
+    if ( !doc )
+	return 0;
+
+    if ( doc->compiled )
+	return 1;
+
+    doc->compiled = 1;
+    memset(doc->ctx, 0, sizeof(MMIOT) );
+    doc->ctx->cb        = &(doc->cb);
+    doc->ctx->flags     = flags & USER_FLAGS;
+    CREATE(doc->ctx->in);
+    doc->ctx->footnotes = malloc(sizeof doc->ctx->footnotes[0]);
+    CREATE(*doc->ctx->footnotes);
+
+    mkd_initialize();
+
+    doc->code = compile_document(T(doc->content), doc->ctx);
+    qsort(T(*doc->ctx->footnotes), S(*doc->ctx->footnotes),
+		        sizeof T(*doc->ctx->footnotes)[0],
+			           (stfu)__mkd_footsort);
+    memset(&doc->content, 0, sizeof doc->content);
+    return 1;
+}
+
diff --git a/markdown.h b/markdown.h
new file mode 100644
index 0000000..5a6d705
--- /dev/null
+++ b/markdown.h
@@ -0,0 +1,169 @@
+#ifndef _MARKDOWN_D
+#define _MARKDOWN_D
+
+#include "cstring.h"
+
+/* reference-style links (and images) are stored in an array
+ * of footnotes.
+ */
+typedef struct footnote {
+    Cstring tag;		/* the tag for the reference link */
+    Cstring link;		/* what this footnote points to */
+    Cstring title;		/* what it's called (TITLE= attribute) */
+    int height, width;		/* dimensions (for image link) */
+    int dealloc;		/* deallocation needed? */
+} Footnote;
+
+/* each input line is read into a Line, which contains the line,
+ * the offset of the first non-space character [this assumes 
+ * that all tabs will be expanded to spaces!], and a pointer to
+ * the next line.
+ */
+typedef struct line {
+    Cstring text;
+    struct line *next;
+    int dle;
+} Line;
+
+
+/* a paragraph is a collection of Lines, with links to the next paragraph
+ * and (if it's a QUOTE, UL, or OL) to the reparsed contents of this
+ * paragraph.
+ */
+typedef struct paragraph {
+    struct paragraph *next;	/* next paragraph */
+    struct paragraph *down;	/* recompiled contents of this paragraph */
+    struct line *text;		/* all the text in this paragraph */
+    char *ident;		/* %id% tag for QUOTE */
+    enum { WHITESPACE=0, CODE, QUOTE, MARKUP,
+	   HTML, STYLE, DL, UL, OL, AL, LISTITEM,
+	   HDR, HR, TABLE, SOURCE } typ;
+    enum { IMPLICIT=0, PARA, CENTER} align;
+    int hnumber;		/* <Hn> for typ == HDR */
+} Paragraph;
+
+enum { ETX, SETEXT };	/* header types */
+
+
+typedef struct block {
+    enum { bTEXT, bSTAR, bUNDER } b_type;
+    int  b_count;
+    char b_char;
+    Cstring b_text;
+    Cstring b_post;
+} block;
+
+typedef STRING(block) Qblock;
+
+
+typedef char* (*mkd_callback_t)(const char*, const int, void*);
+typedef void  (*mkd_free_t)(char*, void*);
+
+typedef struct callback_data {
+    void *e_data;		/* private data for callbacks */
+    mkd_callback_t e_url;	/* url edit callback */
+    mkd_callback_t e_flags;	/* extra href flags callback */
+    mkd_free_t e_free;		/* edit/flags callback memory deallocator */
+} Callback_data;
+
+
+/* a magic markdown io thing holds all the data structures needed to
+ * do the backend processing of a markdown document
+ */
+typedef struct mmiot {
+    Cstring out;
+    Cstring in;
+    Qblock Q;
+    int isp;
+    STRING(Footnote) *footnotes;
+    DWORD flags;
+#define MKD_NOLINKS	0x00000001
+#define MKD_NOIMAGE	0x00000002
+#define MKD_NOPANTS	0x00000004
+#define MKD_NOHTML	0x00000008
+#define MKD_STRICT	0x00000010
+#define MKD_TAGTEXT	0x00000020
+#define MKD_NO_EXT	0x00000040
+#define MKD_CDATA	0x00000080
+#define MKD_NOSUPERSCRIPT 0x00000100
+#define MKD_NORELAXED	0x00000200
+#define MKD_NOTABLES	0x00000400
+#define MKD_NOSTRIKETHROUGH 0x00000800
+#define MKD_TOC		0x00001000
+#define MKD_1_COMPAT	0x00002000
+#define MKD_AUTOLINK	0x00004000
+#define MKD_SAFELINK	0x00008000
+#define MKD_NOHEADER	0x00010000
+#define MKD_TABSTOP	0x00020000
+#define MKD_NODIVQUOTE	0x00040000
+#define MKD_NOALPHALIST	0x00080000
+#define MKD_NODLIST	0x00100000
+#define IS_LABEL	0x08000000
+#define USER_FLAGS	0x0FFFFFFF
+#define INPUT_MASK	(MKD_NOHEADER|MKD_TABSTOP)
+
+    Callback_data *cb;
+} MMIOT;
+
+
+/*
+ * the mkdio text input functions return a document structure,
+ * which contains a header (retrieved from the document if
+ * markdown was configured * with the * --enable-pandoc-header
+ * and the document begins with a pandoc-style header) and the
+ * root of the linked list of Lines.
+ */
+typedef struct document {
+    int magic;			/* "I AM VALID" magic number */
+#define VALID_DOCUMENT		0x19600731
+    Line *title;
+    Line *author;
+    Line *date;
+    ANCHOR(Line) content;	/* uncompiled text, not valid after compile() */
+    Paragraph *code;		/* intermediate code generated by compile() */
+    int compiled;		/* set after mkd_compile() */
+    int html;			/* set after (internal) htmlify() */
+    int tabstop;		/* for properly expanding tabs (ick) */
+    MMIOT *ctx;			/* backend buffers, flags, and structures */
+    Callback_data cb;		/* callback functions & private data */
+} Document;
+
+
+extern int  mkd_firstnonblank(Line *);
+extern int  mkd_compile(Document *, DWORD);
+extern int  mkd_document(Document *, char **);
+extern int  mkd_generatehtml(Document *, FILE *);
+extern int  mkd_css(Document *, char **);
+extern int  mkd_generatecss(Document *, FILE *);
+#define mkd_style mkd_generatecss
+extern int  mkd_xml(char *, int , char **);
+extern int  mkd_generatexml(char *, int, FILE *);
+extern void mkd_cleanup(Document *);
+extern int  mkd_line(char *, int, char **, DWORD);
+extern int  mkd_generateline(char *, int, FILE*, DWORD);
+#define mkd_text mkd_generateline
+extern void mkd_basename(Document*, char *);
+extern void mkd_string_to_anchor(char*,int, void(*)(int,void*), void*, int);
+
+extern Document *mkd_in(FILE *, DWORD);
+extern Document *mkd_string(char*,int, DWORD);
+
+extern void mkd_initialize();
+extern void mkd_shlib_destructor();
+
+/* internal resource handling functions.
+ */
+extern void ___mkd_freeLine(Line *);
+extern void ___mkd_freeLines(Line *);
+extern void ___mkd_freeParagraph(Paragraph *);
+extern void ___mkd_freefootnote(Footnote *);
+extern void ___mkd_freefootnotes(MMIOT *);
+extern void ___mkd_initmmiot(MMIOT *, void *);
+extern void ___mkd_freemmiot(MMIOT *, void *);
+extern void ___mkd_freeLineRange(Line *, Line *);
+extern void ___mkd_xml(char *, int, FILE *);
+extern void ___mkd_reparse(char *, int, int, MMIOT*);
+extern void ___mkd_emblock(MMIOT*);
+extern void ___mkd_tidy(Cstring *);
+
+#endif/*_MARKDOWN_D*/
diff --git a/mkd-callbacks.3 b/mkd-callbacks.3
new file mode 100644
index 0000000..be14c2a
--- /dev/null
+++ b/mkd-callbacks.3
@@ -0,0 +1,71 @@
+.\"
+.Dd January 18, 2008
+.Dt MKD_CALLBACKS 3
+.Os Mastodon
+.Sh NAME
+.Nm mkd_callbacks 
+.Nd functions that modify link targets
+.Sh LIBRARY
+Markdown 
+.Pq libmarkdown , -lmarkdown
+.Sh SYNOPSIS
+.Fd #include <mkdio.h>
+.Ft char*
+.Fn (*mkd_callback_t) "const char*" "const int" "void*"
+.Ft void
+.Fn (*mkd_free_t) "char *" "void*"
+.Ft void
+.Fn mkd_e_url "MMIOT *document" "mkd_callback_t edit"
+.Ft void
+.Fn mkd_e_flags "MMIOT *document" "mkd_callback_t edit"
+.Ft void
+.Fn mkd_e_free "MMIOT *document" "mkd_free_t dealloc"
+.Ft void
+.Fn mkd_e_data  "MMIOT *document" "void *data"
+.Sh DESCRIPTION
+.Pp
+.Nm Discount
+provides a small set of data access functions to let a
+library user modify the targets given in a `[]' link, and to
+add additional flags to the generated link.
+.Pp
+The data access functions are passed a character pointer to
+the url being generated, the size of the url, and a data pointer
+pointing to a user data area (set by the
+.Fn mkd_e_data
+function.)     After the callback function is called (either
+.Fn mkd_e_url 
+or
+.Fn mkd_e_flags )
+the data freeing function (if supplied) is called and passed the
+character pointer and user data pointer.
+.Sh EXAMPLE
+The
+.Fn mkd_basename
+function (in the module basename.c) is implemented by means of
+mkd callbacks;  it modifies urls that start with a `/' so that
+they begin with a user-supplied url base by allocating a new
+string and filling it with the base + the url.  Discount plugs
+that url in in place of the original, then calls the basename
+free function (it only does this when
+.Fn mkd_e_url
+or
+.Fn mkd_e_flags
+returns nonzero) to deallocate this memory.
+.Pp
+Note that only one level of callbacks are supported; if you
+wish to do multiple callbacks, you need to write your own
+code to handle them all.
+.Sh SEE ALSO
+.Xr markdown 1 ,
+.Xr markdown 3 ,
+.Xr mkd-line 3 ,
+.Xr markdown 7 ,
+.Xr mkd-extensions 7 ,
+.Xr mmap 2 .
+.Pp
+basename.c
+.Pp
+http://daringfireball.net/projects/markdown/syntax
+.Sh BUGS
+Error handling is minimal at best.
diff --git a/mkd-extensions.7 b/mkd-extensions.7
new file mode 100644
index 0000000..d1d2600
--- /dev/null
+++ b/mkd-extensions.7
@@ -0,0 +1,190 @@
+.\"
+.Dd Dec 22, 2007
+.Dt MKD-EXTENSIONS 7
+.Os MASTODON
+.Sh NAME
+.Nm mkd-extensions
+.Nd Extensions to the Markdown text formatting syntax
+.Sh DESCRIPTION
+This version of markdown has been extended in a few ways by
+extending existing markup, creating new markup from scratch,
+and borrowing markup from other markup languages.
+.Ss Image dimensions
+Markdown embedded images have been extended to allow specifying
+the dimensions of the image by adding a new argument
+.Em =/height/x/width/
+to the link description.
+.Pp
+The new image syntax is
+.nf
+	![alt text](image =/height/x/width/ "title")
+.fi
+.Ss pseudo-protocols
+Five pseudo-protocols have been added to links
+.Bl -tag -width XXXXX
+.It Ar id:
+The 
+.Ar "alt text"
+is marked up and written to the output, wrapped with
+.Em "<a id=id>"
+and
+.Em "</a>" .
+.It Ar class:
+The
+.Ar "alt text"
+is marked up and written to the output, wrapped with
+.Em "<span class=class>"
+and
+.Em "</span>" .
+.It Ar raw:
+The
+.Ar title
+is written
+.Em -- with no further processing --
+to the output.  The 
+.Ar "alt text"
+is discarded.
+.It Ar abbr:
+The
+.Ar "alt text"
+is marked up and written to the output, wrapped with
+.Em "<abbr title=abbr>"
+and
+.Em "</abbr>" .
+.It Ar lang:
+The
+.Ar "alt text"
+s marked up and written to the output, wrapped with
+.Em "<span lang=lang>"
+and
+.Em "</span>" .
+.El
+.Ss Pandoc headers
+The markdown source document can have a 3-line 
+.Xr Pandoc
+header in the format of
+.nf
+    % title
+    % author(s)
+    % date
+.fi
+which will be made available to the
+.Fn mkd_doc_title ,
+.Fn mkd_doc_author ,
+and
+.Fn mkd_doc_date
+functions.
+.Ss Definition lists
+A definition list item
+is defined as
+.nf
+=tag=
+    description
+.fi
+(that is a
+.Ar = ,
+followed by text, another
+.Ar = ,
+a newline, 4 spaces of intent, and then more text.)
+.Pp
+.Ss embedded stylesheets
+Stylesheets may be defined and modified in a
+.Em <style>
+block.   A style block is parsed like any other
+block level html;  
+.Em <style>
+starting on column 1, raw html (or, in this case, css) following
+it, and either ending with a 
+.Em </style>
+at the end of the line or a
+.Em </style>
+at the beginning of a subsequent line.
+.Pp
+Be warned that style blocks work like footnote links -- no matter
+where you define them they are valid for the entire document.
+.Ss relaxed emphasis
+The rules for emphasis are changed so that a single
+.Ar _
+will
+.Em not
+count as a emphasis character if it's in the middle of a word.
+This is primarily for documenting code, if you don't wish to
+have to backquote all code references.
+.Ss alpha lists
+Alphabetic lists (like regular numeric lists, but with alphabetic
+items) are supported.    So:
+.nf
+    a. this
+    b. is
+    c. an alphabetic
+    d. list
+.fi
+will produce:
+.nf
+    <ol type=a>
+    <li>this</li>
+    <li>is</li>
+    <li>an alphabetic</li>
+    <li>list</li>
+    </ol>
+.fi
+.Ss tables
+.Ar "PHP Markdown Extra"
+tables are supported;  input of the form
+.nf
+    header|header
+    ------|------
+     text | text
+.fi
+will produce:
+.nf
+    <table>
+    <thead>
+    <tr>
+    <th>header</th>
+    <th>header</th>
+    </tr>
+    </thead>
+    <tbody>
+    <tr>
+    <td>text</td>
+    <td>text</td>
+    </tr>
+    </tbody>
+    </table>
+.fi
+The dashed line can also contain
+.Em :
+characters for formatting;  if a 
+.Em :
+is at the start of a column, it tells
+.Nm discount
+to align the cell contents to the left;  if it's at the end, it
+aligns right, and if there's one at the start and at the
+end, it centers.
+.Ss strikethrough
+A strikethrough syntax is supported in much the same way that
+.Ar `
+is used to define a section of code.   If you enclose text with
+two or more tildes, such as
+.Em ~~erased text~~
+it will be written as
+.Em "<del>erased text</del>" .
+Like code sections, you may use as many 
+.Ar ~
+as you want, but there must be as many starting tildes as closing
+tildes.
+.Sh AUTHOR
+David Parsons
+.%T http://www.pell.portland.or.us/~orc/
+.Sh SEE ALSO
+.Xr markdown 1 ,
+.Xr markdown 3 ,
+.Xr mkd-callbacks 3 ,
+.Xr mkd-functions 3 ,
+.Xr mkd-line 3 ,
+.Xr mkd-extensions 7 .
+.Pp
+.%T http://daringfireball.net/projects/markdown
+.Pp
+.%T http://michelf.com/projects/php-markdown
diff --git a/mkd-functions.3 b/mkd-functions.3
new file mode 100644
index 0000000..19dee7c
--- /dev/null
+++ b/mkd-functions.3
@@ -0,0 +1,182 @@
+.\"
+.Dd January 18, 2008
+.Dt MKD_FUNCTIONS 3
+.Os Mastodon
+.Sh NAME
+.Nm mkd_functions 
+.Nd access and process Markdown documents.
+.Sh LIBRARY
+Markdown 
+.Pq libmarkdown , -lmarkdown
+.Sh SYNOPSIS
+.Fd #include <mkdio.h>
+.Ft int
+.Fn mkd_compile "MMIOT *document" "int flags"
+.Ft int
+.Fn mkd_css "MMIOT *document" "char **doc"
+.Ft int
+.Fn mkd_generatecss  "MMIOT *document" "FILE *output"
+.Ft int
+.Fn mkd_document "MMIOT *document" "char **doc"
+.Ft int
+.Fn mkd_generatehtml  "MMIOT *document" "FILE *output"
+.Ft int
+.Fn mkd_xhtmlpage "MMIOT *document" "int flags" "FILE *output"
+.Ft int
+.Fn mkd_toc "MMIOT *document" "char **doc"
+.Ft void
+.Fn mkd_generatetoc "MMIOT *document" "FILE *output"
+.Ft void
+.Fn mkd_cleanup "MMIOT*"
+.Ft char*
+.Fn mkd_doc_title "MMIOT*"
+.Ft char*
+.Fn mkd_doc_author "MMIOT*"
+.Ft char*
+.Fn mkd_doc_date "MMIOT*"
+.Sh DESCRIPTION
+.Pp
+The
+.Nm markdown
+format supported in this implementation includes
+Pandoc-style header and inline 
+.Ar \<style\>
+blocks, and the standard
+.Xr markdown 3
+functions do not provide access to
+the data provided by either of those extensions.
+These functions give you access to that data, plus
+they provide a finer-grained way of converting
+.Em Markdown 
+documents into HTML.
+.Pp
+Given a
+.Ar MMIOT*
+generated by
+.Fn mkd_in
+or
+.Fn mkd_string ,
+.Fn mkd_compile
+compiles the document into
+.Em \<style\> ,
+.Em Pandoc ,
+and
+.Em html
+sections.
+.Pp
+Once compiled, the document can be examined and written
+by the
+.Fn mkd_css ,
+.Fn mkd_document ,
+.Fn mkd_generatecss ,
+.Fn mkd_generatehtml ,
+.Fn mkd_generatetoc ,
+.Fn mkd_toc ,
+.Fn mkd_xhtmlpage ,
+.Fn mkd_doc_title ,
+.Fn mkd_doc_author ,
+and
+.Fn mkd_doc_date
+functions.
+.Pp
+.Fn mkd_css
+allocates a string and populates it with any \<style\> sections
+provided in the document,
+.Fn mkd_generatecss
+writes any \<style\> sections to the output,
+.Fn mkd_document
+points
+.Ar text
+to the text of the document and returns the
+size of the document,
+.Fn mkd_generatehtml
+writes the rest of the document to the output,
+and 
+.Fn mkd_doc_title ,
+.Fn mkd_doc_author ,
+.Fn mkd_doc_date
+are used to read the contents of a Pandoc header,
+if any.
+.Pp
+.Fn mkd_xhtmlpage
+writes a xhtml page containing the document.  The regular set of
+flags can be passed.
+.Pp
+.Fn mkd_toc
+writes a document outline, in the form of a collection of nested
+lists with links to each header in the document, into a string
+allocated with
+.Fn malloc ,
+and returns the size.
+.Pp
+.Fn mkd_generatetoc
+is like
+.Fn mkd_toc ,
+except that it writes the document outline to the given
+.Pa FILE*
+argument.
+.Pp
+.Fn mkd_cleanup
+deletes a
+.Ar MMIOT*
+after processing is done.
+.Pp
+.Fn mkd_compile
+accepts the same flags that
+.Fn markdown
+and
+.Fn mkd_string
+do; 
+.Bl -tag -width MKD_NOSTRIKETHROUGH -compact
+.It Ar MKD_NOIMAGE
+Do not process `![]' and
+remove
+.Em \<img\>
+tags from the output.
+.It Ar MKD_NOLINKS
+Do not process `[]' and remove
+.Em \<a\>
+tags from the output.
+.It Ar MKD_NOPANTS
+Do not do Smartypants-style mangling of quotes, dashes, or ellipses.
+.It Ar MKD_TAGTEXT
+Process the input as if you were inside a html tag.  This means that
+no html tags will be generated, and 
+.Fn mkd_compile
+will attempt to escape anything that might terribly confuse a 
+web browser.
+.It Ar MKD_NO_EXT
+Do not process any markdown pseudo-protocols when
+handing
+.Ar [][]
+links.
+.It Ar MKD_NOHEADER
+Do not attempt to parse any Pandoc-style headers.
+.It Ar MKD_TOC
+Label all headers for use with the
+.Fn mkd_generatetoc
+function.
+.It Ar MKD_1_COMPAT
+MarkdownTest_1.0 compatibility flag; trim trailing spaces from the
+first line of code blocks and disable implicit reference links.
+.It Ar MKD_NOSTRIKETHROUGH
+Disable strikethrough support.
+.El
+.Sh RETURN VALUES
+The functions
+.Fn mkd_compile ,
+.Fn mkd_style ,
+and
+.Fn mkd_generatehtml
+return 0 on success, -1 on failure.
+.Sh SEE ALSO
+.Xr markdown 1 ,
+.Xr markdown 3 ,
+.Xr mkd-line 3 ,
+.Xr markdown 7 ,
+.Xr mkd-extensions 7 ,
+.Xr mmap 2 .
+.Pp
+http://daringfireball.net/projects/markdown/syntax
+.Sh BUGS
+Error handling is minimal at best.
diff --git a/mkd-line.3 b/mkd-line.3
new file mode 100644
index 0000000..7a35446
--- /dev/null
+++ b/mkd-line.3
@@ -0,0 +1,41 @@
+.\"
+.Dd January 18, 2008
+.Dt MKD_LINE 3
+.Os Mastodon
+.Sh NAME
+.Nm mkd_line 
+.Nd do Markdown translation of small items
+.Sh LIBRARY
+Markdown 
+.Pq libmarkdown , -lmarkdown
+.Sh SYNOPSIS
+.Fd #include <mkdio.h>
+.Ft int
+.Fn mkd_line "char *string" "int size" "char **doc" "int flags"
+.Ft int
+.Fn mkd_generateline "char *string" "int size" "FILE *output" "int flags"
+.Sh DESCRIPTION
+.Pp
+Occasionally one might want to do markdown translations on fragments of
+data, like the title of an weblog article, a date, or a simple signature
+line.
+.Nm mkd_line
+and
+.Nm mkd_generateline
+allow you to do markdown translations on small blocks of text.
+.Nm mkd_line
+allocates a buffer, then writes the translated text into that buffer,
+and
+.Nm mkd_generateline
+writes the output to the specified
+.Ar FILE* .
+.Sh SEE ALSO
+.Xr markdown 1 ,
+.Xr markdown 3 ,
+.Xr markdown 7 ,
+.Xr mkd-extensions 7 ,
+.Xr mmap 2 .
+.Pp
+http://daringfireball.net/projects/markdown/syntax
+.Sh BUGS
+Error handling is minimal at best.
diff --git a/mkd2html.1 b/mkd2html.1
new file mode 100644
index 0000000..44688d9
--- /dev/null
+++ b/mkd2html.1
@@ -0,0 +1,52 @@
+.\"     %A%
+.\"
+.Dd January 10, 2010
+.Dt MKD2HTML 1
+.Os MASTODON
+.Sh NAME
+.Nm mkd2html
+.Nd markdown to html converter
+.Sh SYNOPSIS
+.Nm
+.Op Fl css Pa file
+.Op Fl header Pa string
+.Op Fl footer Pa string
+.Op Pa file
+.Sh DESCRIPTION
+.Nm
+utility parses a
+.Xr markdown 7 Ns -formatted
+.Pa textfile
+.Pq or stdin if not specified,
+and generates a web page. It
+reads
+.Ar file
+or
+.Ar file.text
+ and writes the result in
+.Ar file.html
+.Pq where file is the passed argument.
+.Pp
+.Nm
+is part of discount.
+.Sh OPTIONS
+.Bl -tag -width "-header string"
+.It Fl css Ar file
+Specifies a CSS file.
+.It Fl header Ar string
+Specifies a line to add to the <header> tag.
+.It Fl footer Ar string
+Specifies a line to add before the <\/body> tag.
+.El
+.Sh RETURN VALUES
+The
+.Nm
+utility exits 0 on success, and >0 if an error occurs.
+.Sh SEE ALSO
+.Xr markdown 1 ,
+.Xr markdown 3 ,
+.Xr markdown 7 ,
+.Xr mkd-extensions 7 .
+.Sh AUTHOR
+.An David Parsons
+.Pq Li orc at pell.chi.il.us
diff --git a/mkd2html.c b/mkd2html.c
new file mode 100644
index 0000000..ae15320
--- /dev/null
+++ b/mkd2html.c
@@ -0,0 +1,185 @@
+/*
+ * mkd2html:  parse a markdown input file and generate a web page.
+ *
+ * usage:  mkd2html [options] filename
+ *  or     mkd2html [options] < markdown > html
+ *
+ *  options
+ *         -css css-file
+ *         -header line-to-add-to-<HEADER>
+ *         -footer line-to-add-before-</BODY>
+ *
+ * example:
+ *
+ *   mkd2html -cs /~orc/pages.css syntax
+ *     ( read syntax OR syntax.text, write syntax.html )
+ */
+/*
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_BASENAME
+# ifdef HAVE_LIBGEN_H
+#  include <libgen.h>
+# else
+#  include <unistd.h>
+# endif
+#endif
+#include <stdarg.h>
+
+#include "mkdio.h"
+#include "cstring.h"
+#include "amalloc.h"
+
+char *pgm = "mkd2html";
+
+#ifndef HAVE_BASENAME
+char *
+basename(char *path)
+{
+    char *p;
+
+    if (( p = strrchr(path, '/') ))
+	return 1+p;
+    return path;
+}
+#endif
+
+void
+fail(char *why, ...)
+{
+    va_list ptr;
+
+    va_start(ptr,why);
+    fprintf(stderr, "%s: ", pgm);
+    vfprintf(stderr, why, ptr);
+    fputc('\n', stderr);
+    va_end(ptr);
+    exit(1);
+}
+
+
+void
+main(argc, argv)
+char **argv;
+{
+    char *h;
+    char *source = 0, *dest = 0;
+    MMIOT *mmiot;
+    int i;
+    FILE *input, *output; 
+    STRING(char*) css, headers, footers;
+
+
+    CREATE(css);
+    CREATE(headers);
+    CREATE(footers);
+    pgm = basename(argv[0]);
+
+    while ( argc > 2 ) {
+	if ( strcmp(argv[1], "-css") == 0 ) {
+	    EXPAND(css) = argv[2];
+	    argc -= 2;
+	    argv += 2;
+	}
+	else if ( strcmp(argv[1], "-header") == 0 ) {
+	    EXPAND(headers) = argv[2];
+	    argc -= 2;
+	    argv += 2;
+	}
+	else if ( strcmp(argv[1], "-footer") == 0 ) {
+	    EXPAND(footers) = argv[2];
+	    argc -= 2;
+	    argv += 2;
+	}
+    }
+
+
+    if ( argc > 1 ) {
+	char *p, *dot;
+	
+	source = malloc(strlen(argv[1]) + 6);
+	dest   = malloc(strlen(argv[1]) + 6);
+
+	if ( !(source && dest) )
+	    fail("out of memory allocating name buffers");
+
+	strcpy(source, argv[1]);
+	if (( p = strrchr(source, '/') ))
+	    p = source;
+	else
+	    ++p;
+
+	if ( (input = fopen(source, "r")) == 0 ) {
+	    strcat(source, ".text");
+	    if ( (input = fopen(source, "r")) == 0 )
+		fail("can't open either %s or %s", argv[1], source);
+	}
+	strcpy(dest, source);
+
+	if (( dot = strrchr(dest, '.') ))
+	    *dot = 0;
+	strcat(dest, ".html");
+
+	if ( (output = fopen(dest, "w")) == 0 )
+	    fail("can't write to %s", dest);
+    }
+    else {
+	input = stdin;
+	output = stdout;
+    }
+
+    if ( (mmiot = mkd_in(input, 0)) == 0 )
+	fail("can't read %s", source ? source : "stdin");
+
+    if ( !mkd_compile(mmiot, 0) )
+	fail("couldn't compile input");
+
+
+    h = mkd_doc_title(mmiot);
+
+    /* print a header */
+
+    fprintf(output,
+	"<!doctype html public \"-//W3C//DTD HTML 4.0 Transitional //EN\">\n"
+	"<html>\n"
+	"<head>\n"
+	"  <meta name=\"GENERATOR\" content=\"mkd2html %s\">\n", markdown_version);
+
+    fprintf(output,"  <meta http-equiv=\"Content-Type\"\n"
+		   "        content=\"text/html; charset-us-ascii\">");
+
+    for ( i=0; i < S(css); i++ )
+	fprintf(output, "  <link rel=\"stylesheet\"\n"
+			"        type=\"text/css\"\n"
+			"        href=\"%s\" />\n", T(css)[i]);
+
+    if ( h ) {
+	fprintf(output,"  <title>");
+	mkd_generateline(h, strlen(h), output, 0);
+	fprintf(output, "</title>\n");
+    }
+    for ( i=0; i < S(headers); i++ )
+	fprintf(output, "  %s\n", T(headers)[i]);
+    fprintf(output, "</head>\n"
+		    "<body>\n");
+
+    /* print the compiled body */
+
+    mkd_generatehtml(mmiot, output);
+
+    for ( i=0; i < S(footers); i++ )
+	fprintf(output, "%s\n", T(footers)[i]);
+    
+    fprintf(output, "</body>\n"
+		    "</html>\n");
+    
+    mkd_cleanup(mmiot);
+    exit(0);
+}
diff --git a/mkdio.c b/mkdio.c
new file mode 100644
index 0000000..13784fd
--- /dev/null
+++ b/mkdio.c
@@ -0,0 +1,344 @@
+/*
+ * mkdio -- markdown front end input functions
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+typedef ANCHOR(Line) LineAnchor;
+
+/* create a new blank Document
+ */
+static Document*
+new_Document()
+{
+    Document *ret = calloc(sizeof(Document), 1);
+
+    if ( ret ) {
+	if (( ret->ctx = calloc(sizeof(MMIOT), 1) )) {
+	    ret->magic = VALID_DOCUMENT;
+	    return ret;
+	}
+	free(ret);
+    }
+    return 0;
+}
+
+
+/* add a line to the markdown input chain
+ */
+static void
+queue(Document* a, Cstring *line)
+{
+    Line *p = calloc(sizeof *p, 1);
+    unsigned char c;
+    int xp = 0;
+    int           size = S(*line);
+    unsigned char *str = (unsigned char*)T(*line);
+
+    CREATE(p->text);
+    ATTACH(a->content, p);
+
+    while ( size-- ) {
+	if ( (c = *str++) == '\t' ) {
+	    /* expand tabs into ->tabstop spaces.  We use ->tabstop
+	     * because the ENTIRE FREAKING COMPUTER WORLD uses editors
+	     * that don't do ^T/^D, but instead use tabs for indentation,
+	     * and, of course, set their tabs down to 4 spaces 
+	     */
+	    do {
+		EXPAND(p->text) = ' ';
+	    } while ( ++xp % a->tabstop );
+	}
+	else if ( c >= ' ' ) {
+	    EXPAND(p->text) = c;
+	    ++xp;
+	}
+    }
+    EXPAND(p->text) = 0;
+    S(p->text)--;
+    p->dle = mkd_firstnonblank(p);
+}
+
+
+/* trim leading blanks from a header line
+ */
+static void
+header_dle(Line *p)
+{
+    CLIP(p->text, 0, 1);
+    p->dle = mkd_firstnonblank(p);
+}
+
+
+/* build a Document from any old input.
+ */
+typedef int (*getc_func)(void*);
+
+Document *
+populate(getc_func getc, void* ctx, int flags)
+{
+    Cstring line;
+    Document *a = new_Document();
+    int c;
+    int pandoc = 0;
+
+    if ( !a ) return 0;
+
+    a->tabstop = (flags & MKD_TABSTOP) ? 4 : TABSTOP;
+
+    CREATE(line);
+
+    while ( (c = (*getc)(ctx)) != EOF ) {
+	if ( c == '\n' ) {
+	    if ( pandoc != EOF && pandoc < 3 ) {
+		if ( S(line) && (T(line)[0] == '%') )
+		    pandoc++;
+		else
+		    pandoc = EOF;
+	    }
+	    queue(a, &line);
+	    S(line) = 0;
+	}
+	else if ( isprint(c) || isspace(c) || (c & 0x80) )
+	    EXPAND(line) = c;
+    }
+
+    if ( S(line) )
+	queue(a, &line);
+
+    DELETE(line);
+
+    if ( (pandoc == 3) && !(flags & (MKD_NOHEADER|MKD_STRICT)) ) {
+	/* the first three lines started with %, so we have a header.
+	 * clip the first three lines out of content and hang them
+	 * off header.
+	 */
+	Line *headers = T(a->content);
+
+	a->title = headers;             header_dle(a->title);
+	a->author= headers->next;       header_dle(a->author);
+	a->date  = headers->next->next; header_dle(a->date);
+
+	T(a->content) = headers->next->next->next;
+    }
+
+    return a;
+}
+
+
+/* convert a file into a linked list
+ */
+Document *
+mkd_in(FILE *f, DWORD flags)
+{
+    return populate((getc_func)fgetc, f, flags & INPUT_MASK);
+}
+
+
+/* return a single character out of a buffer
+ */
+struct string_ctx {
+    char *data;		/* the unread data */
+    int   size;		/* and how much is there? */
+} ;
+
+
+static int
+strget(struct string_ctx *in)
+{
+    if ( !in->size ) return EOF;
+
+    --(in->size);
+
+    return *(in->data)++;
+}
+
+
+/* convert a block of text into a linked list
+ */
+Document *
+mkd_string(char *buf, int len, DWORD flags)
+{
+    struct string_ctx about;
+
+    about.data = buf;
+    about.size = len;
+
+    return populate((getc_func)strget, &about, flags & INPUT_MASK);
+}
+
+
+/* write the html to a file (xmlified if necessary)
+ */
+int
+mkd_generatehtml(Document *p, FILE *output)
+{
+    char *doc;
+    int szdoc;
+
+    if ( (szdoc = mkd_document(p, &doc)) != EOF ) {
+	if ( p->ctx->flags & MKD_CDATA )
+	    mkd_generatexml(doc, szdoc, output);
+	else
+	    fwrite(doc, szdoc, 1, output);
+	putc('\n', output);
+	return 0;
+    }
+    return -1;
+}
+
+
+/* convert some markdown text to html
+ */
+int
+markdown(Document *document, FILE *out, int flags)
+{
+    if ( mkd_compile(document, flags) ) {
+	mkd_generatehtml(document, out);
+	mkd_cleanup(document);
+	return 0;
+    }
+    return -1;
+}
+
+
+/* write out a Cstring, mangled into a form suitable for `<a href=` or `<a id=`
+ */
+void
+mkd_string_to_anchor(char *s, int len, void(*outchar)(int,void*),
+				       void *out, int labelformat)
+{
+    unsigned char c;
+
+    int i, size;
+    char *line;
+
+    size = mkd_line(s, len, &line, IS_LABEL);
+    
+    if ( labelformat && size && !isalpha(line[0]) )
+	(*outchar)('L',out);
+    for ( i=0; i < size ; i++ ) {
+	c = line[i];
+	if ( labelformat ) {
+	    if ( isalnum(c) || (c == '_') || (c == ':') || (c == '-') || (c == '.' ) )
+		(*outchar)(c, out);
+	    else
+		(*outchar)('.',out);
+	}
+	else
+	    (*outchar)(c,out);
+    }
+	
+    if (line)
+	free(line);
+}
+
+
+/*  ___mkd_reparse() a line
+ */
+static void
+mkd_parse_line(char *bfr, int size, MMIOT *f, int flags)
+{
+    ___mkd_initmmiot(f, 0);
+    f->flags = flags & USER_FLAGS;
+    ___mkd_reparse(bfr, size, 0, f);
+    ___mkd_emblock(f);
+}
+
+
+/* ___mkd_reparse() a line, returning it in malloc()ed memory
+ */
+int
+mkd_line(char *bfr, int size, char **res, DWORD flags)
+{
+    MMIOT f;
+    int len;
+    
+    mkd_parse_line(bfr, size, &f, flags);
+
+    if ( len = S(f.out) ) {
+	/* kludge alert;  we know that T(f.out) is malloced memory,
+	 * so we can just steal it away.   This is awful -- there
+	 * should be an opaque method that transparently moves 
+	 * the pointer out of the embedded Cstring.
+	 */
+	EXPAND(f.out) = 0;
+	*res = T(f.out);
+	T(f.out) = 0;
+	S(f.out) = ALLOCATED(f.out) = 0;
+    }
+    else {
+	 *res = 0;
+	 len = EOF;
+     }
+    ___mkd_freemmiot(&f, 0);
+    return len;
+}
+
+
+/* ___mkd_reparse() a line, writing it to a FILE
+ */
+int
+mkd_generateline(char *bfr, int size, FILE *output, DWORD flags)
+{
+    MMIOT f;
+
+    mkd_parse_line(bfr, size, &f, flags);
+    if ( flags & MKD_CDATA )
+	mkd_generatexml(T(f.out), S(f.out), output);
+    else
+	fwrite(T(f.out), S(f.out), 1, output);
+
+    ___mkd_freemmiot(&f, 0);
+    return 0;
+}
+
+
+/* set the url display callback
+ */
+void
+mkd_e_url(Document *f, mkd_callback_t edit)
+{
+    if ( f )
+	f->cb.e_url = edit;
+}
+
+
+/* set the url options callback
+ */
+void
+mkd_e_flags(Document *f, mkd_callback_t edit)
+{
+    if ( f )
+	f->cb.e_flags = edit;
+}
+
+
+/* set the url display/options deallocator
+ */
+void
+mkd_e_free(Document *f, mkd_free_t dealloc)
+{
+    if ( f )
+	f->cb.e_free = dealloc;
+}
+
+
+/* set the url display/options context data field
+ */
+void
+mkd_e_data(Document *f, void *data)
+{
+    if ( f )
+	f->cb.e_data = data;
+}
diff --git a/mkdio.h.in b/mkdio.h.in
new file mode 100644
index 0000000..7ab658c
--- /dev/null
+++ b/mkdio.h.in
@@ -0,0 +1,100 @@
+#ifndef _MKDIO_D
+#define _MKDIO_D
+
+#include <stdio.h>
+
+typedef void MMIOT;
+
+typedef @DWORD@ mkd_flag_t;
+
+/* line builder for markdown()
+ */
+MMIOT *mkd_in(FILE*,mkd_flag_t);		/* assemble input from a file */
+MMIOT *mkd_string(char*,int,mkd_flag_t);	/* assemble input from a buffer */
+
+void mkd_basename(MMIOT*,char*);
+
+void mkd_initialize();
+void mkd_shlib_destructor();
+
+/* compilation, debugging, cleanup
+ */
+int mkd_compile(MMIOT*, mkd_flag_t);
+int mkd_cleanup(MMIOT*);
+
+/* markup functions
+ */
+int mkd_dump(MMIOT*, FILE*, int, char*);
+int markdown(MMIOT*, FILE*, mkd_flag_t);
+int mkd_line(char *, int, char **, mkd_flag_t);
+void mkd_string_to_anchor(char *, int, int (*)(int,void*), void*, int);
+int mkd_xhtmlpage(MMIOT*,int,FILE*);
+
+/* header block access
+ */
+char* mkd_doc_title(MMIOT*);
+char* mkd_doc_author(MMIOT*);
+char* mkd_doc_date(MMIOT*);
+
+/* compiled data access
+ */
+int mkd_document(MMIOT*, char**);
+int mkd_toc(MMIOT*, char**);
+int mkd_css(MMIOT*, char **);
+int mkd_xml(char *, int, char **);
+
+/* write-to-file functions
+ */
+int mkd_generatehtml(MMIOT*,FILE*);
+int mkd_generatetoc(MMIOT*,FILE*);
+int mkd_generatexml(char *, int,FILE*);
+int mkd_generatecss(MMIOT*,FILE*);
+#define mkd_style mkd_generatecss
+int mkd_generateline(char *, int, FILE*, mkd_flag_t);
+#define mkd_text mkd_generateline
+
+/* url generator callbacks
+ */
+typedef char * (*mkd_callback_t)(const char*, const int, void*);
+typedef void   (*mkd_free_t)(char*, void*);
+
+void mkd_e_url(void *, mkd_callback_t);
+void mkd_e_flags(void *, mkd_callback_t);
+void mkd_e_free(void *, mkd_free_t );
+void mkd_e_data(void *, void *);
+
+/* version#.
+ */
+extern char markdown_version[];
+
+/* special flags for markdown() and mkd_text()
+ */
+#define MKD_NOLINKS	0x00000001	/* don't do link processing, block <a> tags  */
+#define MKD_NOIMAGE	0x00000002	/* don't do image processing, block <img> */
+#define MKD_NOPANTS	0x00000004	/* don't run smartypants() */
+#define MKD_NOHTML	0x00000008	/* don't allow raw html through AT ALL */
+#define MKD_STRICT	0x00000010	/* disable SUPERSCRIPT, RELAXED_EMPHASIS */
+#define MKD_TAGTEXT	0x00000020	/* process text inside an html tag; no
+					 * <em>, no <bold>, no html or [] expansion */
+#define MKD_NO_EXT	0x00000040	/* don't allow pseudo-protocols */
+#define MKD_CDATA	0x00000080	/* generate code for xml ![CDATA[...]] */
+#define MKD_NOSUPERSCRIPT 0x00000100	/* no A^B */
+#define MKD_NORELAXED	0x00000200	/* emphasis happens /everywhere/ */
+#define MKD_NOTABLES	0x00000400	/* disallow tables */
+#define MKD_NOSTRIKETHROUGH 0x00000800	/* forbid ~~strikethrough~~ */
+#define MKD_TOC		0x00001000	/* do table-of-contents processing */
+#define MKD_1_COMPAT	0x00002000	/* compatibility with MarkdownTest_1.0 */
+#define MKD_AUTOLINK	0x00004000	/* make http://foo.com link even without <>s */
+#define MKD_SAFELINK	0x00008000	/* paranoid check for link protocol */
+#define MKD_NOHEADER	0x00010000	/* don't process header blocks */
+#define MKD_TABSTOP	0x00020000	/* expand tabs to 4 spaces */
+#define MKD_NODIVQUOTE	0x00040000	/* forbid >%class% blocks */
+#define MKD_NOALPHALIST	0x00080000	/* forbid alphabetic lists */
+#define MKD_NODLIST	0x00100000	/* forbid definition lists */
+#define MKD_EMBED	MKD_NOLINKS|MKD_NOIMAGE|MKD_TAGTEXT
+
+/* special flags for mkd_in() and mkd_string()
+ */
+
+
+#endif/*_MKDIO_D*/
diff --git a/resource.c b/resource.c
new file mode 100644
index 0000000..1dee8e3
--- /dev/null
+++ b/resource.c
@@ -0,0 +1,157 @@
+/* markdown: a C implementation of John Gruber's Markdown markup language.
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "config.h"
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+/* free a (single) line
+ */
+void
+___mkd_freeLine(Line *ptr)
+{
+    DELETE(ptr->text);
+    free(ptr);
+}
+
+
+/* free a list of lines
+ */
+void
+___mkd_freeLines(Line *p)
+{
+    if (p->next)
+	 ___mkd_freeLines(p->next);
+    ___mkd_freeLine(p);
+}
+
+
+/* bye bye paragraph.
+ */
+void
+___mkd_freeParagraph(Paragraph *p)
+{
+    if (p->next)
+	___mkd_freeParagraph(p->next);
+    if (p->down)
+	___mkd_freeParagraph(p->down);
+    if (p->text)
+	___mkd_freeLines(p->text);
+    if (p->ident)
+	free(p->ident);
+    free(p);
+}
+
+
+/* bye bye footnote.
+ */
+void
+___mkd_freefootnote(Footnote *f)
+{
+    DELETE(f->tag);
+    DELETE(f->link);
+    DELETE(f->title);
+}
+
+
+/* bye bye footnotes.
+ */
+void
+___mkd_freefootnotes(MMIOT *f)
+{
+    int i;
+
+    if ( f->footnotes ) {
+	for (i=0; i < S(*f->footnotes); i++)
+	    ___mkd_freefootnote( &T(*f->footnotes)[i] );
+	DELETE(*f->footnotes);
+	free(f->footnotes);
+    }
+}
+
+
+/* initialize a new MMIOT
+ */
+void
+___mkd_initmmiot(MMIOT *f, void *footnotes)
+{
+    if ( f ) {
+	memset(f, 0, sizeof *f);
+	CREATE(f->in);
+	CREATE(f->out);
+	CREATE(f->Q);
+	if ( footnotes )
+	    f->footnotes = footnotes;
+	else {
+	    f->footnotes = malloc(sizeof f->footnotes[0]);
+	    CREATE(*f->footnotes);
+	}
+    }
+}
+
+
+/* free the contents of a MMIOT, but leave the object alone.
+ */
+void
+___mkd_freemmiot(MMIOT *f, void *footnotes)
+{
+    if ( f ) {
+	DELETE(f->in);
+	DELETE(f->out);
+	DELETE(f->Q);
+	if ( f->footnotes != footnotes )
+	    ___mkd_freefootnotes(f);
+	memset(f, 0, sizeof *f);
+    }
+}
+
+
+/* free lines up to an barrier.
+ */
+void
+___mkd_freeLineRange(Line *anchor, Line *stop)
+{
+    Line *r = anchor->next;
+
+    if ( r != stop ) {
+	while ( r && (r->next != stop) )
+	    r = r->next;
+	if ( r ) r->next = 0;
+	___mkd_freeLines(anchor->next);
+    }
+    anchor->next = 0;
+}
+
+
+/* clean up everything allocated in __mkd_compile()
+ */
+void
+mkd_cleanup(Document *doc)
+{
+    if ( doc && (doc->magic == VALID_DOCUMENT) ) {
+	if ( doc->ctx ) {
+	    ___mkd_freemmiot(doc->ctx, 0);
+	    free(doc->ctx);
+	}
+
+	if ( doc->code) ___mkd_freeParagraph(doc->code);
+	if ( doc->title) ___mkd_freeLine(doc->title);
+	if ( doc->author) ___mkd_freeLine(doc->author);
+	if ( doc->date) ___mkd_freeLine(doc->date);
+	if ( T(doc->content) ) ___mkd_freeLines(T(doc->content));
+	memset(doc, 0, sizeof doc[0]);
+	free(doc);
+    }
+}
diff --git a/setup.c b/setup.c
new file mode 100644
index 0000000..79f47aa
--- /dev/null
+++ b/setup.c
@@ -0,0 +1,47 @@
+/* markdown: a C implementation of John Gruber's Markdown markup language.
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+#include "tags.h"
+    
+static int need_to_setup = 1;
+static int need_to_initrng = 1;
+
+void
+mkd_initialize()
+{
+
+    if ( need_to_initrng ) {
+	need_to_initrng = 0;
+	INITRNG(time(0));
+    }
+    if ( need_to_setup ) {
+	need_to_setup = 0;
+	mkd_prepare_tags();
+    }
+}
+
+
+void
+mkd_shlib_destructor()
+{
+    if ( !need_to_setup ) {
+	need_to_setup = 1;
+	mkd_deallocate_tags();
+    }
+}
+
diff --git a/tags.c b/tags.c
new file mode 100644
index 0000000..00affc1
--- /dev/null
+++ b/tags.c
@@ -0,0 +1,123 @@
+/* block-level tags for passing html blocks through the blender
+ */
+#define __WITHOUT_AMALLOC 1
+#include "cstring.h"
+#include "tags.h"
+
+STRING(struct kw) blocktags;
+
+
+/* define a html block tag
+ */
+void
+mkd_define_tag(char *id, int selfclose)
+{
+    struct kw *p = &EXPAND(blocktags);
+
+    p->id = id;
+    p->size = strlen(id);
+    p->selfclose = selfclose;
+}
+
+
+/* case insensitive string sort (for qsort() and bsearch() of block tags)
+ */
+static int
+casort(struct kw *a, struct kw *b)
+{
+    if ( a->size != b->size )
+	return a->size - b->size;
+    return strncasecmp(a->id, b->id, b->size);
+}
+
+
+/* stupid cast to make gcc shut up about the function types being
+ * passed into qsort() and bsearch()
+ */
+typedef int (*stfu)(const void*,const void*);
+
+
+/* sort the list of html block tags for later searching
+ */
+void
+mkd_sort_tags()
+{
+    qsort(T(blocktags), S(blocktags), sizeof(struct kw), (stfu)casort);
+}
+
+
+
+/* look for a token in the html block tag list
+ */
+struct kw*
+mkd_search_tags(char *pat, int len)
+{
+    struct kw key;
+    
+    key.id = pat;
+    key.size = len;
+    
+    return bsearch(&key, T(blocktags), S(blocktags), sizeof key, (stfu)casort);
+}
+
+
+static int populated = 0;
+
+
+/* load in the standard collection of html tags that markdown supports
+ */
+void
+mkd_prepare_tags()
+{
+
+#define KW(x)	mkd_define_tag(x, 0)
+#define SC(x)	mkd_define_tag(x, 1)
+
+    if ( populated ) return;
+    populated = 1;
+    
+    KW("STYLE");
+    KW("SCRIPT");
+    KW("ADDRESS");
+    KW("BDO");
+    KW("BLOCKQUOTE");
+    KW("CENTER");
+    KW("DFN");
+    KW("DIV");
+    KW("OBJECT");
+    KW("H1");
+    KW("H2");
+    KW("H3");
+    KW("H4");
+    KW("H5");
+    KW("H6");
+    KW("LISTING");
+    KW("NOBR");
+    KW("UL");
+    KW("P");
+    KW("OL");
+    KW("DL");
+    KW("PLAINTEXT");
+    KW("PRE");
+    KW("TABLE");
+    KW("WBR");
+    KW("XMP");
+    SC("HR");
+    SC("BR");
+    KW("IFRAME");
+    KW("MAP");
+
+    mkd_sort_tags();
+} /* mkd_prepare_tags */
+
+
+/* destroy the blocktags list (for shared libraries)
+ */
+void
+mkd_deallocate_tags()
+{
+    if ( S(blocktags) > 0 ) {
+	populated = 0;
+	DELETE(blocktags);
+    }
+} /* mkd_deallocate_tags */
diff --git a/tags.h b/tags.h
new file mode 100644
index 0000000..72d7647
--- /dev/null
+++ b/tags.h
@@ -0,0 +1,19 @@
+/* block-level tags for passing html blocks through the blender
+ */
+#ifndef _TAGS_D
+#define _TAGS_D
+
+struct kw {
+    char *id;
+    int  size;
+    int  selfclose;
+} ;
+
+
+struct kw* mkd_search_tags(char *, int);
+void mkd_prepare_tags();
+void mkd_deallocate_tags();
+void mkd_sort_tags();
+void mkd_define_tag(char *, int);
+
+#endif
diff --git a/tests/autolink.t b/tests/autolink.t
new file mode 100644
index 0000000..b19dcb3
--- /dev/null
+++ b/tests/autolink.t
@@ -0,0 +1,27 @@
+. tests/functions.sh
+
+title 'Reddit-style automatic links'
+rc=0
+
+try -fautolink 'single link' \
+    'http://www.pell.portland.or.us/~orc/Code/discount' \
+    '<p><a href="http://www.pell.portland.or.us/~orc/Code/discount">http://www.pell.portland.or.us/~orc/Code/discount</a></p>'
+
+try -fautolink '[!](http://a.com "http://b.com")' \
+    '[!](http://a.com "http://b.com")' \
+    '<p><a href="http://a.com" title="http://b.com">!</a></p>'
+
+try -fautolink 'link surrounded by text' \
+    'here http://it is?' \
+    '<p>here <a href="http://it">http://it</a> is?</p>'
+
+try -fautolink 'naked @' '@' '<p>@</p>'
+
+try -fautolink 'parenthesised (url)' \
+    '(http://here)' \
+    '<p>(<a href="http://here">http://here</a>)</p>'
+
+try -fautolink 'token with trailing @' 'orc@' '<p>orc@</p>'
+
+summary $0
+exit $rc
diff --git a/tests/automatic.t b/tests/automatic.t
new file mode 100644
index 0000000..c0df571
--- /dev/null
+++ b/tests/automatic.t
@@ -0,0 +1,27 @@
+. tests/functions.sh
+
+title "automatic links"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'http url' '<http://here>' '<p><a href="http://here">http://here</a></p>'
+try 'ftp url' '<ftp://here>' '<p><a href="ftp://here">ftp://here</a></p>'
+try 'http://foo/bar' '<http://foo/bar>' '<p><a href="http://foo/bar">http://foo/bar</a></p>'
+try 'http:/foo/bar'  '<http:/foo/bar>'  '<p><a href="http:/foo/bar">http:/foo/bar</a></p>'
+try 'http:foo/bar'   '<http:foo/bar>'   '<p><a href="http:foo/bar">http:foo/bar</a></p>'
+try '</foo/bar>'     '</foo/bar>'       '<p></foo/bar></p>'
+match '<orc at pell.portland.or.us>' '<orc at pell.portland.or.us>' '<a href='
+match '<orc at pell.com.>' '<orc at pell.com.>' '<a href='
+try 'invalid <orc@>' '<orc@>' '<p><orc@></p>'
+try 'invalid <@pell>' '<@pell>' '<p><@pell></p>'
+try 'invalid <orc at pell>' '<orc at pell>' '<p><orc at pell></p>'
+try 'invalid <orc at .pell>' '<orc at .pell>' '<p><orc at .pell></p>'
+try 'invalid <orc at pell.>' '<orc at pell.>' '<p><orc at pell.></p>'
+match '<mailto:orc at pell>' '<mailto:orc at pell>' '<a href='
+match '<mailto:orc at pell.com>' '<mailto:orc at pell.com>' '<a href='
+match '<mailto:orc@>' '<mailto:orc@>' '<a href='
+match '<mailto:@pell>' '<mailto:@pell>' '<a href='
+
+summary $0
+exit $rc
diff --git a/tests/backslash.t b/tests/backslash.t
new file mode 100644
index 0000000..f3cdf2e
--- /dev/null
+++ b/tests/backslash.t
@@ -0,0 +1,16 @@
+. tests/functions.sh
+
+title "backslash escapes"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'backslashes in []()' '[foo](http://\this\is\.a\test\(here\))' \
+'<p><a href="http://\this\is.a\test(here)">foo</a></p>'
+
+try -fautolink 'autolink url with trailing \' \
+    'http://a.com/\' \
+    '<p><a href="http://a.com/\">http://a.com/\</a></p>'
+
+summary $0
+exit $rc
diff --git a/tests/callbacks.t b/tests/callbacks.t
new file mode 100644
index 0000000..28217bb
--- /dev/null
+++ b/tests/callbacks.t
@@ -0,0 +1,17 @@
+. tests/functions.sh
+
+title "callbacks"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try -bZZZ 'url modification' \
+'[a](/b)' \
+'<p><a href="ZZZ/b">a</a></p>'
+
+try -EZZZ 'additional flags' \
+'[a](/b)' \
+'<p><a href="/b" ZZZ>a</a></p>'
+
+summary $0
+exit $rc
diff --git a/tests/chrome.text b/tests/chrome.text
new file mode 100644
index 0000000..d97b47b
--- /dev/null
+++ b/tests/chrome.text
@@ -0,0 +1,13 @@
+->###chrome with my markdown###<-
+
+1. `(c)`  -> `©`  (c)
+2. `(r)`  -> `®`   (r)
+3. `(tm)` -> `™` (tm)
+4. `...`  -> `…` ...
+5. `--`   -> `&emdash;` --
+6. `-`    -> `–`  - (but not if it's between-words)
+7. "fancy quoting"
+8. 'fancy quoting (#2)'
+9. don't do it unless it's a real quote.
+10. `` (`) ``
+
diff --git a/tests/code.t b/tests/code.t
new file mode 100644
index 0000000..8e8f20c
--- /dev/null
+++ b/tests/code.t
@@ -0,0 +1,35 @@
+. tests/functions.sh
+
+title "code blocks"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'format for code block html' \
+'    this is
+    code' \
+    '<pre><code>this is
+code
+</code></pre>'
+
+try 'mismatched backticks' '```tick``' '<p><code>`tick</code></p>'
+try 'mismatched backticks(2)' '``tick```' '<p>``tick```</p>'
+try 'unclosed single backtick' '`hi there' '<p>`hi there</p>'
+try 'unclosed double backtick' '``hi there' '<p>``hi there</p>'
+try 'triple backticks' '```hi there```' '<p><code>hi there</code></p>'
+try 'quadruple backticks' '````hi there````' '<p><code>hi there</code></p>'
+try 'remove space around code' '`` hi there ``' '<p><code>hi there</code></p>'
+try 'code containing backticks' '`` a```b ``' '<p><code>a```b</code></p>'
+try 'backslash before backtick' '`a\`' '<p><code>a\</code></p>'
+try '`>`' '`>`' '<p><code>></code></p>'
+try '`` ` ``' '`` ` ``' '<p><code>`</code></p>'
+try '````` ``` `' '````` ``` `' '<p><code>``</code> `</p>'
+try '````` ` ```' '````` ` ```' '<p><code>`` `</code></p>'
+try 'backslashes in code(1)' '    printf "%s: \n", $1;' \
+'<pre><code>printf "%s: \n", $1;
+</code></pre>'
+try 'backslashes in code(2)' '`printf "%s: \n", $1;`' \
+'<p><code>printf "%s: \n", $1;</code></p>'
+
+summary $0
+exit $rc
diff --git a/tests/compat.t b/tests/compat.t
new file mode 100644
index 0000000..af39e49
--- /dev/null
+++ b/tests/compat.t
@@ -0,0 +1,29 @@
+. tests/functions.sh
+
+title "markdown 1.0 compatibility"
+
+rc=0
+MARKDOWN_FLAGS=
+
+LINKY='[this] is a test
+
+[this]: /this'
+
+try 'implicit reference links' "$LINKY" '<p><a href="/this">this</a> is a test</p>'
+try -f1.0 'implicit reference links (-f1.0)' "$LINKY" '<p>[this] is a test</p>'
+
+WSP=' '
+WHITESPACE="
+    white space$WSP
+    and more"
+
+try 'trailing whitespace' "$WHITESPACE" '<pre><code>white space ''
+and more
+</code></pre>'
+
+try -f1.0 'trailing whitespace (-f1.0)' "$WHITESPACE" '<pre><code>white space''
+and more
+</code></pre>'
+
+summary $0
+exit $rc
diff --git a/tests/crash.t b/tests/crash.t
new file mode 100644
index 0000000..b958c4a
--- /dev/null
+++ b/tests/crash.t
@@ -0,0 +1,31 @@
+. tests/functions.sh
+
+title "crashes"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'zero-length input' '' ''
+
+try 'hanging quote in list' \
+' * > this should not die
+
+no.' \
+'<ul>
+<li><blockquote><p>this should not die</p></blockquote></li>
+</ul>
+
+
+<p>no.</p>'
+
+try 'dangling list item' ' - ' \
+'<ul>
+<li></li>
+</ul>'
+
+try -bHOHO 'empty []() with baseurl' '[]()' '<p><a href=""></a></p>'
+try 'unclosed html block' '<table></table' '<p><table></table</p>'
+try 'unclosed style block' '<style>' '<p><style></p>'
+
+summary $0
+exit $rc
diff --git a/tests/div.t b/tests/div.t
new file mode 100644
index 0000000..17ca298
--- /dev/null
+++ b/tests/div.t
@@ -0,0 +1,45 @@
+. tests/functions.sh
+
+title "%div% blocks"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'simple >%div% block' \
+'>%this%
+this this' \
+'<div class="this"><p>this this</p></div>'
+
+try 'two >%div% blocks in a row' \
+'>%this%
+this this
+
+>%that%
+that that' \
+'<div class="this"><p>this this</p></div>
+
+<div class="that"><p>that that</p></div>'
+
+try '>%class:div%' \
+'>%class:this%
+this this' \
+'<div class="this"><p>this this</p></div>'
+
+try '>%id:div%' \
+'>%id:this%
+this this' \
+'<div id="this"><p>this this</p></div>'
+
+try 'nested >%div%' \
+'>%this%
+>>%that%
+>>that
+
+>%more%
+more' \
+'<div class="this"><div class="that"><p>that</p></div></div>
+
+<div class="more"><p>more</p></div>'
+
+summary $0
+exit $rc
diff --git a/tests/dl.t b/tests/dl.t
new file mode 100644
index 0000000..99d1fe4
--- /dev/null
+++ b/tests/dl.t
@@ -0,0 +1,96 @@
+. tests/functions.sh
+
+title "definition lists"
+
+eval `./markdown -V | tr ' ' '\n' | grep '^DL='`
+
+DL=${DL:-BOTH}
+
+rc=0
+MARKDOWN_FLAGS=
+
+SRC='
+=this=
+    is an ugly
+=test=
+    eh?'
+
+RSLT='<dl>
+<dt>this</dt>
+<dd>is an ugly</dd>
+<dt>test</dt>
+<dd>eh?</dd>
+</dl>'
+
+if [ "$DL" = "DISCOUNT" -o "$DL" = "BOTH" ]; then
+    try -fdefinitionlist '=tag= generates definition lists' "$SRC" "$RSLT"
+
+	try 'one item with two =tags=' \
+	    '=this=
+=is=
+    A test, eh?' \
+	    '<dl>
+<dt>this</dt>
+<dt>is</dt>
+<dd>A test, eh?</dd>
+</dl>'
+
+
+	try -fnodefinitionlist '=tag= does nothing' "$SRC" \
+	    '<p>=this=</p>
+
+<pre><code>is an ugly
+</code></pre>
+
+<p>=test=</p>
+
+<pre><code>eh?
+</code></pre>'
+fi
+
+if [ "$DL" = "EXTRA" -o "$DL" = "BOTH" ]; then
+    try 'markdown extra-style definition lists' \
+'foo
+: bar' \
+'<dl>
+<dt>foo</dt>
+<dd>bar</dd>
+</dl>'
+
+    try '... with two <dt>s in a row' \
+'foo
+bar
+: baz' \
+'<dl>
+<dt>foo</dt>
+<dt>bar</dt>
+<dd>baz</dd>
+</dl>'
+
+    try '... with two <dd>s in a row' \
+'foo
+: bar
+: baz' \
+'<dl>
+<dt>foo</dt>
+<dd>bar</dd>
+<dd>baz</dd>
+</dl>'
+
+    try '... with blanks between list items' \
+'foo
+: bar
+
+zip
+: zap' \
+'<dl>
+<dt>foo</dt>
+<dd>bar</dd>
+<dt>zip</dt>
+<dd>zap</dd>
+</dl>'
+
+fi
+
+summary $0
+exit $rc
diff --git a/tests/embedlinks.text b/tests/embedlinks.text
new file mode 100644
index 0000000..a79ef1b
--- /dev/null
+++ b/tests/embedlinks.text
@@ -0,0 +1,9 @@
+* [![an image](http://dustmite.org/mite.jpg =50x50)] (http://dustmite.org)
+* [[an embedded link](http://wontwork.org)](http://willwork.org)
+* [![dustmite][]] (http:/dustmite.org)
+* ![dustmite][]
+* ![dustmite][dustmite]
+* [<a href="http://cheating.us">cheat me</a>](http://I.am.cheating)
+
+[dustmite]: http://dustmite.org/mite.jpg =25x25 "here I am!"
+
diff --git a/tests/emphasis.t b/tests/emphasis.t
new file mode 100644
index 0000000..622f55f
--- /dev/null
+++ b/tests/emphasis.t
@@ -0,0 +1,19 @@
+. tests/functions.sh
+
+title "emphasis"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try '*hi* -> <em>hi</em>' '*hi*' '<p><em>hi</em></p>'
+try '* -> *' 'A * A' '<p>A * A</p>'
+try -fstrict '***A**B*' '***A**B*' '<p><em><strong>A</strong>B</em></p>'
+try -fstrict '***A*B**' '***A*B**' '<p><strong><em>A</em>B</strong></p>'
+try -fstrict '**A*B***' '**A*B***' '<p><strong>A<em>B</em></strong></p>'
+try -fstrict '*A**B***' '*A**B***' '<p><em>A<strong>B</strong></em></p>'
+
+try -frelax '_A_B with -frelax' '_A_B' '<p>_A_B</p>'
+try -fstrict '_A_B with -fstrict' '_A_B' '<p><em>A</em>B</p>'
+
+summary $0
+exit $rc
diff --git a/tests/flow.t b/tests/flow.t
new file mode 100644
index 0000000..44cf53a
--- /dev/null
+++ b/tests/flow.t
@@ -0,0 +1,33 @@
+. tests/functions.sh
+
+title "paragraph flow"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'header followed by paragraph' \
+    '###Hello, sailor###
+And how are you today?' \
+    '<h3>Hello, sailor</h3>
+
+<p>And how are you today?</p>'
+
+try 'two lists punctuated with a HR' \
+    '* A
+* * *
+* B
+* C' \
+    '<ul>
+<li>A</li>
+</ul>
+
+
+<hr />
+
+<ul>
+<li>B</li>
+<li>C</li>
+</ul>'
+
+summary $0
+exit $rc
diff --git a/tests/footnotes.t b/tests/footnotes.t
new file mode 100644
index 0000000..2c923b0
--- /dev/null
+++ b/tests/footnotes.t
@@ -0,0 +1,16 @@
+. tests/functions.sh
+
+title "footnotes"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'a line with multiple []s' '[a][] [b][]:' '<p>[a][] [b][]:</p>'
+try 'a valid footnote' \
+    '[alink][]
+
+[alink]: link_me' \
+    '<p><a href="link_me">alink</a></p>'
+
+summary $0
+exit $rc
diff --git a/tests/functions.sh b/tests/functions.sh
new file mode 100644
index 0000000..d280d65
--- /dev/null
+++ b/tests/functions.sh
@@ -0,0 +1,77 @@
+__tests=0
+__passed=0
+__failed=0
+__title=
+
+title() {
+    __title="$*"
+    if [ "$VERBOSE" ]; then
+	./echo "$__title"
+    else
+	./echo -n "$__title" \
+'.................................................................' | ./cols 54
+    fi
+}
+
+
+summary() {
+    if [ -z "$VERBOSE" ]; then
+	if [ $__failed -eq 0 ]; then
+	    ./echo " OK"
+	else
+	    ./echo
+	    ./echo "$1: $__tests tests; $__failed failed/$__passed passed"
+	    ./echo
+	fi
+    fi
+}
+
+
+try() {
+    unset FLAGS
+    case "$1" in
+    -*) FLAGS=$1
+	shift ;;
+    esac
+
+    testcase=`./echo -n "  $1" '........................................................' | ./cols 50`
+    __tests=`expr $__tests + 1`
+
+
+    test "$VERBOSE" && ./echo -n "$testcase"
+
+    case "$2" in
+    -t*) Q=`./markdown $FLAGS "$2"` ;;
+    *)   Q=`./echo "$2" | ./markdown $FLAGS` ;;
+    esac
+
+    if [ "$3" = "$Q" ]; then
+	__passed=`expr $__passed + 1`
+	test $VERBOSE && ./echo " ok"
+    else
+	__failed=`expr $__failed + 1`
+	if [ -z  "$VERBOSE" ]; then
+	    ./echo
+	    ./echo "$1"
+	fi
+	./echo "wanted: $3"
+	./echo "got   : $Q"
+	rc=1
+    fi
+}
+
+match() {
+    testcase=`./echo -n "  $1" '........................................................' | ./cols 50`
+
+    test $VERBOSE && ./echo -n "$testcase"
+
+    if ./echo "$2" | ./markdown | grep "$3" >/dev/null; then
+	test $VERBOSE && ./echo " ok"
+    else
+	if [ -z "$VERBOSE" ]; then
+	    ./echo
+	    ./echo "$testcase"
+	fi
+	rc=1
+    fi
+}
diff --git a/tests/header.t b/tests/header.t
new file mode 100644
index 0000000..d72a516
--- /dev/null
+++ b/tests/header.t
@@ -0,0 +1,26 @@
+. tests/functions.sh
+
+title "headers"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'single #' '#' '<p>#</p>'
+try 'empty ETX' '##' '<h1>#</h1>'
+try 'single-char ETX (##W)' '##W' '<h2>W</h2>'
+try 'single-char ETX (##W )' '##W  ' '<h2>W</h2>'
+try 'single-char ETX (## W)' '## W' '<h2>W</h2>'
+try 'single-char ETX (## W )' '## W ' '<h2>W</h2>'
+try 'single-char ETX (##W##)' '##W##' '<h2>W</h2>'
+try 'single-char ETX (##W ##)' '##W ##' '<h2>W</h2>'
+try 'single-char ETX (## W##)' '## W##' '<h2>W</h2>'
+try 'single-char ETX (## W ##)' '## W ##' '<h2>W</h2>'
+
+try 'multiple-char ETX (##Hello##)' '##Hello##' '<h2>Hello</h2>'
+
+try 'SETEXT with trailing whitespace' \
+'hello
+=====  ' '<h1>hello</h1>'
+
+summary $0
+exit $rc
diff --git a/tests/html.t b/tests/html.t
new file mode 100644
index 0000000..6d98226
--- /dev/null
+++ b/tests/html.t
@@ -0,0 +1,141 @@
+. tests/functions.sh
+
+title "html blocks"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'self-closing block tags (hr)' \
+    '<hr>
+
+text' \
+    '<hr>
+
+
+<p>text</p>'
+
+try 'self-closing block tags (hr/)' \
+    '<hr/>
+
+text' \
+    '<hr/>
+
+
+<p>text</p>'
+
+try 'self-closing block tags (br)' \
+    '<br>
+
+text' \
+    '<br>
+
+
+<p>text</p>'
+
+try 'html comments' \
+    '<!--
+**hi**
+-->' \
+    '<!--
+**hi**
+-->'
+    
+try 'no smartypants inside tags (#1)' \
+    '<img src="linky">' \
+    '<p><img src="linky"></p>'
+
+try 'no smartypants inside tags (#2)' \
+    '<img src="linky" alt=":)" />' \
+    '<p><img src="linky" alt=":)" /></p>'
+
+try -fnohtml 'block html with -fnohtml' '<b>hi!</b>' '<p><b>hi!</b></p>'
+try -fnohtml 'malformed tag injection' '<x <script>' '<p><x <script></p>'
+try -fhtml 'allow html with -fhtml' '<b>hi!</b>' '<p><b>hi!</b></p>'
+
+
+# check that nested raw html blocks terminate properly.
+#
+BLOCK1SRC='Markdown works fine *here*.
+
+*And* here.
+
+<div><pre>
+</pre></div>
+
+Markdown here is *not* parsed by RDiscount.
+
+Nor in *this* paragraph, and there are no paragraph breaks.'
+
+BLOCK1OUT='<p>Markdown works fine <em>here</em>.</p>
+
+<p><em>And</em> here.</p>
+
+<div><pre>
+</pre></div>
+
+
+<p>Markdown here is <em>not</em> parsed by RDiscount.</p>
+
+<p>Nor in <em>this</em> paragraph, and there are no paragraph breaks.</p>'
+
+try 'nested html blocks (1)' "$BLOCK1SRC" "$BLOCK1OUT"
+
+try 'nested html blocks (2)' \
+    '<div>This is inside a html block
+<div>This is, too</div>and
+so is this</div>' \
+    '<div>This is inside a html block
+<div>This is, too</div>and
+so is this</div>'
+
+try 'unfinished tags' '<foo bar' '<p><foo bar</p>'
+
+
+try 'comment with trailing text' '<!-- this is -->a test' \
+'<!-- this is -->
+
+
+<p>a test</p>'
+
+try 'block with trailing text' '<p>this is</p>a test' \
+'<p>this is</p>
+
+
+<p>a test</p>'
+
+
+COMMENTS='<!-- 1. -->line 1
+
+<!-- 2. -->line 2'
+
+try 'two comments' "$COMMENTS" \
+'<!-- 1. -->
+
+
+<p>line 1</p>
+
+<!-- 2. -->
+
+
+<p>line 2</p>'
+
+COMMENTS='<!-- 1. -->line 1
+<!-- 2. -->line 2'
+
+try 'two adjacent comments' "$COMMENTS" \
+'<!-- 1. -->
+
+
+<p>line 1</p>
+
+<!-- 2. -->
+
+
+<p>line 2</p>'
+
+try 'comment, no white space' '<!--foo-->' '<!--foo-->'
+
+try 'unclosed block' '<p>here we go!' '<p><p>here we go!</p>'
+
+summary $0
+exit $rc
diff --git a/tests/html5.t b/tests/html5.t
new file mode 100644
index 0000000..0056f01
--- /dev/null
+++ b/tests/html5.t
@@ -0,0 +1,17 @@
+. tests/functions.sh
+
+title "html5 blocks (mkd_with_html5_tags)"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try -5 'html5 block elements enabled' \
+       '<aside>html5 does not suck</aside>' \
+       '<aside>html5 does not suck</aside>'
+
+try    'html5 block elements disabled' \
+       '<aside>html5 sucks</aside>' \
+       '<p><aside>html5 sucks</aside></p>'
+
+summary $0
+exit $rc
diff --git a/tests/links.text b/tests/links.text
new file mode 100644
index 0000000..ca40439
--- /dev/null
+++ b/tests/links.text
@@ -0,0 +1,14 @@
+ 1. <http://automatic>
+ 2. [automatic] (http://automatic "automatic link")
+ 3. [automatic](http://automatic "automatic link")
+ 4. [automatic](http://automatic)
+ 5. [automatic] (http://automatic)
+ 6. [automatic] []
+ 7. [automatic][]
+ 8. [my][automatic]
+ 9. [my] [automatic]
+
+ [automatic]: http://automatic "footnote"
+
+
+ [automatic] [
diff --git a/tests/linkylinky.t b/tests/linkylinky.t
new file mode 100644
index 0000000..a837e10
--- /dev/null
+++ b/tests/linkylinky.t
@@ -0,0 +1,130 @@
+. tests/functions.sh
+
+title "embedded links"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'url contains &' '[hehehe](u&rl)' '<p><a href="u&rl">hehehe</a></p>'
+try 'url contains +' '[hehehe](u+rl)' '<p><a href="u+rl">hehehe</a></p>'
+try 'url contains "' '[hehehe](u"rl)' '<p><a href="u%22rl">hehehe</a></p>'
+try 'url contains <' '[hehehe](u<rl)' '<p><a href="u<rl">hehehe</a></p>'
+try 'url contains whitespace' '[ha](r u)' '<p><a href="r%20u">ha</a></p>'
+try 'label contains escaped []s' '[a\[b\]c](d)' '<p><a href="d">a[b]c</a></p>'
+
+try '<label> w/o title' '[hello](<sailor>)' '<p><a href="sailor">hello</a></p>'
+try '<label> with title' '[hello](<sailor> "boy")' '<p><a href="sailor" title="boy">hello</a></p>'
+try '<label> with whitespace' '[hello](  <sailor> )' '<p><a href="sailor">hello</a></p>'
+
+try 'url contains whitespace & title' \
+    '[hehehe](r u "there")' \
+    '<p><a href="r%20u" title="there">hehehe</a></p>'
+
+try 'url contains escaped )' \
+    '[hehehe](u\))' \
+    '<p><a href="u)">hehehe</a></p>'
+
+try 'image label contains <' \
+    '![he<he<he](url)' \
+    '<p><img src="url" alt="he<he<he" /></p>'
+
+try 'image label contains >' \
+    '![he>he>he](url)' \
+    '<p><img src="url" alt="he>he>he" /></p>'
+
+try 'sloppy context link' \
+    '[heh]( url "how about it?" )' \
+    '<p><a href="url" title="how about it?">heh</a></p>'
+
+try 'footnote urls formed properly' \
+    '[hehehe]: hohoho "ha ha"
+
+[hehehe][]' \
+    '<p><a href="hohoho" title="ha ha">hehehe</a></p>'
+
+try 'linky-like []s work' \
+    '[foo]' \
+    '<p>[foo]</p>'
+
+try 'pseudo-protocol "id:"'\
+    '[foo](id:bar)' \
+    '<p><span id="bar">foo</span></p>'
+
+try 'pseudo-protocol "class:"' \
+    '[foo](class:bar)' \
+    '<p><span class="bar">foo</span></p>'
+
+try 'pseudo-protocol "abbr:"'\
+    '[foo](abbr:bar)' \
+    '<p><abbr title="bar">foo</abbr></p>'
+
+try 'nested [][]s' \
+    '[[z](y)](x)' \
+    '<p><a href="x">[z](y)</a></p>'
+
+try 'empty [][] tags' \
+    '[![][1]][2]
+
+[1]: image1
+[2]: image2' \
+    '<p><a href="image2"><img src="image1" alt="" /></a></p>'
+
+try 'footnote cuddled up to text' \
+'foo
+[bar]:bar' \
+    '<p>foo</p>'
+
+try 'mid-paragraph footnote' \
+'talk talk talk talk
+[bar]: bar
+talk talk talk talk' \
+'<p>talk talk talk talk
+talk talk talk talk</p>'
+
+try 'mid-blockquote footnote' \
+'>blockquote!
+[footnote]: here!
+>blockquote!' \
+'<blockquote><p>blockquote!
+blockquote!</p></blockquote>'
+
+try 'end-blockquote footnote' \
+'>blockquote!
+>blockquote!
+[footnote]: here!' \
+'<blockquote><p>blockquote!
+blockquote!</p></blockquote>'
+
+try 'start-blockquote footnote' \
+'[footnote]: here!
+>blockquote!
+>blockquote!' \
+'<blockquote><p>blockquote!
+blockquote!</p></blockquote>'
+
+try '[text] (text) not a link' \
+'[test] (me)' \
+'<p>[test] (me)</p>'
+
+try '[test] [this] w/ one space between' \
+'[test] [this]
+[test]: yay!
+[this]: nay!' \
+'<p><a href="nay!">test</a></p>'
+
+try '[test] [this] w/ two spaces between' \
+'[test]  [this]
+[test]: yay!
+[this]: nay!' \
+'<p><a href="yay!">test</a>  <a href="nay!">this</a></p>'
+
+try -f1.0 'link with <> (-f1.0)' \
+	  '[this](<is a (test)>)' \
+	  '<p><a href="is%20a%20(test">this</a>>)</p>'
+
+try       'link with <>' \
+	  '[this](<is a (test)>)' \
+	  '<p><a href="is%20a%20(test)">this</a></p>'
+
+summary $0
+exit $rc
diff --git a/tests/linkypix.t b/tests/linkypix.t
new file mode 100644
index 0000000..5c7572a
--- /dev/null
+++ b/tests/linkypix.t
@@ -0,0 +1,21 @@
+. tests/functions.sh
+
+title "embedded images"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'image with size extension' \
+    '![picture](pic =200x200)' \
+    '<p><img src="pic" height="200" width="200" alt="picture" /></p>'
+
+try 'image with height' \
+    '![picture](pic =x200)' \
+    '<p><img src="pic" height="200" alt="picture" /></p>'
+
+try 'image with width' \
+    '![picture](pic =200x)' \
+    '<p><img src="pic" width="200" alt="picture" /></p>'
+
+summary $0
+exit $rc
diff --git a/tests/list.t b/tests/list.t
new file mode 100644
index 0000000..550700d
--- /dev/null
+++ b/tests/list.t
@@ -0,0 +1,155 @@
+. tests/functions.sh
+title "lists"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'two separated items' \
+    ' * A
+
+* B' \
+    '<ul>
+<li><p>A</p></li>
+<li><p>B</p></li>
+</ul>'
+
+try 'two adjacent items' \
+    ' * A
+ * B' \
+    '<ul>
+<li>A</li>
+<li>B</li>
+</ul>'
+
+
+try 'two adjacent items, then space' \
+    ' * A
+* B
+
+space, the final frontier' \
+    '<ul>
+<li>A</li>
+<li>B</li>
+</ul>
+
+
+<p>space, the final frontier</p>'
+
+try 'nested lists (1)' \
+    ' *   1. Sub (list)
+     2. Two (items)
+     3. Here' \
+    '<ul>
+<li><ol>
+<li>Sub (list)</li>
+<li>Two (items)</li>
+<li>Here</li>
+</ol>
+</li>
+</ul>'
+
+try 'nested lists (2)' \
+    ' * A (list)
+
+     1. Sub (list)
+     2. Two (items)
+     3. Here
+
+     Here
+ * B (list)' \
+    '<ul>
+<li><p>A (list)</p>
+
+<ol>
+<li> Sub (list)</li>
+<li> Two (items)</li>
+<li> Here</li>
+</ol>
+
+
+<p>  Here</p></li>
+<li>B (list)</li>
+</ul>'
+
+try 'list inside blockquote' \
+    '>A (list)
+>
+>1. Sub (list)
+>2. Two (items)
+>3. Here' \
+    '<blockquote><p>A (list)</p>
+
+<ol>
+<li>Sub (list)</li>
+<li>Two (items)</li>
+<li>Here</li>
+</ol>
+</blockquote>'
+    
+try 'blockquote inside list' \
+    ' *  A (list)
+   
+    > quote
+    > me
+
+    dont quote me' \
+    '<ul>
+<li><p>A (list)</p>
+
+<blockquote><p>quote
+me</p></blockquote>
+
+<p>dont quote me</p></li>
+</ul>'
+
+try 'empty list' \
+'
+- 
+
+- 
+' \
+'<ul>
+<li></li>
+<li></li>
+</ul>'
+
+
+try 'blockquote inside a list' \
+'   * This is a list item.
+
+      > This is a quote insde a list item. ' \
+'<ul>
+<li><p> This is a list item.</p>
+
+<blockquote><p>This is a quote insde a list item.</p></blockquote></li>
+</ul>'
+
+try 'dl followed by non-dl' \
+    '=a=
+    test
+2. here' \
+'<dl>
+<dt>a</dt>
+<dd>test</dd>
+</dl>
+
+<ol>
+<li>here</li>
+</ol>'
+
+try 'non-dl followed by dl' \
+    '1. hello
+=sailor=
+    hi!' \
+'<ol>
+<li>hello</li>
+</ol>
+
+
+<dl>
+<dt>sailor</dt>
+<dd>hi!</dd>
+</dl>'
+
+summary $0
+exit $rc
diff --git a/tests/list3deep.t b/tests/list3deep.t
new file mode 100644
index 0000000..5909055
--- /dev/null
+++ b/tests/list3deep.t
@@ -0,0 +1,38 @@
+. tests/functions.sh
+title "deeply nested lists"
+
+rc=0
+MARKDOWN_FLAGS=
+
+LIST='
+ *  top-level list ( list 1)
+     +  second-level list (list 2)
+        * first item third-level list (list 3)
+     +  * second item, third-level list, first item. (list 4)
+        * second item, third-level list, second item.
+ *  top-level list again.'
+
+RSLT='<ul>
+<li>top-level list ( list 1)
+
+<ul>
+<li>second-level list (list 2)
+
+<ul>
+<li>first item third-level list (list 3)</li>
+</ul>
+</li>
+<li><ul>
+<li>second item, third-level list, first item. (list 4)</li>
+<li>second item, third-level list, second item.</li>
+</ul>
+</li>
+</ul>
+</li>
+<li>top-level list again.</li>
+</ul>'
+
+try 'thrice-nested lists' "$LIST" "$RSLT"
+
+summary $0
+exit $rc
diff --git a/tests/misc.t b/tests/misc.t
new file mode 100644
index 0000000..969ca3b
--- /dev/null
+++ b/tests/misc.t
@@ -0,0 +1,12 @@
+. tests/functions.sh
+
+title "misc"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'single paragraph' 'AAA' '<p>AAA</p>'
+try '< -> <' '<' '<p><</p>'
+
+summary $0
+exit $rc
diff --git a/tests/pandoc.t b/tests/pandoc.t
new file mode 100644
index 0000000..5d312c3
--- /dev/null
+++ b/tests/pandoc.t
@@ -0,0 +1,44 @@
+. tests/functions.sh
+
+title "pandoc headers"
+
+rc=0
+MARKDOWN_FLAGS=
+
+HEADER='% title
+% author(s)
+% date'
+
+
+try 'valid header' "$HEADER" ''
+try -F0x00010000 'valid header with -F0x00010000' "$HEADER" '<p>% title
+% author(s)
+% date</p>'
+
+try 'invalid header' \
+	'% title
+% author(s)
+a pony!' \
+	'<p>% title
+% author(s)
+a pony!</p>'
+
+try 'offset header' \
+	'
+% title
+% author(s)
+% date' \
+	'<p>% title
+% author(s)
+% date</p>'
+
+try 'indented header' \
+	'  % title
+% author(s)
+% date' \
+	'<p>  % title
+% author(s)
+% date</p>'
+
+summary $0
+exit $rc
diff --git a/tests/para.t b/tests/para.t
new file mode 100644
index 0000000..29445e5
--- /dev/null
+++ b/tests/para.t
@@ -0,0 +1,19 @@
+. tests/functions.sh
+
+title "paragraph blocking"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'paragraph followed by code' \
+    'a
+    b' \
+    '<p>a</p>
+
+<pre><code>b
+</code></pre>'
+
+try 'single-line paragraph' 'a' '<p>a</p>'
+
+summary $0
+exit $rc
diff --git a/tests/paranoia.t b/tests/paranoia.t
new file mode 100644
index 0000000..a12147b
--- /dev/null
+++ b/tests/paranoia.t
@@ -0,0 +1,12 @@
+. tests/functions.sh
+
+title "paranoia"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try -fsafelink 'bogus url (-fsafelink)' '[test](bad:protocol)' '<p>[test](bad:protocol)</p>'
+try -fnosafelink 'bogus url (-fnosafelink)' '[test](bad:protocol)' '<p><a href="bad:protocol">test</a></p>'
+
+summary $0
+exit $rc
diff --git a/tests/peculiarities.t b/tests/peculiarities.t
new file mode 100644
index 0000000..f97597f
--- /dev/null
+++ b/tests/peculiarities.t
@@ -0,0 +1,77 @@
+. tests/functions.sh
+
+title "markup peculiarities"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'list followed by header .......... ' \
+    "
+- AAA
+- BBB
+-" \
+    '<ul>
+<li>AAA
+
+<h2>– BBB</h2></li>
+</ul>'
+
+try 'ul with mixed item prefixes' \
+    '
+-  A
+1. B' \
+    '<ul>
+<li>A</li>
+<li>B</li>
+</ul>'
+
+try 'ol with mixed item prefixes' \
+    '
+1. A
+-  B
+' \
+    '<ol>
+<li>A</li>
+<li>B</li>
+</ol>'
+
+try 'nested lists and a header' \
+    '- A list item
+That goes over multiple lines
+
+     and paragraphs
+
+- Another list item
+
+    + with a
+    + sublist
+
+## AND THEN A HEADER' \
+'<ul>
+<li><p>A list item
+That goes over multiple lines</p>
+
+<p>   and paragraphs</p></li>
+<li><p>Another list item</p>
+
+<ul>
+<li>with a</li>
+<li>sublist</li>
+</ul>
+</li>
+</ul>
+
+
+<h2>AND THEN A HEADER</h2>'
+
+try 'forcing a <br/>' 'this  
+is' '<p>this<br/>
+is</p>'
+
+try 'trimming single spaces' 'this ' '<p>this</p>'
+try -fnohtml 'markdown <br/> with -fnohtml' 'foo  
+is'  '<p>foo<br/>
+is</p>'
+
+summary $0
+exit $rc
diff --git a/tests/pseudo.t b/tests/pseudo.t
new file mode 100644
index 0000000..82045a9
--- /dev/null
+++ b/tests/pseudo.t
@@ -0,0 +1,20 @@
+. tests/functions.sh
+
+title "pseudo-protocols"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try '[](id:) links' '[foo](id:bar)' '<p><span id="bar">foo</span></p>'
+try -fnoext  '[](id:) links with -fnoext' '[foo](id:bar)' '<p>[foo](id:bar)</p>'
+try '[](class:) links' '[foo](class:bar)' '<p><span class="bar">foo</span></p>'
+try -fnoext '[](class:) links with -fnoext' '[foo](class:bar)' '<p>[foo](class:bar)</p>'
+try '[](lang:) links' '[foo](lang:en)' '<p><span lang="en">foo</span></p>'
+try -fnoext '[](lang:) links with -fnoext' '[foo](lang:en)' '<p>[foo](lang:en)</p>'
+try '[](raw:) links' '[foo](raw:bar)' '<p>bar</p>'
+try -fnoext '[](raw:) links with -fnoext' '[foo](raw:bar)' '<p>[foo](raw:bar)</p>'
+
+# try '[](id:) wrapping a href' '[[foo](bar)](id:baz)' '<p><span id="baz">foo</span></p>'
+
+summary $0
+exit $rc
diff --git a/tests/reddit.t b/tests/reddit.t
new file mode 100644
index 0000000..268cbcf
--- /dev/null
+++ b/tests/reddit.t
@@ -0,0 +1,27 @@
+. tests/functions.sh
+
+title "bugs & misfeatures found during the Reddit rollout"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'smiley faces?' '[8-9] <]:-( x ---> [4]' \
+		    '<p>[8-9] <]:–( x —–> [4]</p>'
+
+try 'really long ETX headers' \
+    '#####################################################hi' \
+    '<h6>###############################################hi</h6>'
+
+try 'unescaping "  " inside `code`' \
+'`foo  
+bar`' \
+'<p><code>foo  
+bar</code></p>'
+
+try 'unescaping "  " inside []()' \
+'[foo](bar  
+bar)' \
+'<p><a href="bar  %0Abar">foo</a></p>'
+
+summary $0
+exit $rc
diff --git a/tests/reparse.t b/tests/reparse.t
new file mode 100644
index 0000000..93e11b5
--- /dev/null
+++ b/tests/reparse.t
@@ -0,0 +1,14 @@
+. tests/functions.sh
+
+title "footnotes inside reparse sections"
+
+rc=0
+
+try 'footnote inside [] section' \
+    '[![foo][]](bar)
+
+[foo]: bar2' \
+    '<p><a href="bar"><img src="bar2" alt="foo" /></a></p>'
+
+summary $0
+exit $rc
diff --git a/tests/schiraldi.t b/tests/schiraldi.t
new file mode 100644
index 0000000..a576fdd
--- /dev/null
+++ b/tests/schiraldi.t
@@ -0,0 +1,91 @@
+. tests/functions.sh
+
+title "Bugs & misfeatures reported by Mike Schiraldi"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try -fnohtml 'breaks with -fnohtml' 'foo  
+bar' '<p>foo<br/>
+bar</p>'
+
+try 'links with trailing \)' \
+    '[foo](http://en.wikipedia.org/wiki/Link_(film\))' \
+    '<p><a href="http://en.wikipedia.org/wiki/Link_(film)">foo</a></p>'
+
+try -fautolink '(url) with -fautolink' \
+    '(http://tsfr.org)' \
+    '<p>(<a href="http://tsfr.org">http://tsfr.org</a>)</p>'
+
+try 'single #' \
+    '#' \
+    '<p>#</p>'
+
+try -frelax '* processing with -frelax' \
+    '2*4 = 8 * 1 = 2**3' \
+    '<p>2*4 = 8 * 1 = 2**3</p>'
+
+try -fnopants '[]() with a single quote mark' \
+    '[Poe'"'"'s law](http://rationalwiki.com/wiki/Poe'"'"'s_Law)' \
+    '<p><a href="http://rationalwiki.com/wiki/Poe'"'"'s_Law">Poe'"'"'s law</a></p>'
+
+try -fautolink 'autolink url with escaped spaces' \
+    'http://\(here\ I\ am\)' \
+    '<p><a href="http://(here%20I%20am)">http://(here I am)</a></p>'
+
+try -fautolink 'autolink café_racer' \
+    'http://en.wikipedia.org/wiki/café_racer' \
+    '<p><a href="http://en.wikipedia.org/wiki/caf%C3%A9_racer">http://en.wikipedia.org/wiki/caf%C3%A9_racer</a></p>'
+
+try -fautolink 'autolink url with arguments' \
+    'http://foo.bar?a&b=c' \
+    '<p><a href="http://foo.bar?a&b=c">http://foo.bar?a&b=c</a></p>'
+
+try '\( escapes in []()' \
+    '[foo](http://a.com/\(foo\))' \
+    '<p><a href="http://a.com/(foo)">foo</a></p>'
+
+try -fautolink 'autolink url with escaped ()' \
+    'http://a.com/\(foo\)' \
+    '<p><a href="http://a.com/(foo)">http://a.com/(foo)</a></p>'
+
+try -fautolink 'autolink url with escaped \' \
+    'http://a.com/\\\)' \
+    '<p><a href="http://a.com/\)">http://a.com/\)</a></p>'
+
+try -fautolink 'autolink url with -' \
+    'http://experts-exchange.com' \
+    '<p><a href="http://experts-exchange.com">http://experts-exchange.com</a></p>'
+
+try -fautolink 'autolink url with +' \
+    'http://www67.wolframalpha.com/input/?i=how+old+was+jfk+jr+when+jfk+died' \
+    '<p><a href="http://www67.wolframalpha.com/input/?i=how+old+was+jfk+jr+when+jfk+died">http://www67.wolframalpha.com/input/?i=how+old+was+jfk+jr+when+jfk+died</a></p>'
+
+try -fautolink 'autolink url with &' \
+    'http://foo.bar?a&b=c' \
+    '<p><a href="http://foo.bar?a&b=c">http://foo.bar?a&b=c</a></p>'
+
+    
+try -fautolink 'autolink url with ,' \
+    'http://www.spiegel.de/international/europe/0,1518,626171,00.html' \
+    '<p><a href="http://www.spiegel.de/international/europe/0,1518,626171,00.html">http://www.spiegel.de/international/europe/0,1518,626171,00.html</a></p>'
+
+try -fautolink 'autolink url with : & ;' \
+    'http://www.biblegateway.com/passage/?search=Matthew%205:29-30;&version=31;' \
+    '<p><a href="http://www.biblegateway.com/passage/?search=Matthew%205:29-30;&version=31;">http://www.biblegateway.com/passage/?search=Matthew%205:29-30;&version=31;</a></p>'
+
+Q="'"
+try -fautolink 'security hole with \" in []()' \
+'[XSS](/ "\"=\"\"onmouseover='$Q'alert(String.fromCharCode(88,83,83))'$Q'")' \
+'<p><a href="/" title="\"=\"\"onmouseover='$Q'alert(String.fromCharCode(88,83,83))'$Q'">XSS</a></p>'
+
+try -fautolink 'autolink and prefix fragment' \
+'xxxxxxx http://x.com/
+
+xxx xxxxht' \
+'<p>xxxxxxx <a href="http://x.com/">http://x.com/</a></p>
+
+<p>xxx xxxxht</p>'
+
+summary $0
+exit $rc
diff --git a/tests/smarty.t b/tests/smarty.t
new file mode 100644
index 0000000..8c9ccc5
--- /dev/null
+++ b/tests/smarty.t
@@ -0,0 +1,24 @@
+. tests/functions.sh
+
+title "smarty pants"
+
+rc=0
+MARKDOWN_FLAGS=0x0; export MARKDOWN_FLAGS
+
+try '(c) -> ©' '(c)' '<p>©</p>'
+try '(r) -> ®' '(r)' '<p>®</p>'
+try '(tm) -> ™' '(tm)' '<p>™</p>'
+try '... -> …' '...' '<p>…</p>'
+
+try '"--" -> —' '--' '<p>—</p>'
+
+try '"-" -> –' 'regular -' '<p>regular –</p>'
+try 'A-B -> A-B' 'A-B' '<p>A-B</p>'
+try '"fancy" -> “fancy”' '"fancy"' '<p>“fancy”</p>'
+try "'fancy'" "'fancy'" '<p>‘fancy’</p>'
+try "don<b>'t -> don<b>’t" "don<b>'t" '<p>don<b>’t</p>'
+try "don't -> don’t" "don't" '<p>don’t</p>'
+try "it's -> it’s" "it's" '<p>it’s</p>'
+
+summary $0
+exit $rc
diff --git a/tests/snakepit.t b/tests/snakepit.t
new file mode 100644
index 0000000..79d71ba
--- /dev/null
+++ b/tests/snakepit.t
@@ -0,0 +1,29 @@
+. tests/functions.sh
+
+title "The snakepit of Markdown.pl compatibility"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try '[](single quote) text (quote)' \
+    "[foo](http://Poe's law) will make this fail ('no, it won't!') here."\
+    '<p><a href="http://Poe" title="s law) will make this fail ('"'no, it won't!"'">foo</a> here.</p>'
+
+try '-f1.0' '[](unclosed <url) (MKD_1_COMPAT)' '[foo](<http://no trailing gt)' \
+			'<p><a href="http://no%20trailing%20gt">foo</a></p>'
+
+try '[](unclosed <url)' '[foo](<http://no trailing gt)' \
+			'<p>[foo](<http://no trailing gt)</p>'
+
+try '<unfinished <tags> (1)' \
+'<foo [bar](foo)  <s>hi</s>' \
+'<p><foo [bar](foo)  <s>hi</s></p>'
+    
+try '<unfinished &<tags> (2)' \
+'<foo [bar](foo)  &<s>hi</s>' \
+'<p><foo [bar](foo)  &<s>hi</s></p>'
+
+try 'paragraph <br/> oddity' 'EOF  ' '<p>EOF</p>'
+    
+summary $0
+exit $rc
diff --git a/tests/strikethrough.t b/tests/strikethrough.t
new file mode 100644
index 0000000..9016a5c
--- /dev/null
+++ b/tests/strikethrough.t
@@ -0,0 +1,15 @@
+. tests/functions.sh
+
+title "strikethrough"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'strikethrough' '~~deleted~~' '<p><del>deleted</del></p>'
+try -fnodel '... with -fnodel' '~~deleted~~' '<p>~~deleted~~</p>'
+try 'mismatched tildes' '~~~tick~~' '<p><del>~tick</del></p>'
+try 'mismatched tildes(2)' '~~tick~~~' '<p>~~tick~~~</p>'
+try 'single tildes' '~tick~' '<p>~tick~</p>'
+
+summary $0
+exit $rc
diff --git a/tests/style.t b/tests/style.t
new file mode 100644
index 0000000..3da3d1c
--- /dev/null
+++ b/tests/style.t
@@ -0,0 +1,34 @@
+. tests/functions.sh
+
+title "styles"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'single line' '<style> ul {display:none;} </style>' ''
+
+ASK='<style>
+ul {display:none;}
+</style>'
+
+try 'multiple lines' "$ASK" ''
+
+try 'unclosed' '<style>foo' '<p><style>foo</p>'
+
+UNCLOSED='<style>
+text
+
+text'
+
+RESULT='<p><style>
+text</p>
+
+<p>text</p>'
+
+
+try 'multiple lines unclosed' "$UNCLOSED" "$RESULT"
+
+try -fnohtml 'unclosed with -fnohtml' '<style>foo' '<p><style>foo</p>'
+
+summary $0
+exit $rc
diff --git a/tests/superscript.t b/tests/superscript.t
new file mode 100644
index 0000000..d04647a
--- /dev/null
+++ b/tests/superscript.t
@@ -0,0 +1,17 @@
+. tests/functions.sh
+
+title "superscript"
+
+rc=0
+MARKDOWN_FLAGS=0x0; export MARKDOWN_FLAGS
+
+try -frelax  'A^B -> A<sup>B</sup> (-frelax)' 'A^B' '<p>A<sup>B</sup></p>'
+try -fstrict 'A^B != A<sup>B</sup> (-fstrict)' 'A^B' '<p>A^B</p>'
+try -fnosuperscript 'A^B != A<sup>B</sup> (-fnosuperscript)' 'A^B' '<p>A^B</p>'
+try -frelax 'A^B in link title' '[link](here "A^B")' '<p><a href="here" title="A^B">link</a></p>'
+try 'A^(B+2)C^2' 'A^(B+2)C^2' '<p>A<sup>B+2</sup>C<sup>2</sup></p>'
+try 'A^((B+2))C^2' 'A^((B+2))C^2' '<p>A<sup>(B+2)</sup>C<sup>2</sup></p>'
+try 'A^B+C^D' 'A^B+C^D' '<p>A<sup>B</sup>+C<sup>D</sup></p>'
+
+summary $0
+exit $rc
diff --git a/tests/syntax.text b/tests/syntax.text
new file mode 100644
index 0000000..df740dd
--- /dev/null
+++ b/tests/syntax.text
@@ -0,0 +1,897 @@
+Markdown: Syntax
+================
+
+<ul id="ProjectSubmenu">
+    <li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
+    <li><a href="/projects/markdown/basics" title="Markdown Basics">Basics</a></li>
+    <li><a class="selected" title="Markdown Syntax Documentation">Syntax</a></li>
+    <li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
+    <li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
+</ul>
+
+
+*   [Overview](#overview)
+    *   [Philosophy](#philosophy)
+    *   [Inline HTML](#html)
+    *   [Automatic Escaping for Special Characters](#autoescape)
+*   [Block Elements](#block)
+    *   [Paragraphs and Line Breaks](#p)
+    *   [Headers](#header)
+    *   [Blockquotes](#blockquote)
+    *   [Lists](#list)
+    *   [Code Blocks](#precode)
+    *   [Horizontal Rules](#hr)
+*   [Span Elements](#span)
+    *   [Links](#link)
+    *   [Emphasis](#em)
+    *   [Code](#code)
+    *   [Images](#img)
+*   [Miscellaneous](#misc)
+    *   [Backslash Escapes](#backslash)
+    *   [Automatic Links](#autolink)
+
+
+**Note:** This document is itself written using Markdown; you
+can [see the source for it by adding '.text' to the URL][src].
+
+  [src]: /projects/markdown/syntax.text
+
+* * *
+
+<h2 id="overview">Overview</h2>
+
+<h3 id="philosophy">Philosophy</h3>
+
+Markdown is intended to be as easy-to-read and easy-to-write as is feasible.
+
+Readability, however, is emphasized above all else. A Markdown-formatted
+document should be publishable as-is, as plain text, without looking
+like it's been marked up with tags or formatting instructions. While
+Markdown's syntax has been influenced by several existing text-to-HTML
+filters -- including [Setext] [1], [atx] [2], [Textile] [3], [reStructuredText] [4],
+[Grutatext] [5], and [EtText] [6] -- the single biggest source of
+inspiration for Markdown's syntax is the format of plain text email.
+
+  [1]: http://docutils.sourceforge.net/mirror/setext.html
+  [2]: http://www.aaronsw.com/2002/atx/
+  [3]: http://textism.com/tools/textile/
+  [4]: http://docutils.sourceforge.net/rst.html
+  [5]: http://www.triptico.com/software/grutatxt.html
+  [6]: http://ettext.taint.org/doc/
+
+To this end, Markdown's syntax is comprised entirely of punctuation
+characters, which punctuation characters have been carefully chosen so
+as to look like what they mean. E.g., asterisks around a word actually
+look like \*emphasis\*. Markdown lists look like, well, lists. Even
+blockquotes look like quoted passages of text, assuming you've ever
+used email.
+
+
+
+<h3 id="html">Inline HTML</h3>
+
+Markdown's syntax is intended for one purpose: to be used as a
+format for *writing* for the web.
+
+Markdown is not a replacement for HTML, or even close to it. Its
+syntax is very small, corresponding only to a very small subset of
+HTML tags. The idea is *not* to create a syntax that makes it easier
+to insert HTML tags. In my opinion, HTML tags are already easy to
+insert. The idea for Markdown is to make it easy to read, write, and
+edit prose. HTML is a *publishing* format; Markdown is a *writing*
+format. Thus, Markdown's formatting syntax only addresses issues that
+can be conveyed in plain text.
+
+For any markup that is not covered by Markdown's syntax, you simply
+use HTML itself. There's no need to preface it or delimit it to
+indicate that you're switching from Markdown to HTML; you just use
+the tags.
+
+The only restrictions are that block-level HTML elements -- e.g. `<div>`,
+`<table>`, `<pre>`, `<p>`, etc. -- must be separated from surrounding
+content by blank lines, and the start and end tags of the block should
+not be indented with tabs or spaces. Markdown is smart enough not
+to add extra (unwanted) `<p>` tags around HTML block-level tags.
+
+For example, to add an HTML table to a Markdown article:
+
+    This is a regular paragraph.
+
+    <table>
+        <tr>
+            <td>Foo</td>
+        </tr>
+    </table>
+
+    This is another regular paragraph.
+
+Note that Markdown formatting syntax is not processed within block-level
+HTML tags. E.g., you can't use Markdown-style `*emphasis*` inside an
+HTML block.
+
+Span-level HTML tags -- e.g. `<span>`, `<cite>`, or `<del>` -- can be
+used anywhere in a Markdown paragraph, list item, or header. If you
+want, you can even use HTML tags instead of Markdown formatting; e.g. if
+you'd prefer to use HTML `<a>` or `<img>` tags instead of Markdown's
+link or image syntax, go right ahead.
+
+Unlike block-level HTML tags, Markdown syntax *is* processed within
+span-level tags.
+
+
+<h3 id="autoescape">Automatic Escaping for Special Characters</h3>
+
+In HTML, there are two characters that demand special treatment: `<`
+and `&`. Left angle brackets are used to start tags; ampersands are
+used to denote HTML entities. If you want to use them as literal
+characters, you must escape them as entities, e.g. `<`, and
+`&`.
+
+Ampersands in particular are bedeviling for web writers. If you want to
+write about 'AT&T', you need to write '`AT&T`'. You even need to
+escape ampersands within URLs. Thus, if you want to link to:
+
+    http://images.google.com/images?num=30&q=larry+bird
+
+you need to encode the URL as:
+
+    http://images.google.com/images?num=30&q=larry+bird
+
+in your anchor tag `href` attribute. Needless to say, this is easy to
+forget, and is probably the single most common source of HTML validation
+errors in otherwise well-marked-up web sites.
+
+Markdown allows you to use these characters naturally, taking care of
+all the necessary escaping for you. If you use an ampersand as part of
+an HTML entity, it remains unchanged; otherwise it will be translated
+into `&`.
+
+So, if you want to include a copyright symbol in your article, you can write:
+
+    ©
+
+and Markdown will leave it alone. But if you write:
+
+    AT&T
+
+Markdown will translate it to:
+
+    AT&T
+
+Similarly, because Markdown supports [inline HTML](#html), if you use
+angle brackets as delimiters for HTML tags, Markdown will treat them as
+such. But if you write:
+
+    4 < 5
+
+Markdown will translate it to:
+
+    4 < 5
+
+However, inside Markdown code spans and blocks, angle brackets and
+ampersands are *always* encoded automatically. This makes it easy to use
+Markdown to write about HTML code. (As opposed to raw HTML, which is a
+terrible format for writing about HTML syntax, because every single `<`
+and `&` in your example code needs to be escaped.)
+
+
+* * *
+
+
+<h2 id="block">Block Elements</h2>
+
+
+<h3 id="p">Paragraphs and Line Breaks</h3>
+
+A paragraph is simply one or more consecutive lines of text, separated
+by one or more blank lines. (A blank line is any line that looks like a
+blank line -- a line containing nothing but spaces or tabs is considered
+blank.) Normal paragraphs should not be indented with spaces or tabs.
+
+The implication of the "one or more consecutive lines of text" rule is
+that Markdown supports "hard-wrapped" text paragraphs. This differs
+significantly from most other text-to-HTML formatters (including Movable
+Type's "Convert Line Breaks" option) which translate every line break
+character in a paragraph into a `<br />` tag.
+
+When you *do* want to insert a `<br />` break tag using Markdown, you
+end a line with two or more spaces, then type return.
+
+Yes, this takes a tad more effort to create a `<br />`, but a simplistic
+"every line break is a `<br />`" rule wouldn't work for Markdown.
+Markdown's email-style [blockquoting][bq] and multi-paragraph [list items][l]
+work best -- and look better -- when you format them with hard breaks.
+
+  [bq]: #blockquote
+  [l]:  #list
+
+
+
+<h3 id="header">Headers</h3>
+
+Markdown supports two styles of headers, [Setext] [1] and [atx] [2].
+
+Setext-style headers are "underlined" using equal signs (for first-level
+headers) and dashes (for second-level headers). For example:
+
+    This is an H1
+    =============
+
+    This is an H2
+    -------------
+
+Any number of underlining `=`'s or `-`'s will work.
+
+Atx-style headers use 1-6 hash characters at the start of the line,
+corresponding to header levels 1-6. For example:
+
+    # This is an H1
+
+    ## This is an H2
+
+    ###### This is an H6
+
+Optionally, you may "close" atx-style headers. This is purely
+cosmetic -- you can use this if you think it looks better. The
+closing hashes don't even need to match the number of hashes
+used to open the header. (The number of opening hashes
+determines the header level.) :
+
+    # This is an H1 #
+
+    ## This is an H2 ##
+
+    ### This is an H3 ######
+
+
+<h3 id="blockquote">Blockquotes</h3>
+
+Markdown uses email-style `>` characters for blockquoting. If you're
+familiar with quoting passages of text in an email message, then you
+know how to create a blockquote in Markdown. It looks best if you hard
+wrap the text and put a `>` before every line:
+
+    > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+    > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+    > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
+    > 
+    > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+    > id sem consectetuer libero luctus adipiscing.
+
+Markdown allows you to be lazy and only put the `>` before the first
+line of a hard-wrapped paragraph:
+
+    > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+    consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+    Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
+
+    > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+    id sem consectetuer libero luctus adipiscing.
+
+Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
+adding additional levels of `>`:
+
+    > This is the first level of quoting.
+    >
+    > > This is nested blockquote.
+    >
+    > Back to the first level.
+
+Blockquotes can contain other Markdown elements, including headers, lists,
+and code blocks:
+
+	> ## This is a header.
+	> 
+	> 1.   This is the first list item.
+	> 2.   This is the second list item.
+	> 
+	> Here's some example code:
+	> 
+	>     return shell_exec("echo $input | $markdown_script");
+
+Any decent text editor should make email-style quoting easy. For
+example, with BBEdit, you can make a selection and choose Increase
+Quote Level from the Text menu.
+
+
+<h3 id="list">Lists</h3>
+
+Markdown supports ordered (numbered) and unordered (bulleted) lists.
+
+Unordered lists use asterisks, pluses, and hyphens -- interchangably
+-- as list markers:
+
+    *   Red
+    *   Green
+    *   Blue
+
+is equivalent to:
+
+    +   Red
+    +   Green
+    +   Blue
+
+and:
+
+    -   Red
+    -   Green
+    -   Blue
+
+Ordered lists use numbers followed by periods:
+
+    1.  Bird
+    2.  McHale
+    3.  Parish
+
+It's important to note that the actual numbers you use to mark the
+list have no effect on the HTML output Markdown produces. The HTML
+Markdown produces from the above list is:
+
+    <ol>
+    <li>Bird</li>
+    <li>McHale</li>
+    <li>Parish</li>
+    </ol>
+
+If you instead wrote the list in Markdown like this:
+
+    1.  Bird
+    1.  McHale
+    1.  Parish
+
+or even:
+
+    3. Bird
+    1. McHale
+    8. Parish
+
+you'd get the exact same HTML output. The point is, if you want to,
+you can use ordinal numbers in your ordered Markdown lists, so that
+the numbers in your source match the numbers in your published HTML.
+But if you want to be lazy, you don't have to.
+
+If you do use lazy list numbering, however, you should still start the
+list with the number 1. At some point in the future, Markdown may support
+starting ordered lists at an arbitrary number.
+
+List markers typically start at the left margin, but may be indented by
+up to three spaces. List markers must be followed by one or more spaces
+or a tab.
+
+To make lists look nice, you can wrap items with hanging indents:
+
+    *   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+        Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
+        viverra nec, fringilla in, laoreet vitae, risus.
+    *   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
+        Suspendisse id sem consectetuer libero luctus adipiscing.
+
+But if you want to be lazy, you don't have to:
+
+    *   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+    Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
+    viverra nec, fringilla in, laoreet vitae, risus.
+    *   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
+    Suspendisse id sem consectetuer libero luctus adipiscing.
+
+If list items are separated by blank lines, Markdown will wrap the
+items in `<p>` tags in the HTML output. For example, this input:
+
+    *   Bird
+    *   Magic
+
+will turn into:
+
+    <ul>
+    <li>Bird</li>
+    <li>Magic</li>
+    </ul>
+
+But this:
+
+    *   Bird
+
+    *   Magic
+
+will turn into:
+
+    <ul>
+    <li><p>Bird</p></li>
+    <li><p>Magic</p></li>
+    </ul>
+
+List items may consist of multiple paragraphs. Each subsequent
+paragraph in a list item must be intended by either 4 spaces
+or one tab:
+
+    1.  This is a list item with two paragraphs. Lorem ipsum dolor
+        sit amet, consectetuer adipiscing elit. Aliquam hendrerit
+        mi posuere lectus.
+
+        Vestibulum enim wisi, viverra nec, fringilla in, laoreet
+        vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
+        sit amet velit.
+
+    2.  Suspendisse id sem consectetuer libero luctus adipiscing.
+
+It looks nice if you indent every line of the subsequent
+paragraphs, but here again, Markdown will allow you to be
+lazy:
+
+    *   This is a list item with two paragraphs.
+
+        This is the second paragraph in the list item. You're
+    only required to indent the first line. Lorem ipsum dolor
+    sit amet, consectetuer adipiscing elit.
+
+    *   Another item in the same list.
+
+To put a blockquote within a list item, the blockquote's `>`
+delimiters need to be indented:
+
+    *   A list item with a blockquote:
+
+        > This is a blockquote
+        > inside a list item.
+
+To put a code block within a list item, the code block needs
+to be indented *twice* -- 8 spaces or two tabs:
+
+    *   A list item with a code block:
+
+            <code goes here>
+
+
+It's worth noting that it's possible to trigger an ordered list by
+accident, by writing something like this:
+
+    1986. What a great season.
+
+In other words, a *number-period-space* sequence at the beginning of a
+line. To avoid this, you can backslash-escape the period:
+
+    1986\. What a great season.
+
+
+
+<h3 id="precode">Code Blocks</h3>
+
+Pre-formatted code blocks are used for writing about programming or
+markup source code. Rather than forming normal paragraphs, the lines
+of a code block are interpreted literally. Markdown wraps a code block
+in both `<pre>` and `<code>` tags.
+
+To produce a code block in Markdown, simply indent every line of the
+block by at least 4 spaces or 1 tab. For example, given this input:
+
+    This is a normal paragraph:
+
+        This is a code block.
+
+Markdown will generate:
+
+    <p>This is a normal paragraph:</p>
+
+    <pre><code>This is a code block.
+    </code></pre>
+
+One level of indentation -- 4 spaces or 1 tab -- is removed from each
+line of the code block. For example, this:
+
+    Here is an example of AppleScript:
+
+        tell application "Foo"
+            beep
+        end tell
+
+will turn into:
+
+    <p>Here is an example of AppleScript:</p>
+
+    <pre><code>tell application "Foo"
+        beep
+    end tell
+    </code></pre>
+
+A code block continues until it reaches a line that is not indented
+(or the end of the article).
+
+Within a code block, ampersands (`&`) and angle brackets (`<` and `>`)
+are automatically converted into HTML entities. This makes it very
+easy to include example HTML source code using Markdown -- just paste
+it and indent it, and Markdown will handle the hassle of encoding the
+ampersands and angle brackets. For example, this:
+
+        <div class="footer">
+            © 2004 Foo Corporation
+        </div>
+
+will turn into:
+
+    <pre><code><div class="footer">
+        &copy; 2004 Foo Corporation
+    </div>
+    </code></pre>
+
+Regular Markdown syntax is not processed within code blocks. E.g.,
+asterisks are just literal asterisks within a code block. This means
+it's also easy to use Markdown to write about Markdown's own syntax.
+
+
+
+<h3 id="hr">Horizontal Rules</h3>
+
+You can produce a horizontal rule tag (`<hr />`) by placing three or
+more hyphens, asterisks, or underscores on a line by themselves. If you
+wish, you may use spaces between the hyphens or asterisks. Each of the
+following lines will produce a horizontal rule:
+
+    * * *
+
+    ***
+
+    *****
+
+    - - -
+
+    ---------------------------------------
+
+
+* * *
+
+<h2 id="span">Span Elements</h2>
+
+<h3 id="link">Links</h3>
+
+Markdown supports two style of links: *inline* and *reference*.
+
+In both styles, the link text is delimited by [square brackets].
+
+To create an inline link, use a set of regular parentheses immediately
+after the link text's closing square bracket. Inside the parentheses,
+put the URL where you want the link to point, along with an *optional*
+title for the link, surrounded in quotes. For example:
+
+    This is [an example](http://example.com/ "Title") inline link.
+
+    [This link](http://example.net/) has no title attribute.
+
+Will produce:
+
+    <p>This is <a href="http://example.com/" title="Title">
+    an example</a> inline link.</p>
+
+    <p><a href="http://example.net/">This link</a> has no
+    title attribute.</p>
+
+If you're referring to a local resource on the same server, you can
+use relative paths:
+
+    See my [About](/about/) page for details.   
+
+Reference-style links use a second set of square brackets, inside
+which you place a label of your choosing to identify the link:
+
+    This is [an example][id] reference-style link.
+
+You can optionally use a space to separate the sets of brackets:
+
+    This is [an example] [id] reference-style link.
+
+Then, anywhere in the document, you define your link label like this,
+on a line by itself:
+
+    [id]: http://example.com/  "Optional Title Here"
+
+That is:
+
+*   Square brackets containing the link identifier (optionally
+    indented from the left margin using up to three spaces);
+*   followed by a colon;
+*   followed by one or more spaces (or tabs);
+*   followed by the URL for the link;
+*   optionally followed by a title attribute for the link, enclosed
+    in double or single quotes, or enclosed in parentheses.
+
+The following three link definitions are equivalent:
+
+	[foo]: http://example.com/  "Optional Title Here"
+	[foo]: http://example.com/  'Optional Title Here'
+	[foo]: http://example.com/  (Optional Title Here)
+
+**Note:** There is a known bug in Markdown.pl 1.0.1 which prevents
+single quotes from being used to delimit link titles.
+
+The link URL may, optionally, be surrounded by angle brackets:
+
+    [id]: <http://example.com/>  "Optional Title Here"
+
+You can put the title attribute on the next line and use extra spaces
+or tabs for padding, which tends to look better with longer URLs:
+
+    [id]: http://example.com/longish/path/to/resource/here
+        "Optional Title Here"
+
+Link definitions are only used for creating links during Markdown
+processing, and are stripped from your document in the HTML output.
+
+Link definition names may constist of letters, numbers, spaces, and
+punctuation -- but they are *not* case sensitive. E.g. these two
+links:
+
+	[link text][a]
+	[link text][A]
+
+are equivalent.
+
+The *implicit link name* shortcut allows you to omit the name of the
+link, in which case the link text itself is used as the name.
+Just use an empty set of square brackets -- e.g., to link the word
+"Google" to the google.com web site, you could simply write:
+
+	[Google][]
+
+And then define the link:
+
+	[Google]: http://google.com/
+
+Because link names may contain spaces, this shortcut even works for
+multiple words in the link text:
+
+	Visit [Daring Fireball][] for more information.
+
+And then define the link:
+	
+	[Daring Fireball]: http://daringfireball.net/
+
+Link definitions can be placed anywhere in your Markdown document. I
+tend to put them immediately after each paragraph in which they're
+used, but if you want, you can put them all at the end of your
+document, sort of like footnotes.
+
+Here's an example of reference links in action:
+
+    I get 10 times more traffic from [Google] [1] than from
+    [Yahoo] [2] or [MSN] [3].
+
+      [1]: http://google.com/        "Google"
+      [2]: http://search.yahoo.com/  "Yahoo Search"
+      [3]: http://search.msn.com/    "MSN Search"
+
+Using the implicit link name shortcut, you could instead write:
+
+    I get 10 times more traffic from [Google][] than from
+    [Yahoo][] or [MSN][].
+
+      [google]: http://google.com/        "Google"
+      [yahoo]:  http://search.yahoo.com/  "Yahoo Search"
+      [msn]:    http://search.msn.com/    "MSN Search"
+
+Both of the above examples will produce the following HTML output:
+
+    <p>I get 10 times more traffic from <a href="http://google.com/"
+    title="Google">Google</a> than from
+    <a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a>
+    or <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>
+
+For comparison, here is the same paragraph written using
+Markdown's inline link style:
+
+    I get 10 times more traffic from [Google](http://google.com/ "Google")
+    than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or
+    [MSN](http://search.msn.com/ "MSN Search").
+
+The point of reference-style links is not that they're easier to
+write. The point is that with reference-style links, your document
+source is vastly more readable. Compare the above examples: using
+reference-style links, the paragraph itself is only 81 characters
+long; with inline-style links, it's 176 characters; and as raw HTML,
+it's 234 characters. In the raw HTML, there's more markup than there
+is text.
+
+With Markdown's reference-style links, a source document much more
+closely resembles the final output, as rendered in a browser. By
+allowing you to move the markup-related metadata out of the paragraph,
+you can add links without interrupting the narrative flow of your
+prose.
+
+
+<h3 id="em">Emphasis</h3>
+
+Markdown treats asterisks (`*`) and underscores (`_`) as indicators of
+emphasis. Text wrapped with one `*` or `_` will be wrapped with an
+HTML `<em>` tag; double `*`'s or `_`'s will be wrapped with an HTML
+`<strong>` tag. E.g., this input:
+
+    *single asterisks*
+
+    _single underscores_
+
+    **double asterisks**
+
+    __double underscores__
+
+will produce:
+
+    <em>single asterisks</em>
+
+    <em>single underscores</em>
+
+    <strong>double asterisks</strong>
+
+    <strong>double underscores</strong>
+
+You can use whichever style you prefer; the lone restriction is that
+the same character must be used to open and close an emphasis span.
+
+Emphasis can be used in the middle of a word:
+
+    un*fucking*believable
+
+But if you surround an `*` or `_` with spaces, it'll be treated as a
+literal asterisk or underscore.
+
+To produce a literal asterisk or underscore at a position where it
+would otherwise be used as an emphasis delimiter, you can backslash
+escape it:
+
+    \*this text is surrounded by literal asterisks\*
+
+
+
+<h3 id="code">Code</h3>
+
+To indicate a span of code, wrap it with backtick quotes (`` ` ``).
+Unlike a pre-formatted code block, a code span indicates code within a
+normal paragraph. For example:
+
+    Use the `printf()` function.
+
+will produce:
+
+    <p>Use the <code>printf()</code> function.</p>
+
+To include a literal backtick character within a code span, you can use
+multiple backticks as the opening and closing delimiters:
+
+    ``There is a literal backtick (`) here.``
+
+which will produce this:
+
+    <p><code>There is a literal backtick (`) here.</code></p>
+
+The backtick delimiters surrounding a code span may include spaces --
+one after the opening, one before the closing. This allows you to place
+literal backtick characters at the beginning or end of a code span:
+
+	A single backtick in a code span: `` ` ``
+	
+	A backtick-delimited string in a code span: `` `foo` ``
+
+will produce:
+
+	<p>A single backtick in a code span: <code>`</code></p>
+	
+	<p>A backtick-delimited string in a code span: <code>`foo`</code></p>
+
+With a code span, ampersands and angle brackets are encoded as HTML
+entities automatically, which makes it easy to include example HTML
+tags. Markdown will turn this:
+
+    Please don't use any `<blink>` tags.
+
+into:
+
+    <p>Please don't use any <code><blink></code> tags.</p>
+
+You can write this:
+
+    `—` is the decimal-encoded equivalent of `—`.
+
+to produce:
+
+    <p><code>&#8212;</code> is the decimal-encoded
+    equivalent of <code>&mdash;</code>.</p>
+
+
+
+<h3 id="img">Images</h3>
+
+Admittedly, it's fairly difficult to devise a "natural" syntax for
+placing images into a plain text document format.
+
+Markdown uses an image syntax that is intended to resemble the syntax
+for links, allowing for two styles: *inline* and *reference*.
+
+Inline image syntax looks like this:
+
+    ![Alt text](/path/to/img.jpg)
+
+    ![Alt text](/path/to/img.jpg "Optional title")
+
+That is:
+
+*   An exclamation mark: `!`;
+*   followed by a set of square brackets, containing the `alt`
+    attribute text for the image;
+*   followed by a set of parentheses, containing the URL or path to
+    the image, and an optional `title` attribute enclosed in double
+    or single quotes.
+
+Reference-style image syntax looks like this:
+
+    ![Alt text][id]
+
+Where "id" is the name of a defined image reference. Image references
+are defined using syntax identical to link references:
+
+    [id]: url/to/image  "Optional title attribute"
+
+As of this writing, Markdown has no syntax for specifying the
+dimensions of an image; if this is important to you, you can simply
+use regular HTML `<img>` tags.
+
+
+* * *
+
+
+<h2 id="misc">Miscellaneous</h2>
+
+<h3 id="autolink">Automatic Links</h3>
+
+Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this:
+
+    <http://example.com/>
+    
+Markdown will turn this into:
+
+    <a href="http://example.com/">http://example.com/</a>
+
+Automatic links for email addresses work similarly, except that
+Markdown will also perform a bit of randomized decimal and hex
+entity-encoding to help obscure your address from address-harvesting
+spambots. For example, Markdown will turn this:
+
+    <address at example.com>
+
+into something like this:
+
+    <a href="&#x6D;&#x61;i&#x6C;&#x74;&#x6F;:&#x61;&#x64;&#x64;&#x72;&#x65;
+    ss@ex&#x61;m&#x70;&#x6C;e&#x2E;co
+    m">&#x61;&#x64;&#x64;&#x72;&#x65;ss@ex&#x61;
+    m&#x70;&#x6C;e&#x2E;com</a>
+
+which will render in a browser as a clickable link to "address at example.com".
+
+(This sort of entity-encoding trick will indeed fool many, if not
+most, address-harvesting bots, but it definitely won't fool all of
+them. It's better than nothing, but an address published in this way
+will probably eventually start receiving spam.)
+
+
+
+<h3 id="backslash">Backslash Escapes</h3>
+
+Markdown allows you to use backslash escapes to generate literal
+characters which would otherwise have special meaning in Markdown's
+formatting syntax. For example, if you wanted to surround a word with
+literal asterisks (instead of an HTML `<em>` tag), you can backslashes
+before the asterisks, like this:
+
+    \*literal asterisks\*
+
+Markdown provides backslash escapes for the following characters:
+
+    \   backslash
+    `   backtick
+    *   asterisk
+    _   underscore
+    {}  curly braces
+    []  square brackets
+    ()  parentheses
+    #   hash mark
+	+	plus sign
+	-	minus sign (hyphen)
+    .   dot
+    !   exclamation mark
+
diff --git a/tests/tables.t b/tests/tables.t
new file mode 100644
index 0000000..f1fe24d
--- /dev/null
+++ b/tests/tables.t
@@ -0,0 +1,167 @@
+. tests/functions.sh
+
+title "tables"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try 'single-column table' \
+    '|hello
+|-----
+|sailor' \
+    '<table>
+<thead>
+<tr>
+<th></th>
+<th>hello</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td></td>
+<td>sailor</td>
+</tr>
+</tbody>
+</table>'
+
+
+try 'two-column table' \
+    '
+  a  |  b
+-----|------
+hello|sailor' \
+    '<table>
+<thead>
+<tr>
+<th>  a  </th>
+<th>  b</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>hello</td>
+<td>sailor</td>
+</tr>
+</tbody>
+</table>'
+
+try 'three-column table' \
+'a|b|c
+-|-|-
+hello||sailor'\
+    '<table>
+<thead>
+<tr>
+<th>a</th>
+<th>b</th>
+<th>c</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>hello</td>
+<td></td>
+<td>sailor</td>
+</tr>
+</tbody>
+</table>'
+
+try 'two-column table with empty cells' \
+    '
+  a  |  b
+-----|------
+hello|
+     |sailor' \
+    '<table>
+<thead>
+<tr>
+<th>  a  </th>
+<th>  b</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>hello</td>
+<td></td>
+</tr>
+<tr>
+<td>     </td>
+<td>sailor</td>
+</tr>
+</tbody>
+</table>'
+
+try 'two-column table with alignment' \
+    '
+  a  |  b
+----:|:-----
+hello|sailor' \
+    '<table>
+<thead>
+<tr>
+<th align="right">  a  </th>
+<th align="left">  b</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="right">hello</td>
+<td align="left">sailor</td>
+</tr>
+</tbody>
+</table>'
+    
+try 'table with extra data column' \
+    '
+  a  |  b
+-----|------
+hello|sailor|boy' \
+    '<table>
+<thead>
+<tr>
+<th>  a  </th>
+<th>  b</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>hello</td>
+<td>sailor|boy</td>
+</tr>
+</tbody>
+</table>'
+
+
+try -fnotables 'tables with -fnotables' \
+    'a|b
+-|-
+hello|sailor' \
+    '<p>a|b
+–|–
+hello|sailor</p>'
+
+try 'deceptive non-table text' \
+    'a | b | c
+
+text' \
+    '<p>a | b | c</p>
+
+<p>text</p>'
+
+try 'table headers only' \
+    'a|b|c
+-|-|-' \
+    '<table>
+<thead>
+<tr>
+<th>a</th>
+<th>b</th>
+<th>c</th>
+</tr>
+</thead>
+<tbody>
+</tbody>
+</table>'
+
+summary $0
+exit $rc
diff --git a/tests/tabstop.t b/tests/tabstop.t
new file mode 100644
index 0000000..8d27881
--- /dev/null
+++ b/tests/tabstop.t
@@ -0,0 +1,48 @@
+. tests/functions.sh
+
+rc=0
+unset MARKDOWN_FLAGS
+unset MKD_TABSTOP
+
+eval `./markdown -V | tr ' ' '\n' | grep TAB`
+
+if [ "${TAB:-4}" -eq 8 ]; then
+    title "dealing with tabstop derangement"
+
+    LIST='
+ *  A
+     *  B
+	 *  C'
+
+    try 'markdown with TAB=8' \
+	"$LIST" \
+	'<ul>
+<li>A
+
+<ul>
+<li>B
+
+<ul>
+<li>C</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>'
+
+    try -F0x020000 'markdown with TAB=4' \
+	"$LIST" \
+	'<ul>
+<li>A
+
+<ul>
+<li>B</li>
+<li>C</li>
+</ul>
+</li>
+</ul>'
+
+
+summary $0
+fi
+exit $rc
diff --git a/tests/toc.t b/tests/toc.t
new file mode 100644
index 0000000..342f9cb
--- /dev/null
+++ b/tests/toc.t
@@ -0,0 +1,35 @@
+. tests/functions.sh
+
+title "table-of-contents support"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try '-T -ftoc' 'table of contents' \
+'#H1
+hi' \
+'<ul>
+ <li><a href="#H1">H1</a></li>
+</ul>
+<h1 id="H1">H1</h1>
+
+<p>hi</p>'
+
+try '-T -ftoc' 'toc item with link' \
+'##[H2](H2) here' \
+'<ul>
+ <li><ul>
+  <li><a href="#H2.here">H2 here</a></li>
+ </ul></li>
+</ul>
+<h2 id="H2.here"><a href="H2">H2</a> here</h2>'  
+
+try '-T -ftoc' 'toc item with non-alpha start' \
+'#1 header' \
+'<ul>
+ <li><a href="#L1.header">1 header</a></li>
+</ul>
+<h1 id="L1.header">1 header</h1>'
+
+summary $0
+exit $rc
diff --git a/tests/xml.t b/tests/xml.t
new file mode 100644
index 0000000..17ac74a
--- /dev/null
+++ b/tests/xml.t
@@ -0,0 +1,18 @@
+. tests/functions.sh
+
+title "xml output with MKD_CDATA"
+
+rc=0
+MARKDOWN_FLAGS=
+
+try -fcdata 'xml output from markdown()' 'hello,sailor' '<p>hello,sailor</p>'
+try -fcdata 'from mkd_generateline()' -t'"hello,sailor"' '&ldquo;hello,sailor&rdquo;'
+try -fnocdata 'html output from markdown()' '"hello,sailor"' '<p>“hello,sailor”</p>'
+try -fnocdata '... from mkd_generateline()' -t'"hello,sailor"' '“hello,sailor”'
+
+try -fcdata 'xml output with multibyte utf-8' \
+    'tecnología y servicios más confiables' \
+    '<p>tecnología y servicios más confiables</p>'
+
+summary $0
+exit $rc
diff --git a/theme.1 b/theme.1
new file mode 100644
index 0000000..473b913
--- /dev/null
+++ b/theme.1
@@ -0,0 +1,142 @@
+.\"     %A%
+.\"
+.Dd January 23, 2008
+.Dt THEME 1
+.Os MASTODON
+.Sh NAME
+.Nm theme
+.Nd create a web page from a template file
+.Sh SYNOPSIS
+.Nm
+.Op Fl d Pa root
+.Op Fl f
+.Op Fl o Pa file
+.Op Fl p Pa pagename
+.Op Fl t Pa template
+.Op Fl V
+.Op Pa textfile
+.Sh DESCRIPTION
+The
+.Nm
+utility takes a
+.Xr markdown 7 Ns -formatted
+.Pa textfile
+.Pq or stdin if not specified,
+compiles it, and combines it with a
+.Em template
+.Po
+.Pa page.theme
+by default
+.Pc
+to produce a web page.   If a path to the 
+template is not specified,
+.Nm
+looks for 
+.Pa page.theme
+in the current directory, then each parent directory up to the
+.Pa "document root"
+.Po
+set with
+.Fl d
+or, if unset, the
+.Em "root directory"
+of the system.
+.Pc
+If 
+.Pa page.theme
+is found,
+.Nm
+copies it to the output, looking for 
+.Em "<?theme action?>"
+html tags and processing the embedded
+.Ar action 
+as appropriate.
+.Pp
+.Nm
+processes the following actions:
+.Bl -tag -width "include("
+.It Ar author
+Prints the author name(s) from the
+.Xr mkd_doc_author 3
+function.
+.It Ar body
+Prints the formatted
+.Xr markdown 7
+input file.
+.It Ar date
+Prints the date returned by
+.Xr mkd_doc_date 3
+or, if none, the
+date the input file was last modified.
+.It Ar dir
+Prints the directory part of the pagename
+.It Ar include Ns Pq Pa file 
+Prints the contents of 
+.Pa file .
+.Xr Markdown 7
+translation will
+.Em NOT
+be done on this file.
+.It Ar source
+The filename part of the pagename.
+.It Ar style
+Print any stylesheets
+.Pq see Xr mkd-extensions 7
+found in the input file.
+.It Ar title
+Print the title returned by
+.Xr mkd_doc_title 3 ,
+or, if that does not exist, the source filename.
+.It Ar version
+Print the version of
+.Xr discount 7
+that this copy of theme was compiled with.
+.El
+.Pp
+If input is coming from a file and the output was not set with the
+.Ar o
+option, 
+.Nm writes the output to
+.Pa file-sans-text.html
+.Pq if 
+.Ar file
+has a 
+.Pa .text
+suffix, that will be stripped off and replaced with 
+.Pa .html ;
+otherwise a
+.Pa .html
+will be appended to the end of the filename.)
+.Pp
+The options are as follows:
+.Bl -tag -width "-o file"
+.It Fl d Pa root
+Set the 
+.Em "document root"
+to
+.Ar root
+.It Fl f
+Forcibly overwrite existing html files.
+.It Fl o Pa filename
+Write the output to
+.Ar filename .
+.It Fl p Ar path
+Set the pagename to
+.Ar path .
+.It Fl t Ar filename
+Use
+.Ar filename
+as the template file.
+.El
+.Sh RETURN VALUES
+The
+.Nm
+utility exits 0 on success, and >0 if an error occurs.
+.Sh SEE ALSO
+.Xr markdown 1 ,
+.Xr markdown 3 ,
+.Xr markdown 7 ,
+.Xr mkd-extensions 7 .
+.Sh AUTHOR
+.An David Parsons
+.Pq Li orc at pell.chi.il.us
diff --git a/theme.c b/theme.c
new file mode 100644
index 0000000..637fe5e
--- /dev/null
+++ b/theme.c
@@ -0,0 +1,609 @@
+/*
+ * theme:  use a template to create a webpage (markdown-style)
+ *
+ * usage:  theme [-d root] [-p pagename] [-t template] [-o html] [source]
+ *
+ */
+/*
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if defined(HAVE_BASENAME) && defined(HAVE_LIBGEN_H)
+#  include <libgen.h>
+#endif
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#if HAVE_PWD_H
+#  include <pwd.h>
+#endif
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "mkdio.h"
+#include "cstring.h"
+#include "amalloc.h"
+
+char *pgm = "theme";
+char *output = 0;
+char *pagename = 0;
+char *root = 0;
+#if HAVE_PWD_H
+struct passwd *me = 0;
+#endif
+struct stat *infop = 0;
+
+
+#define INTAG 0x01
+#define INHEAD 0x02
+#define INBODY 0x04
+
+
+#ifndef HAVE_BASENAME
+char *
+basename(char *path)
+{
+    char *p;
+
+    if (( p = strrchr(path, '/') ))
+	return 1+p;
+    return path;
+}
+#endif
+
+#ifdef HAVE_FCHDIR
+typedef int HERE;
+#define NOT_HERE (-1)
+
+#define pushd(d)	open(d, O_RDONLY)
+
+int
+popd(HERE pwd)
+{
+    int rc = fchdir(pwd);
+    close(pwd);
+    return rc;
+}
+
+#else
+
+typedef char* HERE;
+#define NOT_HERE 0
+
+HERE
+pushd(char *d)
+{
+    HERE cwd;
+    int size;
+    
+    if ( chdir(d) == -1 )
+	return NOT_HERE;
+
+    for (cwd = malloc(size=40); cwd; cwd = realloc(cwd, size *= 2))
+	if ( getcwd(cwd, size) )
+	    return cwd;
+
+    return NOT_HERE;
+}
+
+int
+popd(HERE pwd)
+{
+    if ( pwd ) {
+	int rc = chdir(pwd);
+	free(pwd);
+
+	return rc;
+    }
+    return -1;
+}
+#endif
+
+typedef STRING(int) Istring;
+
+void
+fail(char *why, ...)
+{
+    va_list ptr;
+
+    va_start(ptr,why);
+    fprintf(stderr, "%s: ", pgm);
+    vfprintf(stderr, why, ptr);
+    fputc('\n', stderr);
+    va_end(ptr);
+    exit(1);
+}
+
+
+/* open_template() -- start at the current directory and work up,
+ *                    looking for the deepest nested template. 
+ *                    Stop looking when we reach $root or /
+ */
+FILE *
+open_template(char *template)
+{
+    char *cwd;
+    int szcwd;
+    HERE here = pushd(".");
+    FILE *ret;
+
+    if ( here == NOT_HERE )
+	fail("cannot access the current directory");
+
+    szcwd = root ? 1 + strlen(root) : 2;
+
+    if ( (cwd = malloc(szcwd)) == 0 )
+	return 0;
+
+    while ( !(ret = fopen(template, "r")) ) {
+	if ( getcwd(cwd, szcwd) == 0 ) {
+	    if ( errno == ERANGE )
+		goto up;
+	    break;
+	}
+
+	if ( root && (strcmp(root, cwd) == 0) )
+	    break;	/* ran out of paths to search */
+	else if ( (strcmp(cwd, "/") == 0) || (*cwd == 0) )
+	    break;	/* reached / */
+
+    up: if ( chdir("..") == -1 )
+	    break;
+    }
+    free(cwd);
+    popd(here);
+    return ret;
+} /* open_template */
+
+
+static Istring inbuf;
+static int psp;
+
+static int
+prepare(FILE *input)
+{
+    int c;
+
+    CREATE(inbuf);
+    psp = 0;
+    while ( (c = getc(input)) != EOF )
+	EXPAND(inbuf) = c;
+    fclose(input);
+    return 1;
+}
+
+static int
+pull()
+{
+    return psp < S(inbuf) ? T(inbuf)[psp++] : EOF;
+}
+
+static int
+peek(int offset)
+{
+    int pos = (psp + offset)-1;
+
+    if ( pos >= 0 && pos < S(inbuf) )
+	return T(inbuf)[pos];
+
+    return EOF;
+}
+
+static int
+shift(int shiftwidth)
+{
+    psp += shiftwidth;
+    return psp;
+}
+
+static int*
+cursor()
+{
+    return T(inbuf) + psp;
+}
+
+
+static int
+thesame(int *p, char *pat)
+{
+    int i;
+
+    for ( i=0; pat[i]; i++ ) {
+	if ( pat[i] == ' ' ) {
+	    if ( !isspace(peek(i+1)) ) {
+		return 0;
+	    }
+	}
+	else if ( tolower(peek(i+1)) != pat[i] ) {
+	    return 0;
+	}
+    }
+    return 1;
+}
+
+
+static int
+istag(int *p, char *pat)
+{
+    int c;
+
+    if ( thesame(p, pat) ) {
+	c = peek(strlen(pat)+1);
+	return (c == '>' || isspace(c));
+    }
+    return 0;
+}
+
+
+/* finclude() includes some (unformatted) source
+ */
+static void
+finclude(MMIOT *doc, FILE *out, int flags, int whence)
+{
+    int c;
+    Cstring include;
+    FILE *f;
+
+    CREATE(include);
+
+    while ( (c = pull()) != '(' )
+	;
+
+    while ( (c=pull()) != ')' && c != EOF )
+	EXPAND(include) = c;
+
+    if ( c != EOF ) {
+	EXPAND(include) = 0;
+	S(include)--;
+
+	if (( f = fopen(T(include), "r") )) {
+	    while ( (c = getc(f)) != EOF )
+		putc(c, out);
+	    fclose(f);
+	}
+    }
+    DELETE(include);
+}
+
+
+/* fdirname() prints out the directory part of a path
+ */
+static void
+fdirname(MMIOT *doc, FILE *output, int flags, int whence)
+{
+    char *p;
+
+    if ( pagename && (p = basename(pagename)) )
+	fwrite(pagename, strlen(pagename)-strlen(p), 1, output);
+}
+
+
+/* fbasename() prints out the file name part of a path
+ */
+static void
+fbasename(MMIOT *doc, FILE *output, int flags, int whence)
+{
+    char *p;
+
+    if ( pagename ) {
+	p = basename(pagename);
+
+	if ( !p )
+	    p = pagename;
+
+	if ( p )
+	    fwrite(p, strlen(p), 1, output);
+    }
+}
+
+
+/* ftitle() prints out the document title
+ */
+static void
+ftitle(MMIOT *doc, FILE* output, int flags, int whence)
+{
+    char *h;
+    if ( (h = mkd_doc_title(doc)) == 0 && pagename )
+	h = pagename;
+
+    if ( h )
+	mkd_generateline(h, strlen(h), output, flags);
+}
+
+
+/* fdate() prints out the document date
+ */
+static void
+fdate(MMIOT *doc, FILE *output, int flags, int whence)
+{
+    char *h;
+
+    if ( (h = mkd_doc_date(doc)) || ( infop && (h = ctime(&infop->st_mtime)) ) )
+	mkd_generateline(h, strlen(h), output, flags|MKD_TAGTEXT);
+}
+
+
+/* fauthor() prints out the document author
+ */
+static void
+fauthor(MMIOT *doc, FILE *output, int flags, int whence)
+{
+    char *h = mkd_doc_author(doc);
+
+#if HAVE_PWD_H
+    if ( (h == 0) && me )
+	h = me->pw_gecos;
+#endif
+
+    if ( h )
+	mkd_generateline(h, strlen(h), output, flags);
+}
+
+
+/* fconfig() prints out a tabular version of
+ * tabular versions of the flags.
+ */
+static void
+fconfig(MMIOT *doc, FILE *output, int flags, int whence)
+{
+    mkd_mmiot_flags(output, doc, (whence & (INHEAD|INTAG)) ? 0 : 1);
+}
+
+
+/* fversion() prints out the document version
+ */
+static void
+fversion(MMIOT *doc, FILE *output, int flags, int whence)
+{
+    fwrite(markdown_version, strlen(markdown_version), 1, output);
+}
+
+
+/* fbody() prints out the document
+ */
+static void
+fbody(MMIOT *doc, FILE *output, int flags, int whence)
+{
+    mkd_generatehtml(doc, output);
+}
+
+/* ftoc() prints out the table of contents
+ */
+static void
+ftoc(MMIOT *doc, FILE *output, int flags, int whence)
+{
+    mkd_generatetoc(doc, output);
+}
+
+/* fstyle() prints out the document's style section
+ */
+static void
+fstyle(MMIOT *doc, FILE *output, int flags, int whence)
+{
+    mkd_generatecss(doc, output);
+}
+
+
+/*
+ * theme expansions we love:
+ *   <?theme date?>	-- the document date (file or header date)
+ *   <?theme title?>	-- the document title (header title or document name)
+ *   <?theme author?>	-- the document author (header author or document owner)
+ *   <?theme version?>  -- the version#
+ *   <?theme body?>	-- the document body
+ *   <?theme source?>	-- the filename part of the document name
+ *   <?theme dir?>	-- the directory part of the document name
+ *   <?theme html?>	-- the html file name
+ *   <?theme style?>	-- document-supplied style blocks
+ *   <?theme include(file)?> -- include a file.
+ */
+static struct _keyword {
+    char *kw;
+    int where;
+    void (*what)(MMIOT*,FILE*,int,int);
+} keyword[] = { 
+    { "author?>",  0xffff, fauthor },
+    { "body?>",    INBODY, fbody },
+    { "toc?>",     INBODY, ftoc },
+    { "date?>",    0xffff, fdate },
+    { "dir?>",     0xffff, fdirname },
+    { "include(",  0xffff, finclude },
+    { "source?>",  0xffff, fbasename },
+    { "style?>",   INHEAD, fstyle },
+    { "title?>",   0xffff, ftitle },
+    { "version?>", 0xffff, fversion },
+    { "config?>",  0xffff, fconfig },
+};
+#define NR(x)	(sizeof x / sizeof x[0])
+
+
+/* spin() - run through the theme template, looking for <?theme expansions
+ */
+void
+spin(FILE *template, MMIOT *doc, FILE *output)
+{
+    int c;
+    int *p;
+    int flags;
+    int where = 0x0;
+    int i;
+
+    prepare(template);
+
+    while ( (c = pull()) != EOF ) {
+	if ( c == '<' ) {
+	    if ( peek(1) == '!' && peek(2) == '-' && peek(3) == '-' ) {
+		fputs("<!--", output);
+		shift(3);
+		do {
+		    putc(c=pull(), output);
+		} while ( ! (c == '-' && peek(1) == '-' && peek(2) == '>') );
+	    }
+	    else if ( (peek(1) == '?') && thesame(cursor(), "?theme ") ) {
+		shift(strlen("?theme "));
+
+		while ( ((c = pull()) != EOF) && isspace(c) )
+		    ;
+
+		shift(-1);
+		p = cursor();
+
+		if ( where & INTAG ) 
+		    flags = MKD_TAGTEXT;
+		else if ( where & INHEAD )
+		    flags = MKD_NOIMAGE|MKD_NOLINKS;
+		else
+		    flags = 0;
+
+		for (i=0; i < NR(keyword); i++)
+		    if ( thesame(p, keyword[i].kw) ) {
+			if ( keyword[i].where & where )
+			    (*keyword[i].what)(doc,output,flags,where);
+			break;
+		    }
+
+		while ( (c = pull()) != EOF && (c != '?' && peek(1) != '>') )
+		    ;
+		shift(1);
+	    }
+	    else
+		putc(c, output);
+
+	    if ( istag(cursor(), "head") ) {
+		where |= INHEAD;
+		where &= ~INBODY;
+	    }
+	    else if ( istag(cursor(), "body") ) {
+		where &= ~INHEAD;
+		where |= INBODY;
+	    }
+	    where |= INTAG;
+	    continue;
+	}
+	else if ( c == '>' )
+	    where &= ~INTAG;
+
+	putc(c, output);
+    }
+} /* spin */
+
+
+void
+main(argc, argv)
+char **argv;
+{
+    char *template = "page.theme";
+    char *source = "stdin";
+    FILE *tmplfile;
+    int opt;
+    int force = 0;
+    MMIOT *doc;
+    struct stat sourceinfo;
+
+    opterr=1;
+    pgm = basename(argv[0]);
+
+    while ( (opt=getopt(argc, argv, "fd:t:p:o:V")) != EOF ) {
+	switch (opt) {
+	case 'd':   root = optarg;
+		    break;
+	case 'p':   pagename = optarg;
+		    break;
+	case 'f':   force = 1;
+		    break;
+	case 't':   template = optarg;
+		    break;
+	case 'o':   output = optarg;
+		    break;
+	case 'V':   printf("theme+discount %s\n", markdown_version);
+		    exit(0);
+	default:    fprintf(stderr, "usage: %s [-V] [-d dir] [-p pagename] [-t template] [-o html] [file]\n", pgm);
+		    exit(1);
+	}
+    }
+
+    tmplfile = open_template(template);
+
+    argc -= optind;
+    argv += optind;
+
+
+    if ( argc > 0 ) {
+	int added_text=0;
+
+	if ( (source = malloc(strlen(argv[0]) + strlen("/index.text") + 1)) == 0 )
+	    fail("out of memory allocating name buffer");
+
+	strcpy(source,argv[0]);
+	if ( (stat(source, &sourceinfo) == 0) && S_ISDIR(sourceinfo.st_mode) )
+	    strcat(source, "/index");
+
+	if ( !freopen(source, "r", stdin) ) {
+	    strcat(source, ".text");
+	    added_text = 1;
+	    if ( !freopen(source, "r", stdin) )
+		fail("can't open either %s or %s", argv[0], source);
+	}
+
+	if ( !output ) {
+	    char *p, *q;
+	    output = alloca(strlen(source) + strlen(".html") + 1);
+
+	    strcpy(output, source);
+
+	    if (( p = strchr(output, '/') ))
+		q = strrchr(p+1, '.');
+	    else
+		q = strrchr(output, '.');
+
+	    if ( q )
+		*q = 0;
+	    else
+		q = output + strlen(output);
+
+	    strcat(q, ".html");
+	}
+    }
+    if ( output ) {
+	if ( force )
+	    unlink(output);
+	if ( !freopen(output, "w", stdout) )
+	    fail("can't write to %s", output);
+    }
+
+    if ( !pagename )
+	pagename = source;
+
+    if ( (doc = mkd_in(stdin, 0)) == 0 )
+	fail("can't read %s", source ? source : "stdin");
+
+    if ( fstat(fileno(stdin), &sourceinfo) == 0 )
+	infop = &sourceinfo;
+
+#if HAVE_GETPWUID
+    me = getpwuid(infop ? infop->st_uid : getuid());
+
+    if ( (root = strdup(me->pw_dir)) == 0 )
+	fail("out of memory");
+#endif
+
+    if ( !mkd_compile(doc, MKD_TOC) )
+	fail("couldn't compile input");
+
+    if ( tmplfile )
+	spin(tmplfile,doc,stdout);
+    else
+	mkd_generatehtml(doc, stdout);
+
+    mkd_cleanup(doc);
+    exit(0);
+}
diff --git a/toc.c b/toc.c
new file mode 100644
index 0000000..f790614
--- /dev/null
+++ b/toc.c
@@ -0,0 +1,101 @@
+/*
+ * toc -- spit out a table of contents based on header blocks
+ *
+ * Copyright (C) 2008 Jjgod Jiang, David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+/* write an header index
+ */
+int
+mkd_toc(Document *p, char **doc)
+{
+    Paragraph *tp, *srcp;
+    int last_hnumber = 0;
+    Cstring res;
+    int size;
+    
+    if ( !(doc && p && p->ctx) ) return -1;
+
+    *doc = 0;
+    
+    if ( ! (p->ctx->flags & MKD_TOC) ) return 0;
+
+    CREATE(res);
+    RESERVE(res, 100);
+
+    for ( tp = p->code; tp ; tp = tp->next ) {
+	if ( tp->typ == SOURCE ) {
+	    for ( srcp = tp->down; srcp; srcp = srcp->next ) {
+		if ( srcp->typ == HDR && srcp->text ) {
+	    
+		    if ( last_hnumber >= srcp->hnumber ) {
+			while ( last_hnumber > srcp->hnumber ) {
+			    Csprintf(&res, "%*s</ul></li>\n", last_hnumber-1,"");
+			    --last_hnumber;
+			}
+		    }
+
+		    while ( srcp->hnumber > last_hnumber ) {
+			Csprintf(&res, "%*s%s<ul>\n", last_hnumber, "",
+				    last_hnumber ? "<li>" : "");
+			++last_hnumber;
+		    }
+		    Csprintf(&res, "%*s<li><a href=\"#", srcp->hnumber, "");
+		    mkd_string_to_anchor(T(srcp->text->text),
+					 S(srcp->text->text), Csputc, &res,1);
+		    Csprintf(&res, "\">");
+		    mkd_string_to_anchor(T(srcp->text->text),
+					 S(srcp->text->text), Csputc, &res,0);
+		    Csprintf(&res, "</a>");
+		    Csprintf(&res, "</li>\n");
+		}
+	    }
+        }
+    }
+
+    while ( last_hnumber > 0 ) {
+	--last_hnumber;
+	Csprintf(&res, last_hnumber ? "%*s</ul></li>\n" : "%*s</ul>\n", last_hnumber, "");
+    }
+
+    if ( (size = S(res)) > 0 ) {
+	EXPAND(res) = 0;
+			/* HACK ALERT! HACK ALERT! HACK ALERT! */
+	*doc = T(res);  /* we know that a T(Cstring) is a character pointer
+			 * so we can simply pick it up and carry it away,
+			 * leaving the husk of the Ctring on the stack
+			 * END HACK ALERT
+			 */
+    }
+    else
+	DELETE(res);
+    return size;
+}
+
+
+/* write an header index
+ */
+int
+mkd_generatetoc(Document *p, FILE *out)
+{
+    char *buf = 0;
+    int sz = mkd_toc(p, &buf);
+    int ret = EOF;
+
+    if ( sz > 0 )
+	ret = fwrite(buf, 1, sz, out);
+
+    if ( buf ) free(buf);
+
+    return (ret == sz) ? ret : EOF;
+}
diff --git a/tools/checkbits.sh b/tools/checkbits.sh
new file mode 100755
index 0000000..afe2108
--- /dev/null
+++ b/tools/checkbits.sh
@@ -0,0 +1,11 @@
+#! /bin/sh
+
+trap "rm -f in.markdown.h in.mkdio.h" EXIT
+
+grep '#define MKD_' markdown.h | awk '$3 ~ /0x0/ {print $2,$3;}' > in.markdown.h
+grep '#define MKD_' mkdio.h | awk '$3 ~ /0x0/ {print $2,$3;}' > in.mkdio.h
+
+diff -c -bw in.markdown.h in.mkdio.h
+retcode=$?
+
+exit $retcode
diff --git a/tools/cols.c b/tools/cols.c
new file mode 100644
index 0000000..68ecc59
--- /dev/null
+++ b/tools/cols.c
@@ -0,0 +1,38 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+main(argc, argv)
+char **argv;
+{
+    register c;
+    int xp;
+    int width;
+
+    if ( argc != 2 ) {
+	fprintf(stderr, "usage: %s width\n", argv[0]);
+	exit(1);
+    }
+    else if ( (width=atoi(argv[1])) < 1 ) {
+	fprintf(stderr, "%s: please set width to > 0\n", argv[0]);
+	exit(1);
+    }
+
+
+    for ( xp = 1; (c = getchar()) != EOF; xp++ ) {
+	while ( c & 0xC0 ) {
+	    /* assume that (1) the output device understands utf-8, and
+	     *             (2) the only c & 0x80 input is utf-8.
+	     */
+	    do {
+		if ( xp <= width )
+		    putchar(c);
+	    } while ( (c = getchar()) != EOF && (c & 0x80) && !(c & 0x40) );
+	    ++xp;
+	}
+	if ( c == '\n' )
+	    xp = 0;
+	if ( xp <= width )
+	    putchar(c);
+    }
+    exit(0);
+}
diff --git a/tools/echo.c b/tools/echo.c
new file mode 100644
index 0000000..6ed30bd
--- /dev/null
+++ b/tools/echo.c
@@ -0,0 +1,23 @@
+#include <stdio.h>
+#include <string.h>
+
+
+main(argc, argv)
+char **argv;
+{
+    int nl = 1;
+    int i;
+
+    if ( (argc > 1) && (strcmp(argv[1], "-n") == 0) ) {
+	++argv;
+	--argc;
+	nl = 0;
+    }
+
+    for ( i=1; i < argc; i++ ) {
+	if ( i > 1 ) putchar(' ');
+	fputs(argv[i], stdout);
+    }
+    if (nl) putchar('\n');
+    exit(0);
+}
diff --git a/version.c.in b/version.c.in
new file mode 100644
index 0000000..219b935
--- /dev/null
+++ b/version.c.in
@@ -0,0 +1,22 @@
+#include "config.h"
+
+char markdown_version[] = VERSION
+#if @TABSTOP@ != 4
+		" TAB=@TABSTOP@"
+#endif
+#if USE_AMALLOC
+		" DEBUG"
+#endif
+#if USE_DISCOUNT_DL
+# if USE_EXTRA_DL
+		" DL=BOTH"
+# else
+		" DL=DISCOUNT"
+# endif
+#elif USE_EXTRA_DL
+		" DL=EXTRA"
+#else
+		" DL=NONE"
+#endif
+
+		;
diff --git a/xml.c b/xml.c
new file mode 100644
index 0000000..5e58389
--- /dev/null
+++ b/xml.c
@@ -0,0 +1,82 @@
+/* markdown: a C implementation of John Gruber's Markdown markup language.
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "config.h"
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+/* return the xml version of a character
+ */
+static char *
+mkd_xmlchar(unsigned char c)
+{
+    switch (c) {
+    case '<':   return "<";
+    case '>':   return ">";
+    case '&':   return "&";
+    case '"':   return """;
+    case '\'':  return "'";
+    default:    if ( isascii(c) || (c & 0x80) )
+		    return 0;
+		return "";
+    }
+}
+
+
+/* write output in XML format
+ */
+int
+mkd_generatexml(char *p, int size, FILE *out)
+{
+    unsigned char c;
+    char *entity;
+
+    while ( size-- > 0 ) {
+	c = *p++;
+
+	if ( entity = mkd_xmlchar(c) )
+	    fputs(entity, out);
+	else
+	    fputc(c, out);
+    }
+    return 0;
+}
+
+
+/* build a xml'ed version of a string
+ */
+int
+mkd_xml(char *p, int size, char **res)
+{
+    unsigned char c;
+    char *entity;
+    Cstring f;
+
+    CREATE(f);
+    RESERVE(f, 100);
+
+    while ( size-- > 0 ) {
+	c = *p++;
+	if ( entity = mkd_xmlchar(c) )
+	    Cswrite(&f, entity, strlen(entity));
+	else
+	    Csputc(c, &f);
+    }
+			/* HACK ALERT! HACK ALERT! HACK ALERT! */
+    *res = T(f);	/* we know that a T(Cstring) is a character pointer */
+			/* so we can simply pick it up and carry it away, */
+    return S(f);	/* leaving the husk of the Ctring on the stack */
+			/* END HACK ALERT */
+}
diff --git a/xmlpage.c b/xmlpage.c
new file mode 100644
index 0000000..96ed2b7
--- /dev/null
+++ b/xmlpage.c
@@ -0,0 +1,48 @@
+/*
+ * xmlpage -- write a skeletal xhtml page
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+
+int
+mkd_xhtmlpage(Document *p, int flags, FILE *out)
+{
+    char *title;
+    extern char *mkd_doc_title(Document *);
+    
+    if ( mkd_compile(p, flags) ) {
+	fprintf(out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+	fprintf(out, "<!DOCTYPE html "
+		     " PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\""
+		     " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n");
+
+	fprintf(out, "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n");
+
+	fprintf(out, "<head>\n");
+	if ( title = mkd_doc_title(p) )
+	    fprintf(out, "<title>%s</title>\n", title);
+	mkd_generatecss(p, out);
+	fprintf(out, "</head>\n");
+	
+	fprintf(out, "<body>\n");
+	mkd_generatehtml(p, out);
+	fprintf(out, "</body>\n");
+	fprintf(out, "</html>\n");
+	
+	mkd_cleanup(p);
+
+	return 0;
+    }
+    return -1;
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/discount.git



More information about the Reproducible-commits mailing list