Add a loom for generating Markdown output, originally written by Dirk Laurie.
diff --git a/anansi.cabal b/anansi.cabal
index 42bd1e4..0dc09c8 100644
--- a/anansi.cabal
+++ b/anansi.cabal
@@ -1,8 +1,8 @@
 name: anansi
-version: 0.4.1
+version: 0.4.2
 license: GPL-3
 license-file: license.txt
-author: John Millikin <jmillikin@gmail.com>
+author: John Millikin <jmillikin@gmail.com>, Dirk Laurie <dirk.laurie@gmail.com>
 maintainer: John Millikin <jmillikin@gmail.com>
 build-type: Simple
 cabal-version: >= 1.6
@@ -32,7 +32,7 @@
 source-repository this
   type: bazaar
   location: https://john-millikin.com/branches/anansi/0.4/
-  tag: anansi_0.4.1
+  tag: anansi_0.4.2
 
 library
   hs-source-dirs: lib
@@ -57,6 +57,7 @@
     Anansi.Loom.Debug
     Anansi.Loom.HTML
     Anansi.Loom.LaTeX
+    Anansi.Loom.Markdown
     Anansi.Loom.NoWeb
     Anansi.Main
     Anansi.Parser
diff --git a/lib/Anansi.hs b/lib/Anansi.hs
index 5e8b8f4..6c3846d 100644
--- a/lib/Anansi.hs
+++ b/lib/Anansi.hs
@@ -51,6 +51,7 @@
 	, loomDebug
 	, loomHTML
 	, loomLaTeX
+	, loomMarkdown
 	, loomNoWeb
 	) where
 
@@ -60,6 +61,7 @@
 import           Anansi.Loom.Debug
 import           Anansi.Loom.HTML
 import           Anansi.Loom.LaTeX
+import           Anansi.Loom.Markdown
 import           Anansi.Loom.NoWeb
 import           Anansi.Main
 import           Anansi.Parser
@@ -73,6 +75,7 @@
 --     [ (\"anansi.debug\", 'loomDebug')
 --     , (\"anansi.html\", 'loomHTML')
 --     , (\"anansi.latex\", 'loomLaTeX')
+--     , (\"anansi.markdown\", 'loomMarkdown')
 --     , (\"anansi.noweb\", 'loomNoWeb')
 --     ]
 -- @
@@ -81,5 +84,6 @@
 	[ ("anansi.debug", loomDebug)
 	, ("anansi.html", loomHTML)
 	, ("anansi.latex", loomLaTeX)
+	, ("anansi.markdown", loomMarkdown)
 	, ("anansi.noweb", loomNoWeb)
 	]
diff --git a/lib/Anansi/Loom/Markdown.hs b/lib/Anansi/Loom/Markdown.hs
new file mode 100644
index 0000000..ee918fc
--- /dev/null
+++ b/lib/Anansi/Loom/Markdown.hs
@@ -0,0 +1,82 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Copyright (C) 2010-2011 John Millikin <jmillikin@gmail.com>
+--               2011  Dirk Laurie <dirk.laurie@gmail.com>
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+module Anansi.Loom.Markdown (loomMarkdown) where
+
+import           Control.Monad (forM_)
+import           Control.Monad.Reader (asks)
+import           Control.Monad.Writer (tell)
+import           Data.ByteString (ByteString)
+import           Data.Monoid (mconcat)
+import qualified Data.Text
+import           Data.Text (Text)
+import           Data.Text.Encoding (encodeUtf8)
+
+import           Anansi.Types
+
+-- | Generate Markdown. Modified from LaTeX.hs.
+loomMarkdown :: Loom
+loomMarkdown = mapM_ putBlock . documentBlocks where
+	putBlock b = case b of
+		BlockText text -> tell (encodeUtf8 text)
+		BlockFile path content -> do
+			tell "\n> **\xC2\xBB "
+			tell =<< escapeText path
+			tell "**\n\n"
+			putContent content
+			tell "\n"
+		BlockDefine name content -> do
+			tell "\n> **\xC2\xAB"
+			tell =<< escapeText name
+			tell "\xC2\xBB**\n\n"
+			putContent content
+			tell "\n"
+	
+	putContent cs = forM_ cs $ \c -> case c of
+		ContentText _ text -> do
+			tell ">     "
+			escapeCode text >>= tell
+			tell "\n"
+		ContentMacro _ indent name -> formatMacro indent name >>= tell
+	
+	formatMacro indent name = do
+		escIndent <- escapeText indent
+		escName <- escapeText name
+		return (mconcat [">     ", escIndent, "\xC2\xAB", escName, "\xC2\xBB\n"])
+
+escapeCode :: Text -> LoomM ByteString
+escapeCode text = do
+	tabSize <- asks loomOptionTabSize
+	return $ encodeUtf8 $ Data.Text.concatMap (\c -> case c of
+		'\t' -> Data.Text.replicate (fromInteger tabSize) " "
+		_ -> Data.Text.singleton c) text
+
+escapeText ::  Text -> LoomM ByteString
+escapeText text = do
+	tabSize <- asks loomOptionTabSize
+	
+	return $ encodeUtf8 $ Data.Text.concatMap (\c -> case c of
+		'\t' -> Data.Text.replicate (fromInteger tabSize) " "
+		'\\' -> "\\\\"
+		'_' -> "\\_"
+		'*' -> "\\*"
+		'[' -> "\\["
+		']' -> "\\]"
+		'`' -> "\\`"
+		'&' -> "&amp;"
+		_ -> Data.Text.singleton c) text
diff --git a/tests/Tests.hs b/tests/Tests.hs
index 6fc58f4..5180b24 100644
--- a/tests/Tests.hs
+++ b/tests/Tests.hs
@@ -286,6 +286,7 @@
 	[ test_WeaveDebug
 	, test_WeaveHtml
 	, test_WeaveLatex
+	, test_WeaveMarkdown
 	, test_WeaveNoweb
 	, test_ParseLoomOptions
 	]
@@ -379,6 +380,40 @@
 		\  |\\emph{bar}|\n\
 		\\\end{alltt}\n"
 
+test_WeaveMarkdown :: Suite
+test_WeaveMarkdown = assertions "markdown" $ do
+	$expect $ equalWeave loomMarkdown Map.empty
+		[]
+		""
+	$expect $ equalWeave loomMarkdown Map.empty
+		[ BlockText "foo"
+		, BlockText " _ * \\ & ` []() "
+		, BlockText "bar\n"
+		, BlockFile "file-1.hs" []
+		, BlockDefine "macro _ * \\ & ` []() 2"
+			[ ContentText (Position "test" 0) "\tfoo"
+			]
+		, BlockFile "file-2.hs"
+			[ ContentText (Position "test" 0) "foo _ * \\ & ` []() bar"
+			, ContentMacro (Position "test" 0) "  " "bar"
+			]
+		]
+		"foo _ * \\ & ` []() bar\n\
+		\\n\
+		\> **\xC2\xBB file-1.hs**\n\
+		\\n\
+		\\n\
+		\\n\
+		\> **\xC2\xAB\&macro \\_ \\* \\\\ &amp; \\` \\[\\]() 2\xC2\xBB**\n\
+		\\n\
+		\>             foo\n\
+		\\n\
+		\\n\
+		\> **\xC2\xBB file-2.hs**\n\
+		\\n\
+		\>     foo _ * \\ & ` []() bar\n\
+		\>       \xC2\xAB\&bar\xC2\xBB\n\
+		\\n"
 
 test_WeaveNoweb :: Suite
 test_WeaveNoweb = assertions "noweb" $ do
@@ -413,7 +448,7 @@
 		\\\nwendcode{}\n"
 
 equalWeave :: Loom -> Map.Map Text Text -> [Block] -> ByteString -> Assertion
-equalWeave loom opts blocks = equalLines (weave loom doc) where
+equalWeave loom opts blocks expected = equalLines expected (weave loom doc) where
 	doc = Document
 		{ documentBlocks = blocks
 		, documentOptions = opts