diff --git a/Makefile b/Makefile
new file mode 100644
index 000000000..3e2948044
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,6 @@
+all:
+ ./ikiwiki doc html
+
+clean:
+ rm -rf html
+ rm -f doc/.index
diff --git a/doc/ikiwiki.mdwn b/doc/ikiwiki.mdwn
new file mode 100644
index 000000000..ad5485bb7
--- /dev/null
+++ b/doc/ikiwiki.mdwn
@@ -0,0 +1,7 @@
+IkiWiki is the engine driving this wiki, which exists to document ikiWiki.
+The [[index]] is where you'll find actual useful info about it.
+
+Why call it IkiWiki? Well, partly because I'm sure some people will find
+this a pretty Iky Wiki, since it's so different from other Wikis. Partly
+because "ikiwiki" is a nice palindrome. Partly because its design turns
+the usual design for a Wiki inside-out and backwards.
diff --git a/doc/index.mdwn b/doc/index.mdwn
new file mode 100644
index 000000000..327e685af
--- /dev/null
+++ b/doc/index.mdwn
@@ -0,0 +1,36 @@
+[[Ikiwiki]] is a wiki compiler. It converts a directory full of wiki pages
+into html pages suitable for publishing on a website. Unlike a traditional
+wiki, ikiwiki does not have its own means of storing page history, its own
+markup language, or support for editing pages online.
+
+To use [[ikiwiki]] to set up a wiki, you will probably want to use it with a
+revision control system, such as [[Subversion]], for keeping track of past
+versions of pages. ikiwiki can run as a Subversion post-commit hook, so
+that each committed change to your wiki is immediatly compiled and
+published. (It can also be run by hand, by cron, or integrated with any
+other revision control system.)
+
+[[Subversion]] also offers a way to let others edit pages on your wiki.
+Just configure subversion to let appropriate users (or everyone) commit to
+the wiki's repository. There are some things you should keep in mind about
+[[Security]] when allowing the world to edit your ikiwiki.
+
+ikiwiki supports pages using [[MarkDown]] as their markup language. Any
+page with a filename ending in ".mdwn" is converted from markdown to html
+by ikiwiki. Markdown understands text formatted as it would be in an email,
+and is quite smart about converting it to html. The only additional markup
+provided by ikiwiki aside from regular markdown is the [[WikiLink]].
+
+ikiwiki also supports files of any other type, including raw html, text,
+images, etc. These are not converted to wiki pages, they are just copied
+unchanged by ikiwiki as it builds your wiki. So you can check in an image,
+program, or other special file and link to it from your wiki pages.
+ikiwiki also supports making one page that is a [[SubPage]] of another.
+
+[[TODO]] lists things that need to be added to ikiwiki before most people
+would consider it a full-fledged wiki.
+
+All wikis are supposed to have a [[SandBox]], so this one does to.
+If you'd like to try editing pages on this wiki, do whatever you'd like in
+
+[[ikiwiki]] is developed by JoeyHess.
diff --git a/doc/joeyhess.mdwn b/doc/joeyhess.mdwn
new file mode 100644
index 000000000..c60c76fe8
--- /dev/null
+++ b/doc/joeyhess.mdwn
@@ -0,0 +1,6 @@
+Joey Hess is joey@kitenet.net.
+His web page is [here](http://kitenet.net/~joey/).
+
+Joey hates programming web crap, and hates being locked into a web browser
+to do something, and this probably shows in the design choices made in
+ikiwiki.
diff --git a/doc/markdown.mdwn b/doc/markdown.mdwn
new file mode 100644
index 000000000..9a0fff198
--- /dev/null
+++ b/doc/markdown.mdwn
@@ -0,0 +1,9 @@
+[Markdown](http://daringfireball.net/projects/markdown/)
+is a minimal markup language that resembles plain text as used in
+email messages. It is the markup language used by this wiki.
+
+For documentation about the markdown syntax, see
+[Markdown: syntax](http://daringfireball.net/projects/markdown/syntax).
+
+Note that [[WikiLink]]s are not part of the markdown syntax, and are the
+only bit of markup that this wiki handles internally.
diff --git a/doc/sandbox.mdwn b/doc/sandbox.mdwn
new file mode 100644
index 000000000..be5c51496
--- /dev/null
+++ b/doc/sandbox.mdwn
@@ -0,0 +1,35 @@
+This is the SandBox, a page anyone can edit to try out ikiwiki.
+
+See [[MarkDown]] for documentation of the markup syntax used on this page.
+
+----
+
+Here's a paragraph.
+
+Here's another one.
+
+# Header
+
+## Subheader
+
+> This is a blockquote.
+>
+> This is the first level of quoting.
+>
+> > This is nested blockquote.
+>
+> Back to the first level.
+
+Numbered list
+
+1. First item.
+1. Another.
+1. And another..
+
+Bulleted list
+
+* item
+* item
+* item
+
+Link back to the [[index]].
diff --git a/doc/security.mdwn b/doc/security.mdwn
new file mode 100644
index 000000000..575ccbad8
--- /dev/null
+++ b/doc/security.mdwn
@@ -0,0 +1,39 @@
+If you are using ikiwiki to render pages that only you can edit, then there
+are no more security issues with this program than with cat(1). If,
+however, you let others edit pages in your wiki, then some security issues
+do need to be kept in mind.
+
+## html attacks
+
+ikiwiki does not attempt to do any santization of the html on the wiki.
+MarkDown allows embedding of arbitrary html into a markdown document. If
+you let anyone else edit files on the wiki, then anyone can have fun exploiting
+the web browser bug of the day. This type of attack is typically referred
+to as an XSS attack ([google](http://www.google.com/search?q=xss+attack)).
+
+## image files etc attacks
+
+If it enounters a file type it does not understand, ikiwiki just copies it
+into place. So if you let users add any kind of file they like, they can
+upload images, movies, windows executables, etc. If these files exploit
+security holes in the browser of someone who's viewing the wiki, that can
+be a security problem.
+
+## exploting ikiwiki with bad content
+
+Someone could add bad content to the wiki and hope to exploit ikiwiki.
+Note that ikiwiki runs with perl taint checks on, so this is unlikely;
+the only data that is not subject to full taint checking is the names of
+files, and filenames are sanitised.
+
+## cgi scripts
+
+ikiwiki does not allow cgi scripts to be published as part of the wiki. Or
+rather, the script is published, but it's not marked executable, so
+hopefully your web server will not run it.
+
+## web server attacks
+
+If your web server does any parsing of special sorts of files (for example,
+server parsed html files), then if you let anyone else add files to the wiki,
+they can try to use this to exploit your web server.
diff --git a/doc/subpage.mdwn b/doc/subpage.mdwn
new file mode 100644
index 000000000..88a964ac2
--- /dev/null
+++ b/doc/subpage.mdwn
@@ -0,0 +1,11 @@
+[[ikiwiki]] supports placing pages in a directory hierarchy. For example,
+this page, [[SubPage]] has some related pages placed under it, like
+[[SubPage/LinkingRules]]. This is a useful way to add some order to your
+wiki rather than just having a great big directory full of pages.
+
+To add a SubPage, just make a subdirectory and put pages in it. For
+example, this page is SubPage.mdwn in this wiki's source, and there is also
+a SubPage subdirectory, which contains SubPage/LinkingRules.mdwn. Subpages
+can be nested as deeply as you'd like.
+
+Linking to and from a SubPage is explained in [[LinkingRules]].
diff --git a/doc/subpage/linkingrules.mdwn b/doc/subpage/linkingrules.mdwn
new file mode 100644
index 000000000..83625ccbd
--- /dev/null
+++ b/doc/subpage/linkingrules.mdwn
@@ -0,0 +1,21 @@
+To link to or from a [[SubPage]], you can normally use a regular
+[[WikiLink]] that does not contain the name of the parent directory of
+the [[SubPage]]. Ikiwiki descends the directory hierarchy looking for a
+page that matches your link.
+
+For example, if FooBar/SubPage links to "OtherPage", ikiwiki will first
+prefer pointing the link to FooBar/SubPage/OtherPage if it exists, next
+to FooBar/OtherPage and finally to OtherPage in the root of the wiki.
+
+Note that this means that if a link on FooBar/SomePage to "OtherPage"
+currently links to OtherPage, in the root of the wiki, and FooBar/OtherPage
+is created, the link will _change_ to point to FooBar/OtherPage. On the
+other hand, a link from BazBar to "OtherPage" would be unchanged by this
+creation of a [[SubPage]] of FooBar.
+
+You can also specify a link that contains a directory name, like
+"FooBar/OtherPage" to more exactly specify what page to link to. This is
+the only way to link to an unrelated [[SubPage]].
+
+You can use this to, for example, to link from BazBar to "FooBar/SubPage",
+or from BazBar/SubPage to "FooBar/SubPage".
diff --git a/doc/subversion.mdwn b/doc/subversion.mdwn
new file mode 100644
index 000000000..9c13dcabe
--- /dev/null
+++ b/doc/subversion.mdwn
@@ -0,0 +1,3 @@
+Subversion is a revision control system. While ikiwiki is relatively
+independant of the underlying revision control system, using it with
+Subversion is recommended.
diff --git a/doc/todo.mdwn b/doc/todo.mdwn
new file mode 100644
index 000000000..f252890ac
--- /dev/null
+++ b/doc/todo.mdwn
@@ -0,0 +1,43 @@
+## online page editing
+
+To support editing pages in a web browser, a CGI script is needed that
+pulls the page out of [[Subversion]], presents it to the user for editing,
+and then commits the changed page back to [[Subversion]].
+
+Due to [[WikiSpam]], this will probably also need to incorporate a user
+registration system. So there will need to be a script that handles logins
+and registrations, sets a cookie, and the page editor can refuse to edit
+pages for users who arn't logged in, and include a not of who made the
+change in the svn log.
+
+If possible I'd prefer to use someone else's generic web user registration
+and login system, if one exists.
+
+## [[RecentChanges]]
+
+This will need to be another cgi script, that grubs through the
+[[Subversion]] logs.
+
+This should support RSS for notification of new and changed pages.
+
+## page history
+
+To see past versions of a page, we can either implement a browser for that,
+or just provide a way to link to the page in viewcvs.
+
+## pluggable renderers
+
+I'm considering a configurable rendering pipeline for each supported
+filename extension. So for ".mdwn" files, it would send the content through
+linkify, markdown, and finalize, while for ".wiki" files it might send it
+through just a wiki formatter and finalize.
+
+This would allow not only supporting more types of markup, but changing
+what style of [[WikiLink]]s are supported, maybe some people want to add
+[[CamelCase]] for example, or don't like the [[SubPage/LinkingRules]].
+
+The finalize step is where the page gets all the pretty junk around the
+edges, so that clearly needs to be pluggable too.
+
+There could also be a step before finalize, where stuff like lists of pages
+that linked back to it could be added to the page.
diff --git a/doc/wikilink.mdwn b/doc/wikilink.mdwn
new file mode 100644
index 000000000..7891add7c
--- /dev/null
+++ b/doc/wikilink.mdwn
@@ -0,0 +1,9 @@
+WikiLinks provide easy linking between pages of the wiki. To create a
+WikiLink, just put the name of the page to link to in double brackets. For
+examples "[[ WikiLink ]]" (without the added whitespace).
+
+Note that there are some special [[SubPage/LinkingRules]] that come into
+play when linking between [[SubPage]]s.
+
+WikiLinks can be entered in any case you like, the page they link to is
+always lowercased.
diff --git a/ikiwiki b/ikiwiki
new file mode 100755
index 000000000..4d736c613
--- /dev/null
+++ b/ikiwiki
@@ -0,0 +1,340 @@
+#!/usr/bin/perl -T
+
+use warnings;
+use strict;
+use File::Find;
+use Memoize;
+use File::Spec;
+
+BEGIN {
+ $blosxom::version="is a proper perl module too much to ask?";
+ do "/usr/bin/markdown";
+}
+
+memoize('pagename');
+memoize('bestlink');
+
+my ($srcdir)= shift =~ /(.*)/; # untaint
+my ($destdir)= shift =~ /(.*)/; # untaint
+my $link=qr/\[\[([^\s]+)\]\]/;
+my $verbose=1;
+
+my %links;
+my %oldpagemtime;
+my %renderedfiles;
+
+sub error ($) {
+ die @_;
+}
+
+sub debug ($) {
+ print "@_\n" if $verbose;
+}
+
+sub mtime ($) {
+ my $page=shift;
+
+ return (stat($page))[9];
+}
+
+sub basename {
+ my $file=shift;
+
+ $file=~s!.*/!!;
+ return $file;
+}
+
+sub dirname {
+ my $file=shift;
+
+ $file=~s!/?[^/]+$!!;
+ return $file;
+}
+
+sub pagetype ($) {
+ my $page=shift;
+
+ if ($page =~ /\.mdwn$/) {
+ return ".mdwn";
+ }
+ else {
+ return "unknown";
+ }
+}
+
+sub pagename ($) {
+ my $file=shift;
+
+ my $type=pagetype($file);
+ my $page=$file;
+ $page=~s/\Q$type\E*$// unless $type eq 'unknown';
+ return $page;
+}
+
+sub htmlpage ($) {
+ my $page=shift;
+
+ return $page.".html";
+}
+
+sub readpage ($) {
+ my $page=shift;
+
+ local $/=undef;
+ open (PAGE, "$srcdir/$page") || error("failed to read $page: $!");
+ my $ret=;
+ close PAGE;
+ return $ret;
+}
+
+sub writepage ($$) {
+ my $page=shift;
+ my $content=shift;
+
+ my $dir=dirname("$destdir/$page");
+ if (! -d $dir) {
+ my $d="";
+ foreach my $s (split(m!/+!, $dir)) {
+ $d.="$s/";
+ if (! -d $d) {
+ mkdir($d) || error("failed to create directory $d: $!");
+ }
+ }
+ }
+
+ open (PAGE, ">$destdir/$page") || error("failed to write $page: $!");
+ print PAGE $content;
+ close PAGE;
+}
+
+sub findlinks {
+ my $content=shift;
+
+ my @links;
+ while ($content =~ /$link/g) {
+ push @links, lc($1);
+ }
+ return @links;
+}
+
+# Given a page and the text of a link on the page, determine which existing
+# page that link best points to. Prefers pages under a subdirectory with
+# the same name as the source page, failing that goes down the directory tree
+# to the base looking for matching pages.
+sub bestlink ($$) {
+ my $page=shift;
+ my $link=lc(shift);
+
+ my $cwd=$page;
+ do {
+ my $l=$cwd;
+ $l.="/" if length $l;
+ $l.=$link;
+
+ if (exists $links{$l}) {
+ #debug("for $page, \"$link\", use $l");
+ return $l;
+ }
+ } while $cwd=~s!/?[^/]+$!!;
+
+ print STDERR "warning: page $page, broken link: $link\n";
+ return "";
+}
+
+sub isinlinableimage ($) {
+ my $file=shift;
+
+ $file=~/\.(png|gif|jpg|jpeg)$/;
+}
+
+sub htmllink ($$) {
+ my $page=shift;
+ my $link=shift;
+
+ my $bestlink=bestlink($page, $link);
+
+ return $page if $page eq $bestlink;
+
+ if (! grep { $_ eq $bestlink } values %renderedfiles) {
+ $bestlink=htmlpage($bestlink);
+ }
+ if (! grep { $_ eq $bestlink } values %renderedfiles) {
+ return "?$link"
+ }
+
+ $bestlink=File::Spec->abs2rel($bestlink, dirname($page));
+
+ if (isinlinableimage($bestlink)) {
+ return "";
+ }
+ return "$link";
+}
+
+sub linkify ($$) {
+ my $content=shift;
+ my $file=shift;
+
+ $content =~ s/$link/htmllink(pagename($file), $1)/eg;
+
+ return $content;
+}
+
+sub htmlize ($$) {
+ my $type=shift;
+ my $content=shift;
+
+ if ($type eq '.mdwn') {
+ return Markdown::Markdown($content);
+ }
+ else {
+ error("htmlization of $type not supported");
+ }
+}
+
+sub finalize ($$) {
+ my $content=shift;
+ my $page=shift;
+
+ my $title=basename($page);
+ $title=~s/_/ /g;
+
+ $content="\n$title\n\n".
+ $content.
+ "\n\n";
+
+ return $content;
+}
+
+sub render ($) {
+ my $file=shift;
+
+ my $type=pagetype($file);
+ my $content=readpage($file);
+ if ($type ne 'unknown') {
+ my $page=pagename($file);
+ $links{$page}=[findlinks($content)];
+
+ $content=linkify($content, $file);
+ $content=htmlize($type, $content);
+ $content=finalize($content, $page);
+
+ writepage(htmlpage($page), $content);
+ $oldpagemtime{$page}=time;
+ $renderedfiles{$page}=htmlpage($page);
+ }
+ else {
+ $links{$file}=[];
+ writepage($file, $content);
+ $oldpagemtime{$file}=time;
+ $renderedfiles{$file}=$file;
+ }
+}
+
+sub loadindex () {
+ open (IN, "$srcdir/.index") || return;
+ while () {
+ chomp;
+ my ($mtime, $page, $rendered, @links)=split(' ', $_);
+ $oldpagemtime{$page}=$mtime;
+ $links{$page}=\@links;
+ ($renderedfiles{$page})=$rendered=~m/(.*)/; # untaint
+ }
+ close IN;
+}
+
+sub saveindex () {
+ open (OUT, ">$srcdir/.index") || error("cannot write to .index: $!");
+ foreach my $page (keys %oldpagemtime) {
+ print OUT "$oldpagemtime{$page} $page $renderedfiles{$page} ".
+ join(" ", @{$links{$page}})."\n"
+ if $oldpagemtime{$page};
+ }
+ close OUT;
+}
+
+sub prune ($) {
+ my $file=shift;
+
+ unlink($file);
+ my $dir=dirname($file);
+ while (rmdir($dir)) {
+ $dir=dirname($dir);
+ }
+}
+
+sub refresh () {
+ # Find existing pages.
+ my %exists;
+ my @files;
+ find({
+ no_chdir => 1,
+ wanted => sub {
+ if (/\/\.svn\//) {
+ $File::Find::prune=1;
+ }
+ elsif (! -d $_ && ! /\.html$/ && ! /\/\./) {
+ my ($f)=/(^[-A-Za-z0-9_.:\/+]+$)/; # untaint
+ if (! defined $f) {
+ warn("skipping bad filename $_\n");
+ }
+ else {
+ $f=~s/^\Q$srcdir\E\/?//;
+ push @files, $f;
+ $exists{pagename($f)}=1;
+ }
+ }
+ },
+ }, $srcdir);
+
+ # check for added or removed pages
+ my @adddel;
+ foreach my $file (@files) {
+ my $page=pagename($file);
+ if (! $oldpagemtime{$page}) {
+ debug("new page $page");
+ push @adddel, $page;
+ $links{$page}=[];
+ }
+ }
+ foreach my $page (keys %oldpagemtime) {
+ if (! $exists{$page}) {
+ debug("removing old page $page");
+ prune($destdir."/".$renderedfiles{$page});
+ delete $renderedfiles{$page};
+ $oldpagemtime{$page}=0;
+ push @adddel, $page;
+ }
+ }
+
+ # render any updated files
+ foreach my $file (@files) {
+ my $page=pagename($file);
+
+ if (! exists $oldpagemtime{$page} ||
+ mtime("$srcdir/$file") > $oldpagemtime{$page}) {
+ debug("rendering changed file $file");
+ render($file);
+ }
+ }
+
+ # if any files were added or removed, check to see if each page
+ # needs an update due to linking to them
+ if (@adddel) {
+FILE: foreach my $file (@files) {
+ my $page=pagename($file);
+ foreach my $p (@adddel) {
+ foreach my $link (@{$links{$page}}) {
+ if (bestlink($page, $link) eq $p) {
+ debug("rendering $file, which links to $p");
+ render($file);
+ next FILE;
+ }
+ }
+ }
+ }
+ }
+}
+
+loadindex();
+refresh();
+saveindex();