diff --git a/CHANGES.txt b/CHANGES.txt
new file mode 100644
--- /dev/null
+++ b/CHANGES.txt
@@ -0,0 +1,8 @@
+Changelog for Neil
+
+2012.10.29
+    Baby
+2009.9.19
+    Married
+1981.12.7
+    Born
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) Neil Mitchell 2010-2014
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * The names of its contributors may not be used to endorse or 
+      promote products derived from this software without specific prior 
+      written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/neil.cabal b/neil.cabal
new file mode 100644
--- /dev/null
+++ b/neil.cabal
@@ -0,0 +1,80 @@
+cabal-version:      >= 1.6
+build-type:         Simple
+name:               neil
+version:            0.0
+license:            BSD3
+license-file:       LICENSE
+category:           Development
+author:             Neil Mitchell <ndmitchell@gmail.com>
+maintainer:         Neil Mitchell <ndmitchell@gmail.com>
+copyright:          Neil Mitchell 2010-2014
+synopsis:           General tools for Neil
+description:
+    General tools for Neil.
+homepage:           http://community.haskell.org/~ndm/
+bug-reports:        https://github.com/ndmitchell/neil/issues
+tested-with:        GHC==7.8.2, GHC==7.6.3, GHC==7.4.2, GHC==7.2.2
+extra-source-files:
+    CHANGES.txt
+
+source-repository head
+    type:     git
+    location: https://github.com/ndmitchell/neil.git
+
+library
+    hs-source-dirs:     src
+    build-depends:      base == 4.*, filepath, directory, time, process, extra
+
+    exposed-modules:
+        Neil
+    other-modules:
+        Util
+
+flag small
+    default: False
+    description: Minimise dependencies
+
+executable neil
+    hs-source-dirs:     src
+    build-depends:      containers, cmdargs
+    if flag(small)
+        cpp-options: -DSMALL
+    else
+        build-depends: json, GoogleChart, old-time
+
+    main-is:            Main.hs
+
+    other-modules:
+        Paper.LatexError
+        Paper.Haskell.Latex.Parser
+        Paper.Haskell.Haskell.Provides
+        Paper.Haskell.Haskell.Tweak
+        Paper.Haskell.Fragment
+        Paper.Haskell.Check
+        Paper.Util.String
+        Paper.Util.CmdNumber
+        Paper.Haskell2.Haskell
+        Paper.Haskell2.Type
+        Paper.Haskell2.Stage3
+        Paper.Haskell2.Stage2
+        Paper.Util.IO
+        Paper.Util.Error
+        Paper.Haskell2.Stage1
+        Paper.Todo
+        Paper.Ref
+        Paper.Ftp
+        Paper.Sync
+        Paper.Push
+        Paper.Haskell2.All
+        Paper.WordCount
+        Paper.Talk
+        Paper.Make
+        Paper.Graph
+        Paper.Util.FileData
+        Paper.Haskell
+        Paper.Main
+        Arguments
+        Util
+        Cabal
+        Git
+        Travis
diff --git a/src/Arguments.hs b/src/Arguments.hs
new file mode 100644
--- /dev/null
+++ b/src/Arguments.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# OPTIONS_GHC -fno-warn-missing-fields #-}
+
+module Arguments where
+
+import System.Console.CmdArgs
+
+data Arguments
+    -- darcs stuff
+    = Whatsnew {repo :: FilePath, delete_locks :: Bool, local :: Bool, look_for_adds :: Bool, ssh :: Bool}
+    | Pull {repo :: FilePath, delete_locks :: Bool}
+    | Push {repo :: FilePath, ssh :: Bool}
+    | Send {repo :: FilePath, patch :: FilePath}
+    | Apply {patch :: FilePath}
+    | Tag
+    | Docs {username :: String}
+
+    | Travis {wait :: Double}
+ 
+    -- cabal stuff
+    | Sdist
+    | Check
+    | Test {install :: Bool}
+      deriving (Data,Typeable,Show)
+
+arguments = cmdArgsMode $ modes
+    [Whatsnew {repo = "." &= typDir &= help "Repo to use"
+              ,delete_locks = False &= help "Delete lock files"
+              ,local = False &= help "Only check for local changes, no network required"
+              ,look_for_adds = False &= name "l" &= help "Look for files to add"
+              ,ssh = False &= help "SSH connections only, for if the http is down"}
+              &= help "See what has changed (local and remote changes)"
+    ,Pull {}
+          &= help "Pull from the default locations"
+    ,Push {}
+          &= help "Push to the calculated SSH location"
+    ,Send {patch="patches.tar" &= typFile &= help "Location for patches"}
+          &= help "Send patches as a tarball"
+    ,Apply {}
+           &= help "Apply a patch tarball"
+    ,Sdist {}
+          &= help "Create a cabal sdist with extra checks"
+    ,Tag {} &= help "Tag the repo, after a release"
+    ,Check
+    ,Docs {username = "NeilMitchell"}
+    ,Travis {wait = 0 &= help "Time to wait after each wget request"}
+    ,Test {install = False &= help "Install after building"}
+    ]
+    &= summary "Neil's utility tool"
diff --git a/src/Cabal.hs b/src/Cabal.hs
new file mode 100644
--- /dev/null
+++ b/src/Cabal.hs
@@ -0,0 +1,192 @@
+{-# LANGUAGE RecordWildCards, PatternGuards, ViewPatterns #-}
+
+module Cabal(run, readCabal) where
+
+import Control.Monad.Extra
+import Data.Char
+import Data.List.Extra
+import Data.Maybe
+import Data.Functor
+import System.Directory.Extra
+import System.IO.Extra
+import System.FilePath
+import Util
+import Arguments
+
+defAllow = ["7.0.4","7.2.2","7.4.2","7.6.3","7.8.2"]
+
+
+---------------------------------------------------------------------
+-- COMMANDS
+
+
+-- | Check the .cabal file is well formed
+cabalCheck :: IO ()
+cabalCheck = do
+    res <- cmdCode "cabal check"
+    checkCabalFile
+    checkReadme
+    let require = ":set -fwarn-unused-binds -fwarn-unused-imports"
+    src <- readFile' ".ghci"
+    when (require `notElem` lines src) $
+        error $ "The .ghci file does not contain " ++ require
+
+-- | Run some commands in a temporary directory with the unpacked cabal
+withSDist :: IO a -> IO a
+withSDist run = withTempDir $ \tdir -> do
+    cmd $ "cabal configure --builddir=" ++ tdir
+    cmd $ "cabal sdist --builddir=" ++ tdir
+    files <- getDirectoryContents tdir
+    let tarball = head $ [x | x <- files, ".tar.gz" `isSuffixOf` x]
+    withCurrentDirectory tdir $ cmd $ "tar -xf " ++ tarball
+    lst <- getDirectoryContentsRecursive tdir
+    let binary = [".png",".gz",".bat",".zip",".gif",""]
+    bad <- flip filterM lst $ \file ->
+        return (takeExtension file `notElem` binary) &&^
+        fmap ('\r' `elem`) (readFileBinary' file)
+    when (bad /= []) $ do
+        error $ unlines $ "The following files have \\r characters in, Windows newlines?" : bad
+    withCurrentDirectory (tdir </> dropExtension (dropExtension $ takeFileName tarball)) run
+
+
+run :: Arguments -> Maybe (IO ())
+run Test{..} = Just $ do
+    cabalCheck
+    withSDist $ do
+        cmd "cabal install --only-dependencies"
+        cmd $ "cabal configure --enable-tests --disable-library-profiling " ++
+              "--ghc-option=-fwarn-unused-binds --ghc-option=-fwarn-unused-imports " ++
+              "--ghc-option=-Werror --ghc-option=-fno-warn-warnings-deprecations" -- CABAL BUG WORKAROUND :(
+        cmd "cabal build"
+        cmd "cabal test --show-details=always"
+        when install $
+            cmd "cabal install --force-reinstalls"
+
+run Check = Just cabalCheck
+
+run Sdist{..} = Just $ do
+    cabalCheck
+    tested <- testedWith
+    withSDist $ do
+        forM_ (sort tested) $ \x -> do -- deliberately start with the oldest first
+            putStrLn $ "Building with " ++ x
+            cmd "cabal clean"
+            cmd $ "cabal install --only-dependencies " ++
+                  "--with-compiler=c:\\ghc\\ghc-" ++ x ++ "\\bin\\ghc.exe --with-haddock=c:\\ghc\\ghc-" ++ x ++ "\\bin\\haddock.exe " ++
+                  "--with-hc-pkg=c:\\ghc\\ghc-" ++ x ++ "\\bin\\ghc-pkg.exe " ++
+                  "--flags=testprog"
+            cmd $ "cabal configure --ghc-option=-fwarn-unused-imports --disable-library-profiling " ++
+                  "--ghc-option=-Werror --ghc-option=-fno-warn-warnings-deprecations " ++ -- CABAL BUG WORKAROUND :(
+                  "--with-compiler=c:\\ghc\\ghc-" ++ x ++ "\\bin\\ghc.exe --with-haddock=c:\\ghc\\ghc-" ++ x ++ "\\bin\\haddock.exe " ++
+                  "--with-hc-pkg=c:\\ghc\\ghc-" ++ x ++ "\\bin\\ghc-pkg.exe " ++
+                  "--flags=testprog"
+            cmd "cabal build"
+            cmd "cabal haddock --executables"
+    cmd "cabal sdist"
+    putStrLn $ "Ready to release! (remember to neil tag after uploading)"
+
+run Docs{..} = Just $ do
+    src <- readCabal
+    let [ver] = [strip $ drop 8 x | x <- lines src, "version:" `isPrefixOf` x]
+    let [name] = [strip $ drop 5 x | x <- lines src, "name:" `isPrefixOf` x]
+    cmd $ "cabal haddock --hoogle --hyperlink-source " ++
+          "--contents-location=/package/" ++ name
+    withTempDir $ \dir -> do
+        cmd $ "cp -R dist/doc/html/" ++ name ++ " \"" ++ dir ++ "/" ++ name ++ "-" ++ ver ++ "-docs"
+        files <- getDirectoryContentsRecursive dir
+        forM_ files $ \file -> when (takeExtension file == ".html") $ do
+            src <- readFileBinary' $ dir </> file
+            src <- return $ filter (/= '\r') src -- filter out \r, due to CPP bugs
+            src <- return $ fixFileLinks $ fixHashT src
+            writeFileBinary (dir </> file) src
+        cmd $ "tar cvz -C " ++ dir ++ " --format=ustar -f " ++ dir ++ "/" ++ name ++ "-" ++ ver ++ "-docs.tar.gz " ++ name ++ "-" ++ ver ++ "-docs"
+        cmd $ "curl -X PUT -H \"Content-Type: application/x-tar\" " ++
+              "-H \"Content-Encoding: gzip\" " ++
+              "-u " ++ username ++ " " ++
+              "--data-binary \"@" ++ dir ++ "/" ++ name ++ "-" ++ ver ++ "-docs.tar.gz\" " ++
+              "https://hackage.haskell.org/package/" ++ name ++ "-" ++ ver ++ "/docs"
+
+run _ = Nothing
+
+
+fixHashT :: String -> String
+fixHashT (stripPrefix ".html#t:" -> Just (x:xs)) | not $ isUpper x = ".html#v:" ++ fixHashT (x:xs)
+fixHashT (x:xs) = x : fixHashT xs
+fixHashT [] = []
+
+fixFileLinks :: String -> String
+fixFileLinks (stripPrefix "<a href=\"file://" -> Just xs)
+    | (a,'\"':b) <- break (== '\"') xs
+    , modu <- takeFileName a
+    , (pkg,_) <- breakEnd (== '-') $ takeFileName $ takeDirectory a
+    = "<a href=\"/package/" ++ pkg ++ "/docs/" ++ modu ++ "\"" ++ fixFileLinks b
+fixFileLinks (x:xs) = x : fixFileLinks xs
+fixFileLinks [] = []
+
+
+testedWith :: IO [String]
+testedWith = do
+    src <- readCabal
+    return $ concat [ map f $ words $ map (\x -> if x == ',' then ' ' else x) $ drop 12 x
+                    | x <- lines src, "tested-with:" `isPrefixOf` x]
+    where
+        f x | Just rest <- stripPrefix "GHC==" x = rest
+            | otherwise = error $ "Invalid tested-with, " ++ x
+
+
+checkReadme :: IO ()
+checkReadme = do
+    project <- takeBaseName . fromMaybe (error "Couldn't find cabal file") <$> findCabal
+    let want =
+            "[![Hackage version](https://img.shields.io/hackage/v/" ++ project ++ ".svg?style=flat)]" ++
+            "(http://hackage.haskell.org/package/" ++ project ++ ") " ++
+            "[![Build Status](http://img.shields.io/travis/ndmitchell/" ++ project ++ ".svg?style=flat)]" ++
+            "(https://travis-ci.org/ndmitchell/" ++ project ++ ")"
+    src <- readFile "README.md"
+    let line1 = head $ lines src ++ [""]
+    when (not $ want `isSuffixOf` line1) $
+        error $ "Expected first line of README.md to end with:\n" ++ want ++ "\nBut got:\n" ++ line1
+
+
+checkCabalFile :: IO ()
+checkCabalFile = do
+    project <- takeBaseName . fromMaybe (error "Couldn't find cabal file") <$> findCabal 
+    src <- fmap lines readCabal
+    test <- testedWith
+    let grab tag = [stripStart $ drop (length tag + 1) x | x <- relines src, (tag ++ ":") `isPrefixOf` x]
+    license <- readFile' $ concat $ grab "license-file"
+    let bad =
+            ["Incorrect declaration style: " ++ x
+                | (x,':':_) <- map (break (== ':') . stripStart) src
+                , not $ any isSpace $ strip x, not $ "http" `isSuffixOf` x || "https" `isSuffixOf` x
+                , not $ all (\x -> isLower x || x == '-') x] ++
+            ["2014 is not in the copyright year" | not $ "2014" `isInfixOf` concat (grab "copyright")] ++
+            ["copyright string is not at the start of the license" | not $ concat (grab "copyright") `isInfixOf` concat (take 1 $ lines license)] ++
+            ["No correct source-repository link"
+                | let want = "source-repository head type: git location: https://github.com/ndmitchell/" ++ project ++ ".git"
+                , not $ want `isInfixOf` unwords (words $ unlines src)] ++
+            ["No bug-reports link" | grab "bug-reports" /= ["https://github.com/ndmitchell/" ++ project ++ "/issues"]] ++
+            ["Incorrect license " | grab "license" `notElem` [["BSD3"],["MIT"]]] ++
+            ["Invalid tested-with: " ++ show test | length test < 1 || not (null $ test \\ defAllow) || test /= reverse (sort test) || not (test `isPrefixOf` reverse defAllow)] ++
+            ["Bad stabilty, should be missing" | grab "stability" /= []] ++
+            ["Missing CHANGES.txt in extra-source-files" | "CHANGES.txt" `notElem` concatMap words (grab "extra-source-files")]
+    unless (null bad) $ error $ unlines bad
+
+relines :: [String] -> [String]
+relines (x:xs) | ":" `isSuffixOf` x = unwords (x:a) : relines b
+    where (a,b) = break (\x -> stripStart x == x) xs
+relines (x:xs) = x : relines xs
+relines [] = []
+
+readCabal :: IO String
+readCabal = do
+    file <- findCabal
+    case file of
+        Nothing -> return []
+        Just file -> readFile' file
+
+
+findCabal :: IO (Maybe FilePath)
+findCabal = do
+    x <- getDirectoryContents "."
+    return $ listToMaybe $ filter ((==) ".cabal" . takeExtension) x
diff --git a/src/Git.hs b/src/Git.hs
new file mode 100644
--- /dev/null
+++ b/src/Git.hs
@@ -0,0 +1,69 @@
+{-# LANGUAGE RecordWildCards #-}
+
+module Git(run) where
+
+import Control.Monad
+import Data.List.Extra
+import System.Directory.Extra
+import System.Exit
+import System.FilePath
+import Util
+import Arguments
+import Cabal(readCabal)
+
+
+---------------------------------------------------------------------
+-- UTILITIES
+
+-- Find all the repos. Look for .git (I am a repo) and */.git (I have child repos)
+-- If you don't find any, look up.
+findRepos :: IO [FilePath]
+findRepos = do
+    putStr "Looking for repos... "
+    dirs <- getDirectoryContents "."
+    res <- filterM (\x -> doesDirectoryExist $ x </> ".git") dirs
+    putStrLn $ show (length res) ++ " found"
+    return $ sort res
+
+
+-- Run over each repo
+forEachRepo :: (String -> IO ()) -> IO ()
+forEachRepo act = do
+    res <- findRepos
+    when (null res) $ do
+        putStrLn $ "Error: No repos found relative to here"
+        exitFailure
+    forM_ res $ \x ->
+        if x == "." then do
+            cdir <- getCurrentDirectory
+            act $ takeFileName cdir
+        else
+            withCurrentDirectory x $ act x
+
+
+---------------------------------------------------------------------
+-- COMMANDS
+
+run :: Arguments -> Maybe (IO ())
+
+run Whatsnew{..} = Just $ forEachRepo $ \name -> do
+    changes <- fmap (length . filter (not . null) . lines) $ cmdOut $ "git status --porcelain --untracked-files=no"
+    adds <- if not look_for_adds then return 0 else fmap (subtract changes . length . filter (not . null) . lines) $ cmdOut "git status --porcelain"
+    unless local $ cmd "git fetch --quiet"
+    local <- fmap read $ cmdOut $ "git rev-list origin/master..master --count"
+    remote <- fmap read $ cmdOut $ "git rev-list master..origin/master --count"
+    let items = [changes,adds,local,remote]
+    let names = [("local change","s"),("addable",""),("local patch","es"),("remote patch","es")]
+    let res = [show n ++ " " ++ s ++ (if n == 1 then "" else ss) | (n,(s,ss)) <- zip items names, n /= 0]
+    unless (null res) $ putStrLn $ name ++ ": " ++ intercalate ", " res
+
+run Tag = Just $ do
+    src <- readCabal
+    let [ver] = [strip $ drop 8 x | x <- lines src, "version:" `isPrefixOf` x]
+    putStrLn $ "Confirm to tag the release with version " ++ ver ++ "? Type 'yes':"
+    "yes" <- getLine
+    cmd "git push"
+    cmd $ "git tag v" ++ ver
+    cmd "git push --tags"
+
+run _ = Nothing
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE CPP #-}
+
+module Main(main) where
+
+import Control.Monad
+import Data.Maybe
+import System.Console.CmdArgs
+import System.Environment
+import System.IO
+
+import Arguments
+import Cabal
+import Git
+#ifndef SMALL
+import Travis
+#endif
+import qualified Paper.Main as Paper
+
+
+main :: IO ()
+main = do
+    hSetBuffering stdout NoBuffering
+    a <- getArgs
+    case a of
+        "paper":a -> withArgs a Paper.main
+        _ -> do
+            args <- cmdArgsRun arguments
+            fromMaybe (error $ "Don't know how to deal with argument: " ++ show args) $ msum
+                [Git.run args
+                ,Cabal.run args
+#ifndef SMALL
+                ,Travis.run args
+#endif
+                ]
diff --git a/src/Neil.hs b/src/Neil.hs
new file mode 100644
--- /dev/null
+++ b/src/Neil.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Neil(
+    neil,
+    module Util,
+    module Control.Monad,
+    module Data.List,
+    module Data.Maybe,
+    module Extra
+    ) where
+
+import Util
+import Control.Monad
+import Data.List
+import Data.Maybe
+import Extra
+import System.Environment
+
+
+neil :: IO () -> IO ()
+neil act = do
+    args <- getArgs
+    if "--go" `elem` args then
+        act
+     else
+        putStrLn "Script type checks successfully, pass --go to run it"
diff --git a/src/Paper/Ftp.hs b/src/Paper/Ftp.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Ftp.hs
@@ -0,0 +1,63 @@
+
+module Paper.Ftp(ftp) where
+
+import Control.Monad
+import System.Cmd
+import System.Directory
+import System.FilePath
+import System.IO
+import Paper.Util.IO
+
+
+ftp :: FilePath -> IO ()
+ftp darcs = do
+    let name = takeFileName darcs
+        out = darcs </> name <.> "patch"
+        ftpfile = out <.> "ftp"
+
+    b <- doesFileExist out
+    when b $ removeFile out
+    b <- doesFileExist (out <.> "send")
+    when b $ removeFile (out <.> "send")
+
+    system $ "darcs send" ++
+                 " --repodir=\"" ++ darcs ++ "\"" ++
+                 " --output=\"" ++ (out <.> "send") ++ "\""
+    b <- doesFileExist (out <.> "send")
+    when b $ do
+        password <- getPassword
+        writeFile ftpfile $ unlines
+            ["ndm500"
+            ,password
+            ,"lcd " ++ darcs
+            ,"cd web/patches"
+            ,"binary"
+            ,"put " ++ (name <.> "patch" <.> "send")
+            ,"rename " ++ (name <.> "patch" <.> "send") ++ " " ++ (name <.> "patch")
+            ,"get " ++ (name <.> "patch")
+            ,"quit"]
+        system $ "ftp -s:" ++ ftpfile ++ " ftp.york.ac.uk > nul"
+        check out (out <.> "send")
+        removeFile ftpfile
+        removeFile out
+        removeFile (out <.> "send")
+        return ()
+
+
+getPassword = do
+        hSetBuffering stdout NoBuffering
+        hSetBuffering stdin NoBuffering
+        hSetEcho stdout False
+        hSetEcho stdin False
+        putStr "Enter password: "
+        s <- getLine
+        putStrLn ""
+        return s
+
+
+check x y = do
+    x1 <- readFile' x
+    y1 <- readFile' y
+    if x1 == y1
+        then putStrLn "Success"
+        else putStrLn "ERROR: FTP was unsuccessful"
diff --git a/src/Paper/Graph.hs b/src/Paper/Graph.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Graph.hs
@@ -0,0 +1,91 @@
+
+module Paper.Graph(graphLog, graphCreate) where
+
+import Data.List
+import qualified Data.Map as Map
+import Data.Ord
+import System.Cmd
+import System.Time
+
+import Graphics.Google.Chart
+
+
+graphLog :: FilePath -> [(String,Int)] -> IO ()
+graphLog dest xs = do
+    t <- getClockTime
+    t <- toCalendarTime t
+    appendFile dest $ unlines [show (t,a,b) | (a,b) <- xs]
+
+
+
+-- load the data
+graphLoad :: FilePath -> [String] -> IO [(Date,String,Int)]
+graphLoad src files = do
+    src <- readFile src
+    res <- return [(date a,b,c) | s <- lines src, not $ null s, let (a,b,c) = read s, b `elem` files]
+    return $ sortBy (comparing fst3) res
+
+fst3 (x,_,_) = x
+
+-- year, month (1 based), day (1 based)
+type Date = (Int,Int,Int)
+
+date :: CalendarTime -> Date
+date t = (ctYear t, fromEnum (ctMonth t) + 1, ctDay t)
+
+
+incDate :: Date -> Date
+incDate (a,12,31) = (a+1,1,1)
+incDate (a, b,31) = (a,b+1,1)
+incDate (a, b, c) = (a,b,c+1)
+
+
+totalCounts :: [FilePath] -> [(Date,FilePath,Int)] -> [(Date,Int)]
+totalCounts _ [] = [((2000,1,1),0)]
+totalCounts files xs = f (Map.fromList (zip files $ repeat 0)) xs steps
+    where
+        steps = iterate incDate $ fst3 (head xs)
+
+        f now xxs@((a,b,c):xs) (s:ss)
+            | a > s = dump s now : f now xxs ss
+            | otherwise = f (Map.insert b c now) xs (s:ss)
+        f now [] (s:ss) = [dump s now]
+
+        dump s mp = (s, sum $ Map.elems mp)
+
+
+graphCreate :: FilePath -> FilePath -> [String] -> IO ()
+graphCreate src dest files = do
+    xs <- graphLoad src files
+    let url = graphUrl $ totalCounts files xs
+    system $ "wget \"" ++ url ++ "\" -O \"" ++ dest ++ "\""
+    return ()
+
+
+-- a list of date/count pairs
+-- all the dates must be in order
+graphUrl :: [(Date,Int)] -> String
+graphUrl dat = chartURL $
+        setSize 400 300 $
+        setTitle "Word Count" $
+        setAxisTypes [AxisBottom,AxisLeft] $
+        setAxisLabels [xAxis, yAxis] $
+        setData (encodeDataSimple [map scale allY]) $
+        newLineChart
+    where
+        -- calculate the bounding box
+        minX = let (a,b,_) = fst (head dat) in (a,b,1)
+        maxX = let (a,b,_) = fst (last dat) in incDate (a,b,31)
+        allY = if length dat == 1 then replicate 2 $ snd $ head dat else map snd dat
+        minY = (minimum allY `div` 1000) * 1000
+        maxY = (maximum allY `div` 1000) * 1000 + 1000
+
+        -- calculate the axis
+        yAxis = map show [minY, minY+1000 .. maxY]
+        xAxis = map g $ takeWhile (<= maxX) $ iterate f minX
+            where
+                f (a,b,c) = incDate (a,b,31)
+                g (a,b,c) = take 3 (show (toEnum (b-1) :: Month)) ++ " " ++ show (a `mod` 100)
+
+        -- scaling (new range is 0 <= x <= 61)
+        scale y = (61 * (y - minY)) `div` (maxY - minY)
diff --git a/src/Paper/Haskell.hs b/src/Paper/Haskell.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Haskell.hs
@@ -0,0 +1,45 @@
+
+module Paper.Haskell where
+
+import Data.Ix
+import Data.Char
+import Data.List
+import System.Directory
+import System.FilePath
+import Paper.Util.FileData
+import Paper.Haskell.Fragment
+import Paper.Haskell.Check
+
+
+haskell obj files = do
+    let incFile = directory files </> "Include.hs"
+    b <- doesFileExist incFile
+    pre <- if b then readFile incFile else return ""
+
+    src <- readFile (directory files </> mainFile files)
+    let (debug,ranges) = partition (== "d") $ flags files
+    checkFragments (not $ null debug)
+                   (checkRange $ parseRanges ranges)
+                   pre (parseFragments src)
+
+
+-- ranges are one of:
+--   -from..to
+--   -from..          (where the .. is optional)
+
+parseRanges :: [String] -> [(Int,Int)]
+parseRanges [] = [(minBound,maxBound)]
+parseRanges xs = map f xs
+    where
+        f x = (read a, if null c then maxBound else read c)
+            where
+                (a,b) = span isDigit x
+                c = dropWhile (== '.') b
+
+-- a list of ranges, union'ed together
+checkRange :: [(Int,Int)] -> Int -> Bool
+checkRange []     i = False
+checkRange (x:xs) i = inRange x i || checkRange xs i
+
+
+rep from with = map (\x -> if x == from then with else x)
diff --git a/src/Paper/Haskell/Check.hs b/src/Paper/Haskell/Check.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Haskell/Check.hs
@@ -0,0 +1,116 @@
+
+module Paper.Haskell.Check where
+
+import Paper.Haskell.Fragment
+import Paper.Haskell.Haskell.Provides
+import System.Cmd
+import System.Exit
+import System.IO
+import Data.List
+import Data.Char
+import Control.Monad
+
+
+data Result = Pass | Fail String | Missing String | Instances [String]
+              deriving (Eq,Show)
+
+builtin = ["where"] ++
+          ["Int","Bool","Float","Integer","String","Char"]
+
+
+checkFragments :: Bool -> (Int -> Bool) -> String -> [Frag] -> IO ()
+checkFragments debug test prefix xs = mapM_ f xs
+    where
+        names = split [drop 5 x | x <- lines prefix, "-- # " `isPrefixOf` x]
+        insts = split [drop 12 x | x <- lines prefix, "-- instance " `isPrefixOf` x]
+        split = concatMap (map trim . lines . map (\x -> if x == ';' then '\n' else x))
+        trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
+    
+        f (Expr i s)
+            | test i && s `elem` (names ++ builtin ++ concat [has | Stmt _ has _ <- xs])
+            = putStrLn $ "Checking line " ++ show i ++ "... success"
+
+        f (Expr i s) = f $ Stmt i [] ("tex2hs _ = (" ++ s ++ ")")
+        f (Stmt i has s) | test i = do
+            putStr $ "Checking line " ++ show i ++ "... "
+            res <- check (prefix ++ "\n" ++ s)
+            case res of
+                Pass -> putStrLn "success"
+                Fail msg -> do
+                    putStrLn "FAILURE"
+                    putStr $ unlines $ map ("  "++) $ lines msg
+                    error "Fix your code, or we'll reject you!"
+
+        f _ = return ()
+
+        check s = do
+            res <- checkCode debug s
+            case res of
+                Missing x -> g (Fail $ "Can't find: " ++ show x) s [t | Stmt _ has t <- xs, x `elem` has]
+                Instances x ->
+                    let add = unlines ["instance " ++ z | i <- x, z <- filter (\j -> blur i == blur j) insts]
+                        blur = filter (isUpper . head) . lexemes
+                    in g (Fail $ "No instance: " ++ show x) s [add | not $ null add]
+                _ -> return res
+        
+        g err s [] = return err
+        g err s (x:xs) = do
+            r <- check (s ++ "\n" ++ x)
+            if r == Pass then return Pass else g r s xs
+
+checkCode :: Bool -> String -> IO Result
+checkCode debug orig = do
+    writeFile "temp.hs" orig
+    res <- system "ffihugs -98 temp.hs 2> temp.txt"
+    if res == ExitSuccess then return Pass else do
+        x <- readFileStrict "temp.txt"
+        let s = unlines $ filter (not . null) $ drop 1 $ lines x
+            err = parseError s
+        
+        when debug $ do
+            putStrLn orig
+            putStrLn s
+            print err
+            getChar
+            return ()
+
+        return err
+
+
+readFileStrict s = do
+    h <- openFile s ReadMode
+    s <- hGetContents h
+    length s `seq` hClose h
+    return s
+
+
+parseError s =
+    if any ("- Undefined" `isPrefixOf`) (tails s) then
+        Missing $ takeWhile (/= '\"') $ drop 1 $ dropWhile (/= '\"') $ dropWhile (/= '-') s
+    else if any ("- Instance" `isPrefixOf`) (tails s) then
+        Instances $ insts s
+    else if any ("requires extra context" `isPrefixOf`) (tails s) then
+        Instances $ nub $ insts2 s
+    else
+        Fail s
+
+
+
+insts = map (unwords . words) . lines . map rep . sel
+    where
+        rep x = if x == ',' then '\n'
+                else if x `elem` "()" then ' '
+                else x
+    
+        sel (' ':'o':'f':' ':xs) = g xs
+        sel (x:xs) = sel xs
+        
+        g (' ':'r':xs) = []
+        g (x:xs) = x : g xs
+
+
+insts2 = map (unwords . words) . lines . map rep . reverse . takeWhile (/= ':') . reverse
+    where
+        rep x = if x == ',' then '\n'
+                else if x `elem` "()" then ' '
+                else x
diff --git a/src/Paper/Haskell/Fragment.hs b/src/Paper/Haskell/Fragment.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Haskell/Fragment.hs
@@ -0,0 +1,19 @@
+
+module Paper.Haskell.Fragment where
+
+import Paper.Haskell.Haskell.Provides
+import Paper.Haskell.Haskell.Tweak
+import Paper.Haskell.Latex.Parser
+
+
+data Frag = Stmt Line [String] String
+          | Expr Line String
+            deriving Show
+
+
+parseFragments :: String -> [Frag]
+parseFragments = map f . parseLatex
+    where
+        f (HsExpr line x) | isStmt x = f $ HsCode line x
+        f (HsCode line x) = Stmt line (map fromProvides $ provides x) (tweak x)
+        f (HsExpr line x) = Expr line x
diff --git a/src/Paper/Haskell/Haskell/Provides.hs b/src/Paper/Haskell/Haskell/Provides.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Haskell/Haskell/Provides.hs
@@ -0,0 +1,91 @@
+
+module Paper.Haskell.Haskell.Provides(Provides(..), provides, fromProvides, isStmt, lexemes) where
+
+import Data.List
+import Data.Char
+
+
+data Provides = ProvidesBody String
+              | ProvidesSig  String
+              | ProvidesName String
+                deriving (Eq,Show)
+
+
+fromProvides (ProvidesBody x) = x
+fromProvides (ProvidesSig  x) = x
+fromProvides (ProvidesName x) = x
+
+
+provides :: String -> [Provides]
+provides x = nub $ concatMap (provider . lexemes) $ indents $ lines x
+
+
+indents (x:(y:ys):zs) | isSpace y = indents $ (x++" ; "++y:ys) : zs
+indents (x:xs) = x : indents xs
+indents [] = []
+
+
+isStmt x = "=" `elem` before "let" xs || "::" `elem` xs
+    where xs = lexemes x
+
+before x xs = if x `elem` xs then takeWhile (/= x) xs else xs
+
+
+split on xs = a : if null b then [] else split on (tail b)
+    where (a,b) = break (== on) xs
+
+
+provider :: [String] -> [Provides]
+provider xs = case xs of
+    "type":xs -> [ProvidesName $ headNote 1 con]
+    "data":xs -> ProvidesName (headNote 2 con) : providerCtors (map (rep "=" "|") (tail con))
+    "class":xs -> ProvidesName (headNote 3 con) : map asClass (concatMap provider (tail $ split ";" con))
+    _ | "::" `elem` (takeWhile (`notElem` ["where","="]) xs) -> providesSig xs
+    _ -> providesBody xs
+    where
+        con = dropContext $ tail xs
+        asClass = ProvidesName . fromProvides
+
+        dropContext xs = if null b then a else tail b
+            where (a,b) = break (== "=>") xs
+
+
+
+providerCtors = map f . tail . split "|"
+    where
+        f xs = ProvidesName $ headNote 4 $ y ++ xs
+            where y = filter (\x -> ":" `isPrefixOf` x && x /= "::") xs
+
+
+providesSig = map ProvidesSig . filter (`notElem` [",","(",")"]) . takeWhile (/= "::") . takeWhile (/= "=")
+
+
+-- approximation:
+-- (symbol)
+-- (...) symbol
+-- ... symbol
+-- name
+providesBody [] = []
+providesBody xs = map ProvidesBody $ f xs
+    where
+        f (x:ys:xs) | isSymbol ys = [getSymbol (ys:xs)]
+        f ("(":xs) = [a | _:a:_ <- [dropWhile (/= ")") xs]]
+        f (x:xs) = [x]
+
+        isSymbol (y:ys) = not (isAlpha y) && y `notElem` "([" && (y:ys) /= "="
+
+        getSymbol ("`":x:xs) = x
+        getSymbol (x:xs) = x
+
+
+lexemes :: String -> [String]
+lexemes x = case lex x of
+    [("",_)] -> []
+    [(x,y)] -> x : lexemes y
+    [] -> []
+
+rep from to x = if x == from then to else x
+
+
+headNote i [] = error $ show i
+headNote i (x:xs) = x
diff --git a/src/Paper/Haskell/Haskell/Tweak.hs b/src/Paper/Haskell/Haskell/Tweak.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Haskell/Haskell/Tweak.hs
@@ -0,0 +1,17 @@
+
+module Paper.Haskell.Haskell.Tweak(tweak) where
+
+import Paper.Haskell.Haskell.Provides
+import Data.List
+
+
+tweak :: String -> String
+tweak = dummy . unlines . filter (not . isModule) . lines
+    where isModule x = "module " `isPrefixOf` x || "import " `isPrefixOf` x
+
+
+dummy x = unlines (x : map f undef)
+    where
+        p = provides x
+        undef = [x | ProvidesSig x <- p] \\ [x | ProvidesBody x <- p]
+        f x = "(" ++ x ++ ") = undefined"
diff --git a/src/Paper/Haskell/Latex/Parser.hs b/src/Paper/Haskell/Latex/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Haskell/Latex/Parser.hs
@@ -0,0 +1,46 @@
+
+module Paper.Haskell.Latex.Parser(Line, HsCode(..), parseLatex) where
+
+import Data.List
+
+type Line = Int
+
+data HsCode = HsCode Line String
+            | HsExpr Line String
+            deriving (Eq,Show)
+
+
+
+parseLatex :: String -> [HsCode]
+parseLatex x = f $ zip [1..] (lines x)
+    where
+        f ((i,x):xs) | beginCode x = let (a,b) = break (endCode . snd) xs
+                                         code = (unlines $ map snd a)
+                                     in HsCode i code : f b
+                     | ignoreCode x = f $ drop 1 $ dropWhile (not . endCode . snd) xs
+                     | otherwise = concat (zipWith (g i) [1..] (parseLine x)) ++ f xs
+        f [] = []
+
+        g line col s = [HsExpr line s]
+
+
+ignoreCode = isPrefixOf "\\ignore\\begin{code}"
+beginCode = isPrefixOf "\\begin{code}"
+endCode = isPrefixOf "\\end{code}"
+
+
+parseLine :: String -> [String]
+parseLine ('%':xs) = []
+parseLine ('|':'|':xs) = parseLine xs
+parseLine ('|':xs) = a : parseLine b
+    where (a,b) = parseBar xs
+parseLine xs | "\\ignore|" `isPrefixOf` xs = parseLine $ snd $ parseBar $ drop 8 xs
+parseLine (x:xs) = parseLine xs
+parseLine [] = []
+
+parseBar ('|':'|':xs) = ('|':a,b)
+    where (a,b) = parseBar xs
+parseBar ('|':xs) = ("",xs)
+parseBar (x:xs) = (x:a,b)
+    where (a,b) = parseBar xs
+parseBar [] = ("","")
diff --git a/src/Paper/Haskell2/All.hs b/src/Paper/Haskell2/All.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Haskell2/All.hs
@@ -0,0 +1,24 @@
+
+module Paper.Haskell2.All(haskell2) where
+
+import Control.Monad
+import System.Cmd
+import System.Exit
+import System.FilePath
+
+import Paper.Haskell2.Stage1
+import Paper.Haskell2.Stage2
+import Paper.Haskell2.Stage3
+
+
+haskell2 :: FilePath -> [FilePath] -> IO ()
+haskell2 obj files = mapM_ f files
+    where
+        f file = do
+            putStrLn $ "Checking " ++ takeBaseName file
+            src <- readFile file
+            let dest = obj </> takeFileName file
+                res = stage3 dest $ stage2 $ stage1 file src
+            mapM_ (uncurry writeFile) res
+            res <- system $ "runghc -i" ++ obj ++ " " ++ fst (head res)
+            when (res /= ExitSuccess) $ error "Failed to check"
diff --git a/src/Paper/Haskell2/Haskell.hs b/src/Paper/Haskell2/Haskell.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Haskell2/Haskell.hs
@@ -0,0 +1,109 @@
+
+module Paper.Haskell2.Haskell(
+    isHaskellSym, haskellKeywords, lexer,
+    defines, rename, fakeImplement
+    ) where
+
+import Data.Char
+import Data.List
+import Data.Maybe
+import Paper.Util.String
+
+
+haskellKeywords = ["class","instance","where","data","type","import","in","let","do","module","newtype"]
+haskellKeySymbols = ["--","="]
+
+
+isHaskellSym xs = all (`elem` "|+-*<>.=?&") xs && xs `notElem` haskellKeySymbols
+isHaskellVar xs = isAlpha (head xs) && xs `notElem` haskellKeywords
+validName xs = isHaskellSym xs || isHaskellVar xs
+
+
+defines :: String -> [String]
+defines = nub . filter validName . concatMap f . map lexer . classLeft . lines
+    where
+        f ("(":name:")":_) | isHaskellSym name = [name]
+        f ("(":xs) | isHaskellSym name = [name]
+            where name:_ = drop 1 $ dropWhile (/= ")") xs
+        f (_:name:_) | isHaskellSym name = [name]
+        f ("type":name:_) = [name]
+        f (name:_) = [name]
+        f _ = []
+
+
+flushLeft (x:xs) = not $ isSpace x
+flushLeft [] = False
+
+
+classLeft (x:xs) | "class" `isPrefixOf` x = x : a ++ classLeft xs
+    where (a,_) = span (\x -> null x || isSpace (head x)) xs
+classLeft ((x:_):xs) | isSpace x = classLeft xs
+classLeft (x:xs) = x : classLeft xs
+classLeft [] = []
+
+
+rename :: [(String, String)] -> String -> String
+rename [] = id
+rename ren = concatMap f . lexerSpace
+    where f x = fromMaybe x $ lookup x ren
+
+
+-- figure out which definitions have a type signature
+-- but no associated body, and make one up
+fakeImplement :: String -> String
+fakeImplement xs = unlines $
+    ["-- !typesigFunction " ++ show typ, "-- !implementsFunction " ++ show imp, xs] ++
+    [operator x ++ " = undefined -- stub" | x <- typ \\ imp]
+    where (typ,imp) = (typesigFunction xs, implementsFunction xs)
+
+
+-- more a "this function is definately defined"
+-- as conservative
+typesigFunction :: String -> [String]
+typesigFunction = nub . concatMap (typesigs . lexer) . filter flushLeft . lines
+
+
+typesigs :: [String] -> [String]
+typesigs = f []
+    where
+        f seen ("(":x:")":xs) = f seen (x:xs)
+        f seen (x:"::":xs) = x:seen
+        f seen (x:",":xs) = f (x:seen) xs
+        f _ _ = []
+
+
+-- more a "this function is possibly implemented"
+-- as conservative
+implementsFunction :: String -> [String]
+implementsFunction = nub . concatMap (f . lexer) . lines
+    where
+        f xs | not $ null $ typesigs xs = []
+        f (_:"`":x:"`":_) = [x]
+        f (_:x:_) | isHaskellSym x = [x]
+        f ("(":xs) | bs /= [] && isHaskellSym (head bs) = [head bs]
+            where bs = drop 1 $ dropWhile (/= ")") xs
+        f (x:xs) = [x]
+        f [] = []
+
+
+operator (x:xs) | isAlpha x = x:xs
+operator x = "(" ++ x ++ ")"
+
+
+-- concat . lexerSpace == id (nearly, just comments)
+lexerSpace :: String -> [String]
+lexerSpace [] = []
+lexerSpace xs@(x:_) | isSpace x = a : lexerSpace b
+    where (a,b) = span isSpace xs
+lexerSpace ('{':'-':'\"':xs) = (" {-\"" ++ a ++ c) : lexerSpace d
+    where (a,b) = breakStr "\"-}" xs
+          (c,d) = splitAt 3 b
+lexerSpace ('-':'-':x:xs) | isAlphaNum x || isSpace x = (" --" ++ a) : lexerSpace b
+    where (a,b) = break (== '\n') (x:xs)
+lexerSpace xs = case lex xs of
+                [(a,'.':x:xs)] | isUpper x -> (a++'.':b) : c
+                    where b:c = lexerSpace (x:xs)
+                [(a,b)] -> a : lexerSpace b
+                other -> error $ "lexerSpace, unexpected: " ++ show (xs, other)
+
+lexer = filter (not . isSpace . head) . lexerSpace
diff --git a/src/Paper/Haskell2/Stage1.hs b/src/Paper/Haskell2/Stage1.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Haskell2/Stage1.hs
@@ -0,0 +1,119 @@
+
+module Paper.Haskell2.Stage1(stage1) where
+
+import Data.Char
+import Data.List
+import Paper.Util.String
+import Paper.Util.Error
+import Paper.Haskell2.Type
+import Paper.Haskell2.Haskell
+
+
+defs = [("\\hsdef{\\begin{comment}","\\end{comment}}")
+       ,("\\begin{hsdef}","\\end{hsdef}")
+       ,("\\hsdef\\begin{comment}","\\end{comment}")
+       ,("\\hsdef{","}")
+       ]
+
+stage1 :: FilePath -> String -> [HsLow]
+stage1 file = f 1 ""
+    where
+        pos = Pos file
+
+        f i cmd xs | match /= [] = map (HsDef (pos i)) (lines a) ++ f (i + newlines a) "" b
+            where
+                match = filter ((`isPrefixOf` xs) . fst) defs
+                (start,end):_ = match
+                (a,b) = breakStr end $ drop (length start) xs
+
+        f i cmd xs | "\\ignore" `isPrefixOf` xs = f i "ignore" $ drop 7 xs
+
+        f i cmd xs | "\\h{" `isPrefixOf` xs = f (i + newlines a) a $ drop 1 b
+            where (a,b) = break (== '}') $ drop 3 xs
+
+        f i cmd ('|':'|':xs) = f i "" xs
+        f i cmd ('|':xs) | '\n' `elem` a = errorDie file i "Failed to parse | lines" a
+                         | otherwise = hsCheck (pos i) Expr cmd a ++ f i "" b
+            where (a,b) = spanExpr xs
+
+        f i cmd xs | "\\begin{code}" `isPrefixOf` xs
+                   = hsCheck (pos i) Stmt cmd a ++ concatMap (f i "") (comments a) ++ f (i + newlines a) "" b
+            where (a,b) = breakStr "\\end{code}" $ drop 12 xs
+
+        f i cmd xs | "%if 0" `isPrefixOf` xs = f (i + newlines a) "" b
+            where (a,b) = breakStr "%endif" xs
+
+        f i cmd ('%':xs) = f i "" $ dropWhile (/= '\n') xs
+        f i cmd (x:xs) | x == '\n' = f (i+1) cmd xs
+                       | isSpace x = f i cmd xs
+                       | otherwise = f i "" xs
+        f i cmd [] = []
+
+
+comments :: String -> [String]
+comments = concatMap f . lines
+    where
+        f xs = [drop 2 b | not $ null b]
+            where (_,b) = breakStr "--" xs
+
+
+spanExpr :: String -> (String, String)
+spanExpr ('|':'|':xs) = ('|':a, b)
+    where (a,b) = spanExpr xs
+spanExpr ('|':xs) = ("", xs)
+spanExpr (x:xs) = (x:a,b)
+    where (a,b) = spanExpr xs
+spanExpr [] = ("","")
+
+
+newlines :: String -> Int
+newlines = length . filter (== '\n')
+
+
+readCmd :: String -> ([String], String)
+readCmd ('.':xs) = (a:c,d)
+    where
+        (a,b) = break (== ' ') xs
+        (c,d) = readCmd $ drop 1 b
+readCmd xs = ([], xs)
+
+
+hsCheck pos typ cmd x
+        | b == "ignore" = []
+        | b == "" = cont [(typ,x)]
+        | otherwise = cont $ hsCustom b x
+    where
+        cont tx = [HsCheck pos t (parseWhere a) x | (t,x) <- tx]
+        (a,b) = readCmd cmd
+
+
+-- @ foo = bar
+-- @     = def
+-- implicitly insert foo before each =
+hsCustom "deflist" x = [(Stmt,unlines $ map f ls)]
+    where
+        ls = lines x
+        prefix = takeWhile (/= '=') $ head $ dropWhile (all isSpace) ls
+        f x | "=" `isPrefixOf` dropWhile isSpace x = prefix ++ x
+            | otherwise = x
+
+-- each line started at column 1 is a new expr
+hsCustom "exprlist" x = map ((,) Expr) $ f $ lines x
+    where
+        f (x:(y:ys):z) | isSpace y = f ((x ++ '\n':y:ys) : z)
+        f (x:xs) = x : f xs
+        f [] = []
+
+-- a piece of context
+hsCustom "ctxt" x = [(Stmt,"context_expression :: " ++ x ++ " => " ++ free ++ "()\ncontext_expression = undefined")]
+    where free = concat [x:xs ++ " -> " | x:xs <- lexer x, isLower x]
+
+-- a type expression
+hsCustom "type" x = [(Stmt,"type TypeType " ++ free ++ " = " ++ x)]
+    where free = unwords [x | x <- lexer x, isLower $ head x]
+
+hsCustom "do" x = [(Expr,unlines $ "do" : map (' ':) (lines x))]
+
+hsCustom "stmt" x = [(Stmt,x)]
+hsCustom "expr" x = [(Expr,unlines $ map (' ':) $ lines x)]
+hsCustom name _ = error $ "Stage1 todo: " ++ show name
diff --git a/src/Paper/Haskell2/Stage2.hs b/src/Paper/Haskell2/Stage2.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Haskell2/Stage2.hs
@@ -0,0 +1,42 @@
+
+module Paper.Haskell2.Stage2(stage2) where
+
+import Data.Char
+import Data.List
+import qualified Data.Set as Set
+import Paper.Util.String
+import Paper.Haskell2.Type
+import Paper.Haskell2.Haskell
+
+
+stage2 :: [HsLow] -> [HsItem]
+stage2 xs = reverse stmts2 ++ exprs2
+    where
+        (defs,checks) = partition isHsDef $ nub xs
+        defNames = nub $ concatMap parseDefs defs
+        (stmts,exprs) = partition ((==) Stmt . lowType) checks
+        (useNames,stmts2) = unzip $ map parseStmt stmts
+        names = flip Set.member $ Set.fromList $ haskellKeywords ++ defNames ++ concat useNames
+        exprs2 = concat $ zipWith (parseExpr defNames names) [1..] exprs
+
+
+
+parseDefs :: HsLow -> [String]
+parseDefs (HsDef pos x) = map (dropWhile (== ',')) $ splitStr "," x
+
+
+parseStmt :: HsLow -> ([String],HsItem)
+parseStmt (HsCheck pos Stmt whr x) = (lexer x, HsItem Stmt pos (fakeImplement x) whr)
+
+
+parseExpr :: [String] -> (String -> Bool) -> Int -> HsLow -> [HsItem]
+parseExpr names seen n (HsCheck pos expr whr x) =
+    case lexer x of
+        [] -> []
+        [y] | seen y -> []
+        ["(",y,")"] | seen y -> []
+        [y] | isHaskellSym y -> f [y] ("(" ++ y ++ ")")
+        lexed -> f lexed x
+    where
+        f lexed x = [HsItem Stmt pos (unwords (("auto_" ++ show n) : free) ++ " = " ++ x ++ "\n") whr]
+            where free = filter (not . isUpper . head) $ names `intersect` lexed
diff --git a/src/Paper/Haskell2/Stage3.hs b/src/Paper/Haskell2/Stage3.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Haskell2/Stage3.hs
@@ -0,0 +1,86 @@
+
+module Paper.Haskell2.Stage3(stage3) where
+
+import Data.Char
+import Data.List
+import System.FilePath
+import Paper.Haskell2.Type
+import Paper.Haskell2.Haskell
+
+
+exts = "NoMonomorphismRestriction, MultiParamTypeClasses, FlexibleContexts, " ++
+       "FlexibleInstances, UndecidableInstances, Rank2Types, EmptyDataDecls, " ++
+       "FunctionalDependencies, TypeSynonymInstances, IncoherentInstances, " ++
+       "ExtendedDefaultRules, ScopedTypeVariables, GADTs, ViewPatterns, " ++
+       "GeneralizedNewtypeDeriving, DeriveDataTypeable"
+
+prefix = "{-# LANGUAGE " ++ exts ++ " #-}\n" ++
+         "{-# OPTIONS_GHC -w #-}\n"
+
+
+stage3 :: FilePath -> [HsItem] -> [(FilePath,String)]
+stage3 file xs = (filename "", importer) : [(filename n, text n) | n <- need]
+    where
+        filename n = dropFileName file </> modname n <.> "hs"
+        modname n = capital (takeBaseName file) ++ ['_'| n/=""] ++ n
+        need = allWhere $ map itemWhere xs
+
+        importer = unlines $ ("module Main where") :
+                             ["import " ++ modname n ++ "()" | n <- need] ++
+                             ["main = putStrLn \"Successfully checked\""]
+
+        text n = unlines $ prefix :
+                           ("module " ++ modname n ++ " where") :
+                           render items
+            where items = filter (matchWhere n . itemWhere) xs
+
+
+render = collectImports . f [] . zip [0..]
+    where
+        f seen [] = []
+        f seen ((n,HsItem Stmt pos x _) : xs) =
+                linePragma pos : defNote : lines x2 ++ "" : f seen2 xs
+            where
+                def = defines x
+                defNote = "-- !defines " ++ show def
+                bad = def `intersect` seen
+                x2 = rename (zip bad $ prime n bad) x
+                seen2 = def `union` seen
+
+        f seen (x:xs) = error $ "Stage 4, todo: " ++ show x
+
+
+collectImports xs = imports ++ map f xs
+    where
+        imports = filter (\x -> "import " `isPrefixOf` x && g x `notElem` modules) xs
+        modules = map g (filter ("module " `isPrefixOf`) xs) \\ ["Prelude"]
+        g = takeWhile (\x -> isAlphaNum x || x `elem` "._") . dropWhile isSpace . dropWhile (not . isSpace)
+    
+        f x | any (`isPrefixOf` x) ["import ","module "] = "-- HIDE " ++ x
+            | otherwise = x
+
+
+capital (x:xs) = toUpper x : xs
+
+
+prime :: Int -> [String] -> [String]
+prime n xs | length pos == length (nub pos) = pos
+           | otherwise = def
+    where
+        end = "''" ++ (uniqueStr !! n)
+        sym = uniqueSym !! n
+        (pos,def) = unzip $ map f xs
+
+        f (x:xs) | isHaskellSym (x:xs) = (x:xs ++ "++" ++ sym, x:xs ++ "++" ++ sym)
+                 | otherwise = (x : (reverse $ drop (length end) $ reverse xs) ++ end, x:xs ++ end)
+
+
+
+uniqueStr = map (:[]) one ++ two ++ error "Stage3, uniqueStr exhausted"
+    where
+        one = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z']
+        two = [[a,b] | a <- one, b <- one]
+
+
+uniqueSym = [[a,b] | a <- syms, b <- syms] ++ error "Stage3, uniqueSym exhausted"
+    where syms = "!+-<>=*&^%$"
diff --git a/src/Paper/Haskell2/Type.hs b/src/Paper/Haskell2/Type.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Haskell2/Type.hs
@@ -0,0 +1,66 @@
+
+module Paper.Haskell2.Type where
+
+import Data.List
+
+
+data Pos = Pos FilePath !Int
+           deriving Show
+
+linePragma :: Pos -> String
+linePragma (Pos file line) = "{- # LINE " ++ show line ++ " " ++ show file ++ " # -}"
+
+
+data HsLow = HsDef   {lowPos :: Pos, lowText :: String}
+           | HsCheck {lowPos :: Pos, lowType :: HsType, lowWhere :: Where, lowText :: String}
+             deriving Show
+
+
+instance Eq HsLow where
+    (HsDef a1 b1) == (HsDef a2 b2) = b1 == b2
+    (HsCheck a1 b1 c1 d1) == (HsCheck a2 b2 c2 d2) = b1 == b2 && c1 == c2 && d1 == d2
+    _ == _ = False
+
+
+isHsDef (HsDef{}) = True; isHsDef _ = False
+
+--     lowExpr: True is Expr, False is Stmt
+--     lowCmd: any proceeding command, possibly none
+--             \ignore, or \hs{command}
+
+
+data HsItem = HsItem {itemType :: HsType, itemPos :: Pos, itemText :: String, itemWhere :: Where}
+              deriving Show
+
+data HsType = Stmt
+            | Expr
+            | TExpr
+              deriving (Eq,Show)
+
+
+data Where = Always
+           | Only [String]
+           | OnlyNot [String]
+             deriving (Show,Eq)
+
+
+parseWhere :: [String] -> Where
+parseWhere [] = Only ["default"]
+parseWhere ["*"] = Always
+parseWhere xs | null neg = Only xs
+              | otherwise = OnlyNot $ map tail neg
+    where neg = filter ("!" `isPrefixOf`) xs
+
+
+allWhere :: [Where] -> [String]
+allWhere = nub . sort . concatMap f
+    where
+        f (Only x) = x
+        f (OnlyNot x) = x
+        f _ = []
+
+
+matchWhere :: String -> Where -> Bool
+matchWhere x Always = True
+matchWhere x (Only xs) = x `elem` xs
+matchWhere x (OnlyNot xs) = x `notElem` xs
diff --git a/src/Paper/LatexError.hs b/src/Paper/LatexError.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/LatexError.hs
@@ -0,0 +1,89 @@
+
+module Paper.LatexError(latexError) where
+
+import Control.Monad
+import Data.Char
+import Data.List
+import Data.Maybe
+import System.Directory
+import System.FilePath
+
+
+-- given a list of all the files that went into the mix
+-- and the log file
+-- and the errors reported to the screen
+-- attempt to diagnose something
+latexError :: [FilePath] -> FilePath -> String -> IO ()
+latexError texFiles logFile messages = do
+        putStrLn "\nAnalysing Errors:\n"
+        src <- readFile logFile
+        let log = processLog src
+        mapM_ (f log) $ nub $ map (\(a,b,_) -> (a,b)) $ mapMaybe splitErrLine $ lines messages
+        mapM_ checkFile texFiles
+    where
+        f log (file,line) = do
+                mapM_ putStr $ take 1 [c | (a,b,c) <- log, a == file2, b == line]
+                showSection file line
+            where file2 = takeBaseName file
+
+splitErrLine s
+    | null rest2 || null pos || not (all isDigit pos) = Nothing
+    | otherwise = Just (s1++file, read pos :: Int, drop 1 rest2)
+    where
+        (s1,s2) = splitAt 3 s
+        (file,rest1) = break (== ':') s2
+        (pos,rest2) = break (== ':') $ drop 1 rest1
+
+
+-- show a section of a file, centered around a particular point
+showSection :: FilePath -> Int -> IO ()
+showSection file pos = do
+        b <- doesFileExist file
+        when b $ do
+            src <- readFile file
+            putStrLn $ unlines $ map f $ take 5 $ drop (pos - 3) $ zip [1..] $ lines src
+    where f (p,s) = show p ++ (if p == pos then ">  " else "   ") ++ s
+
+
+
+processLog :: String -> [(FilePath,Int,String)]
+processLog x = f (lines x)
+    where
+        f [] = []
+        f (x:xs) = case splitErrLine x of
+                       Nothing -> f xs
+                       Just (a,b,c) -> (takeBaseName a,b,unlines (c:d)) : f e
+                           where (d,e) = break null xs
+
+
+---------------------------------------------------------------------
+-- Check a file for obvious bracketing errors
+checkFile :: FilePath -> IO ()
+checkFile file = do
+        src <- readFile file
+        let errs = f [] (zip [0..] $ lines src)
+        putStr $ unlines ["Error in " ++ takeBaseName file ++ ":" ++ show n ++ ", " ++ x | (n,x) <- errs]
+    where
+        (open,shut) = unzip brackets
+        search x ys = listToMaybe [(i, drop (length y) x) | (i,y) <- zip [0..] ys, y `isPrefixOf` x]
+
+        f seen [] = if null seen then [] else
+                    [(n, "Bracket openned but never shut " ++ show (open !! i)) | (n,i) <- seen]
+        f seen ((n,[]):xs) = f seen xs
+        f seen ((n,x):xs) =
+                case (search x open, search x shut) of
+                    (Just (i,x),_) -> f ((n,i):seen) ((n,x):xs)
+                    (_,Just (i,x)) | null seen ->
+                        [(n,"Close bracket " ++ show (shut !! i) ++ ", but no open anywhere")]
+                                   | otherwise ->
+                        let ((n2,i2):seen2) = seen
+                        in if i2 == i then f seen2 ((n,x):xs)
+                           else [(n,"Bracket mismatch, " ++ show (open !! i2) ++ " openned on " ++ show n2 ++
+                                   " but shut with " ++ show (shut !! i))]
+                    _ -> f seen ((n,tail x):xs)
+
+
+brackets :: [(String,String)]
+brackets = let (*) = (,) in
+    ["{" * "}"
+    ]
diff --git a/src/Paper/Main.hs b/src/Paper/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Main.hs
@@ -0,0 +1,159 @@
+{-# LANGUAGE CPP #-}
+module Paper.Main(main) where
+
+import System.Directory
+import System.Environment
+import System.FilePath
+
+import Paper.Util.FileData
+#ifndef SMALL
+import Paper.Graph
+#endif
+import Paper.Make
+import Paper.Talk
+import Paper.WordCount
+import Paper.Haskell
+import Paper.Haskell2.All
+import Paper.Push
+import Paper.Sync
+import Paper.Ftp
+import Paper.Ref
+import Paper.Todo
+
+
+{-
+COMMANDS:
+make -- compile the document
+wc -- word count
+wcmin -- minimal word count
+graph -- make a graph
+spell -- complete spell check
+colin -- auto-colin grammar check
+haskell -- auto-haskell checking
+
+CONVENTIONS:
+all object files go in obj/ directory
+all stored files go in paper/ directory
+    the output from chart
+    word count logs (used for charting)
+    grammar checking logs
+
+all commands that take arguments take either:
+    nothing - use the current directory
+    a directory name - index.tex is the main file, all other *.tex files are extras
+    a list of files - first is the main file, all others are extras (all must be the same directory)
+-}
+
+main :: IO ()
+main = do
+    args <- getArgs
+    case args of
+        (cmd:files) -> do
+            files <- getFileData files
+            process cmd files
+        _ -> error "No arguments given"
+
+
+wc :: (FilePath -> IO Int) -> FileData -> IO ()
+wc f files = do
+    let shw = fixed ("total" : map dropExtension (allFiles files))
+    res <- flip mapM (allFiles files) $ \file -> do
+        putStr $ shw (dropExtension file) ++ "  "
+        count <- f (directory files </> file)
+        putStrLn $ int count
+        return (file,count)
+    putStrLn $ shw "Total" ++ "  " ++ int (sum $ map snd res)
+#ifndef SMALL
+    root <- settingsDir files
+    graphLog (root </> "graph.txt") res
+#endif
+
+process :: String -> FileData -> IO ()
+process "wc" files = wc wordCountNorm files
+process "wcmin" files = wc wordCountMin files
+
+#ifndef SMALL
+process "graph" files = do
+    root <- settingsDir files
+    let res = root </> "graph.png"
+    graphCreate (root </> "graph.txt") res (allFiles files)
+    putStrLn $ "Written graph, " ++ res
+#endif
+
+process "make" files = do
+    dat <- dataDir
+    obj <- objDir "make" files
+    make dat obj (directory files) (mainFile files) (allFiles files)
+
+process "haskell" files = do
+    obj <- objDir "haskell" files
+    haskell obj files
+
+process "haskell2" files = do
+    obj <- objDir "haskell2" files
+    haskell2 obj (argFiles files)
+
+process "talk" files = do
+    tlk <- objDir "talk" files
+    talk tlk (argFiles files) (flags files)
+
+process "push" files = push (darcs files)
+
+process "sync" files = do
+    dat <- dataDir
+    sync (takeDirectory $ takeDirectory dat)
+
+process "ref" files = ref (argFiles files)
+
+process "todo" files = todo (argFiles files)
+
+process "ftp" files = ftp (darcs files)
+
+process "check" files = do
+    mapM_ (`process` files) ["ref","todo"]
+    putStrLn "All checks succeeded"
+
+process x files = putStrLn $ "Error: Unknown action, " ++ show x
+
+
+----- utility stuff
+
+-- the directory where data files (paper.bib/paper.fmt) live
+dataDir :: IO FilePath
+dataDir = do
+    x <- findExecutable "paper"
+    case x of
+        Nothing -> error "Couldn't find the data directory"
+        Just y -> return $ dropFileName y </> "data"
+
+settingsDir :: FileData -> IO FilePath
+settingsDir = ensureDir "paper"
+
+objDir :: String -> FileData -> IO String
+objDir cmd = ensureDir ("obj" </> cmd)
+
+
+ensureDir :: FilePath -> FileData -> IO FilePath
+ensureDir name files = do
+    let s = directory files </> name
+    createDirectoryIfMissing True s
+    return s
+
+
+----- mini formatting library
+
+padR, padL :: Int -> String -> String
+padL n s = replicate (n - length s) ' ' ++ s
+padR n s = s ++ replicate (n - length s) ' '
+
+
+maxIntWidth = maximum $ map (length . show) [minBound::Int, maxBound]
+
+int :: Int -> String
+int = padL maxIntWidth . show
+
+
+fixed :: [String] -> String -> String
+fixed ss = let n = maximum $ map length ss
+           in \s -> padR n s
+
diff --git a/src/Paper/Make.hs b/src/Paper/Make.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Make.hs
@@ -0,0 +1,96 @@
+
+module Paper.Make(make) where
+
+import Control.Exception
+import Control.Monad
+import System.Cmd
+import System.Directory
+import System.Exit
+import System.FilePath
+
+import Paper.LatexError
+import Paper.Util.IO
+
+
+make :: FilePath -> FilePath -> FilePath -> FilePath -> [FilePath] -> IO ()
+make dataDir objDir srcDir mainFile allFiles = do
+    let dataFile x = liftM ((dataDir </> x) :)
+        sys = System (return ()) (const $ return ()) objDir
+
+    eps1 <- files (srcDir </> "graphics") "eps"
+    eps2 <- files srcDir "eps"
+    fmt <- dataFile "paper.fmt" $ files srcDir "fmt"
+    cls <- files srcDir "cls"
+    bib <- dataFile "paper.bib" $ files srcDir "bib"
+    tex <- dataFile "paper.tex" $ return allFiles
+
+    for (eps2 ++ fmt ++ cls) $
+        \e -> replaceDirectory e objDir <== [e] $ copyFile
+    when (not $ null eps1) $ createDirectoryIfMissing True (objDir </> "graphics")
+    for eps1 $
+        \e -> replaceDirectory e (objDir </> "graphics") <== [e] $ copyFile
+    for bib $
+        \b -> replaceDirectory b objDir <== [b] $ \from to -> do
+            copyFile from to
+            system_ sys $ "bibtex -quiet " ++ takeBaseName b
+    for tex $
+        \t -> replaceDirectory t objDir <== (t:fmt) $ \from to -> do
+            -- intermediate copy step because lhs2tex has bugs
+            -- which means the input can't be an absolute path
+            let temp = to <.> "lhs"
+            copyFile from temp
+            system_ sys{cleanup=removeFile to} $
+                "lhs2tex " ++ takeFileName temp ++ " -o " ++ to
+            fixLineEndings to
+
+    let base = takeBaseName mainFile
+    system_ sys{errorMsg=latexError (tex++fmt) (objDir </> base <.> "log")} $
+        "texify --quiet " ++ (base <.> "tex")
+    copyFile (objDir </> base <.> "dvi") (srcDir </> base <.> "dvi")
+
+
+fixLineEndings file = do
+    src <- readFile' file
+    writeFile file $ filter (/= '\r') src
+
+for x = flip mapM x
+
+files dir ext = do
+    b <- doesDirectoryExist dir
+    s <- if b then getDirectoryContents dir else return []
+    s <- return $ filter ((==) ('.':ext) . takeExtension) s
+    return $ map (dir </>) s
+
+
+
+data System = System
+    {cleanup :: IO ()
+    ,errorMsg :: String -> IO ()
+    ,curDir :: FilePath}
+
+system_ sys cmd = do
+    orig <- getCurrentDirectory
+    bracket_ (setCurrentDirectory (curDir sys)) (setCurrentDirectory orig) $ do
+        putStrLn cmd
+        res <- system $ cmd ++ " > stdout.txt 2> stderr.txt"
+        out <- readFile "stdout.txt"
+        err <- readFile "stderr.txt"
+        putStr $ out ++ err
+        when (res /= ExitSuccess) $ do
+            cleanup sys
+            errorMsg sys out
+            putStrLn "System command failed! Press enter to continue"
+            getChar
+            exitWith (ExitFailure 1)
+
+
+(<==) :: FilePath -> [FilePath] -> (FilePath -> FilePath -> IO ()) -> IO ()
+(<==) to froms@(from:_) action = do
+    b <- doesFileExist to
+    rebuild <- if not b then return True else do
+        from2 <- liftM maximum $ mapM getModificationTime froms
+        to2 <- getModificationTime to
+        return $ to2 < from2
+    when rebuild $ do
+        putStrLn $ "Building: " ++ to
+        action from to
diff --git a/src/Paper/Push.hs b/src/Paper/Push.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Push.hs
@@ -0,0 +1,33 @@
+
+module Paper.Push(push) where
+
+import Control.Monad
+import Data.List
+import Data.Maybe
+import System.Cmd
+import System.FilePath
+
+
+push :: FilePath -> IO ()
+push darcs = do
+    src <- readFile $ darcs </> "_darcs" </> "prefs" </> "repos"
+    () <- length src `seq` return ()
+    let r = pick $ lines src
+    when (isNothing r) $ error "No non-http repos in the prefs/repos file"
+
+    putStrLn $ "Pushing to " ++ show (fromJust r) ++ "..."
+    system $ "darcs push --no-set-default \"" ++ fromJust r ++ "\""
+    return ()
+
+
+-- pick the ssh address
+pick :: [String] -> Maybe String
+pick xs = listToMaybe $ ssh ++ [b ++ drop (length a) h | h <- http, (a,b) <- mapping, a `isPrefixOf` h]
+    where (http,ssh) = partition ("http://" `isPrefixOf`) xs
+
+
+mapping = [("http://www.cs.york.ac.uk/fp/darcs/","ndm@community.haskell.org:/home/ndm/darcs/")
+          ,("http://community.haskell.org/~ndm/darcs/","ndm@community.haskell.org:/home/ndm/darcs/")
+          ,("http://code.haskell.org/","ndm@code.haskell.org:/srv/code/")
+          ]
+
diff --git a/src/Paper/Ref.hs b/src/Paper/Ref.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Ref.hs
@@ -0,0 +1,50 @@
+
+module Paper.Ref(ref) where
+
+import Control.Monad
+import Data.Char
+import Data.List
+import qualified Data.Map as Map
+import Paper.Util.Error
+
+data Ref = Ref FilePath Int Bool -- True = ref, False = label
+type Refs = Map.Map String [Ref]
+
+addRef m (s,ref) = Map.insertWith (++) s [ref] m
+
+
+ref :: [FilePath] -> IO ()
+ref files = do
+    r <- mapM readRefs files
+    let errs = checkRefs $ foldl addRef Map.empty $ concat r
+    sequence errs
+    when (not $ null errs) $
+        error $ "Error: " ++ show (length errs) ++ " references failed"
+    putStrLn "All references are correct"
+
+
+readRefs :: FilePath -> IO [(String,Ref)]
+readRefs file = liftM (f 1) $ readFile file
+    where
+        f n ('\n':xs) = f (n+1) xs
+        f n ('\\':'r':'e':'f':xs) = g n True xs
+        f n ('\\':'l':'a':'b':'e':'l':xs) = g n False xs
+        f n (x:xs) = f n xs
+        f n [] = []
+
+        g n b ('{':xs) | "}" `isPrefixOf` post = (pre, Ref file n b) : f n (tail post) 
+            where (pre,post) = span (`notElem` "\n}") xs
+        g n b (x:xs) | isSpace x || x == '{' =
+            error $ file ++ "(" ++ show n ++ ") Unrecognised label: " ++ take 25 xs
+        g n b xs = f n xs
+
+
+checkRefs :: Refs -> [IO ()]
+checkRefs = concatMap f . Map.toList
+    where
+        f (s,xs) | null b = err (head a) "Reference used but not defined"
+                 | length b > 1 = err (head b) "Reference defined multiple times"
+                 | otherwise = []
+            where
+                (a,b) = partition (\(Ref a b c) -> c) xs
+                err (Ref file line _) msg = [errorMsg file line msg ("{" ++ s ++ "}")]
diff --git a/src/Paper/Sync.hs b/src/Paper/Sync.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Sync.hs
@@ -0,0 +1,35 @@
+
+module Paper.Sync(sync) where
+
+import Control.Monad
+import Data.List
+import System.Cmd
+import System.Directory
+import System.FilePath
+import Paper.Util.IO
+
+
+sync :: FilePath -> IO ()
+sync dir = do
+    b <- doesDirectoryExist $ dir </> "_darcs"
+    if b then check dir else do
+        xs <- getDirectoryContents dir
+        xs <- return [dir </> x | x <- xs, not $ all (== '.') x]
+        flip mapM_ xs $ \x -> do
+            b <- doesDirectoryExist x
+            when b $ sync x
+
+
+check :: FilePath -> IO ()
+check dir = do
+    b1 <- run "darcs whatsnew --summary" "No changes!"
+    b2 <- run "darcs send --dry-run --all" "No recorded local changes to send!"
+    let msg = ["changes"|b1] ++ ["patches"|b2]
+    when (msg /= []) $
+        putStrLn $ unwords $ [takeFileName dir,"has"] ++ intersperse "and" msg
+    where
+        run cmd want = do
+            let out = dir </> "paper_sync.tmp"
+            system $ cmd ++ " --repodir=\"" ++ dir ++ "\" > \"" ++ out ++ "\""
+            src <- readFile' out
+            return $ want `notElem` lines src
diff --git a/src/Paper/Talk.hs b/src/Paper/Talk.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Talk.hs
@@ -0,0 +1,175 @@
+
+module Paper.Talk(talk) where
+
+import Control.Monad
+import Data.List
+import Data.Char
+import Data.Maybe
+import Paper.Util.CmdNumber
+import Paper.Util.String
+import System.FilePath
+import System.Cmd
+import System.Directory
+
+
+talk :: FilePath -> [FilePath] -> [String] -> IO ()
+talk objDir files args = do
+    rPtts <- findExecutable "ptts"
+    when (isNothing rPtts) $
+        error $ "Can't find ptts executable, please install"
+
+    rLame <- findExecutable "lame"
+    when (isNothing rLame) $
+        putStrLn "Warning: lame not found, .wav files will be generated"
+
+    mapM_ (f (isJust rLame)) files
+    where
+        permit = cmdNumber args
+
+        f lame file = do
+            src <- readFileSpeach file
+            mapM_ (g lame file) $ zip [0..] src
+
+        g lame file (n,s) | not $ permit n = return ()
+                          | otherwise = do
+            let name ext = objDir </> (takeBaseName file ++ "_" ++ show n) <.> ext
+            writeFile (name "txt") s
+            putStrLn $ "Writing " ++ name "wav"
+            system $ "ptts -voice \"Microsoft Mary\" -w \"" ++ name "wav" ++ "\" < \"" ++ name "txt" ++ "\""
+            putStrLn $ "Writing " ++ name "mp3"
+            system $ "lame -b 64 --quiet \"" ++ name "wav" ++ "\" \"" ++ name "mp3" ++ "\""
+            removeFile (name "wav")
+
+
+---------------------------------------------------------------------
+-- TEXT PROCESSING BIT
+
+
+-- read a file and split it into text chunks
+readFileSpeach :: FilePath -> IO [String]
+readFileSpeach file = do
+    src <- readFile file
+    return $ map (flatten . text) $ divide src
+
+
+-- compress \n\n\n -> \n
+--          "  " -> " "
+--          " \n" -> "\n"
+flatten = f . g
+    where
+        g (' ':'\n':xs) = g ('\n':xs)
+        g ('\n':' ':xs) = g ('\n':xs)
+        g (' ':' ':xs) = g (' ':xs)
+        g (x:xs) = x : g xs
+        g [] = []
+
+        f ('\n':'\n':'\n':xs) = f ('\n':'\n':xs)
+        f (x:xs) = x : f xs
+        f [] = []
+
+
+divide = splitStr "\\section"
+
+
+text :: String -> String
+
+text ('%':xs) = text $ dropWhile (/= '\n') xs
+text ('\\':'%':xs) = '%' : text xs
+
+text xs | "|(:)|" `isPrefixOf` xs = "cons" ++ text (drop 5 xs)
+text xs | "|[]|" `isPrefixOf` xs = "nil" ++ text (drop 4 xs)
+text (x:xs) | x `elem` "|$" = case lex xs of
+    [(s,y:xs)] | x == y -> s ++ text xs
+    _ -> "expression" ++ text (drop 1 $ dropWhile (/= x) xs)
+text ('\\':'$':xs) = '$' : text xs
+
+text ('`':'`':xs) = '\"' : text xs
+text ('\'':'\'':xs) = '\"' : text xs
+text ('`':xs) = '\'' : text xs
+
+-- change some constructs to insert pauses
+text (' ':'-':'-':xs) = " ; " ++ text (dropWhile (== '-') xs)
+text ('(':xs) = " ; " ++ text xs
+text (')':xs) = " ; " ++ text xs
+
+-- Given an environment there are several behaviours:
+-- replace the entire environment with some alternative text (figure, code)
+-- drop the begin/end bits
+text ('\\':xs) | "begin" `isPrefixOf` xs =
+    case lookup a rep of
+        Nothing -> text b
+        Just y -> y ++ "\n\n" ++ text (skipTilEnd a b)
+    where
+        (a,b) = spanCurly $ drop 5 xs
+        rep = [("figure",""),("table",""),("comment","")
+              ,("tabular","Table."),("verbatim","Code."),("code","Code.")]
+
+text ('\\':xs) 
+        -- lists
+        | a == "item" && "[" `isPrefixOf` b =
+            let (c,d) = break (== ']') (tail b) in
+            "\n" ++ text c ++ [':' | not $ ":" `isSuffixOf` c] ++ text (dropWhile (== ']') d)
+        | a == "item" = "\nBullet: " ++ text b
+
+        -- accents
+        | a == "o" = 'o' : text b
+        | null a && "\"" `isPrefixOf` b = text (tail b)
+
+        -- special
+        | a == "ref" = "1" ++ text (skipCurly b)
+        | null a && "[" `isPrefixOf` b = "Code." ++ text (dropUntilStr "\\]" b)
+        | a == "url" = "url" ++ text (skipCurly b)
+        | a == "citet" = "paper" ++ text (skipCurly b)
+        | a == "S" = "section " ++ text b
+
+        | otherwise = text $ skippy (fromMaybe "" $ lookup a skip) b
+    where
+        (a,b2) = span isAlpha xs
+        b = if "*" `isPrefixOf` b2 then tail b2 else b2
+
+text (x:xs) | x `elem` "{}" =  text xs
+text (x:xs) = x : text xs
+text [] = []
+
+
+
+skip = let (*) = (,) in
+    map (* "[{")
+        ["end","label","cite","vspace","hspace","footnote","documentclass"
+        ,"include","title","author","institute","caption","usepackage"
+        ,"copyrightyear","copyrightdata","titlebanner","title","subtitle"
+        ,"bibliographystyle","citep"]
+    ++ ["newenvironment" * "{{{", "newcommand" * "{[{", "conferenceinfo" * "{{"
+       ,"authorinfo" * "{{{","category" * "{{{", "terms" * "\n\n", "keywords" * "\n\n"
+       ,"setcounter" * "{{"]
+
+
+skippy ('{':xs) t = skippy xs $ skipCurly t
+skippy ('[':xs) t = skippy xs $ skipSquare t
+skippy ('\n':xs) t = skippy xs $ drop 1 $ dropWhile (/= '\n') t
+skippy [] t = t
+
+
+
+-- "{foo}test" = ("foo","test")
+-- "test = ("","test")
+spanCurly = spanPair '{' '}'
+skipCurly = snd . spanCurly
+
+skipSquare = snd . spanPair '[' ']'
+
+
+spanPair start stop xs | [start] `isPrefixOf` rest = f 1 $ drop 1 rest
+    where
+        rest = dropWhile isSpace xs
+        f 1 (x:xs) | x == stop  = ([],xs)
+        f n (x:xs) | x == stop  = g (n-1)
+                   | x == start = g (n+1)
+                   | otherwise  = g n
+            where g n = let (a,b) = f n xs in (x:a,b)
+        f n [] = ([],[])
+spanPair start stop xs = ([],xs)
+
+
+
+skipTilEnd s = dropUntilStr ("\\end{" ++ s ++ "}")
diff --git a/src/Paper/Todo.hs b/src/Paper/Todo.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Todo.hs
@@ -0,0 +1,31 @@
+
+module Paper.Todo(todo) where
+
+import Control.Monad
+import Data.Char
+import Paper.Util.Error
+
+
+todo :: [FilePath] -> IO ()
+todo files = do
+    errs <- liftM concat $ mapM readTodos files
+    sequence errs
+    when (not $ null errs) $
+        error $ "Error: " ++ show (length errs) ++ " todo commands found"
+    putStrLn "No todo commands found"
+
+
+readTodos :: FilePath -> IO [IO ()]
+readTodos file = liftM (f 1) $ readFile file
+    where
+        f n ('\n':xs) = f (n+1) xs
+        f n ('\\':t:'o':'d':'o':x:xs)
+                | toLower t == 't' && not (isAlpha x) && x /= '}'
+                = errorMsg file n "\\todo" msg : f n xs
+            where msg = g $ dropWhile isSpace (x:xs)
+        f n (x:xs) = f n xs
+        f n [] = []
+
+        g ('{':xs) = "{" ++ takeWhile (`notElem` "\n}") xs ++ "}"
+        g _ = ""
+
diff --git a/src/Paper/Util/CmdNumber.hs b/src/Paper/Util/CmdNumber.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Util/CmdNumber.hs
@@ -0,0 +1,18 @@
+
+module Paper.Util.CmdNumber(cmdNumber) where
+
+import Data.Char
+import Data.Ix
+import Paper.Util.String
+
+
+cmdNumber :: [String] -> Int -> Bool
+cmdNumber args | null nums = \n -> True
+               | otherwise = \n -> any (`inRange` n) nums
+    where
+        nums :: [(Int,Int)]
+        nums = map f $ filter (isDigit . head) args
+
+        f x = (read a
+              ,case b of {[] -> read a; ".." -> maxBound; _ -> read $ drop 2 b})
+            where (a,b) = breakStr ".." x
diff --git a/src/Paper/Util/Error.hs b/src/Paper/Util/Error.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Util/Error.hs
@@ -0,0 +1,20 @@
+
+module Paper.Util.Error(errorMsg, errorDie) where
+
+import System.FilePath
+
+
+errorMsg :: FilePath -> Int -> String -> String -> IO ()
+errorMsg = errorWith putStrLn
+
+
+errorDie :: FilePath -> Int -> String -> String -> a
+errorDie = errorWith error
+
+
+errorWith :: (String -> a) -> FilePath -> Int -> String -> String -> a
+errorWith action file line short long = action $
+    takeFileName file ++
+    (if line == 0 then "" else "(" ++ show line ++ ")") ++
+    " " ++ short ++
+    (if null long then "" else ": " ++ take 25 long)
diff --git a/src/Paper/Util/FileData.hs b/src/Paper/Util/FileData.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Util/FileData.hs
@@ -0,0 +1,83 @@
+
+module Paper.Util.FileData(
+    FileData(..), getFileData
+    ) where
+
+import Control.Monad
+import Data.List
+import System.Directory
+import System.FilePath
+
+
+data FileData = FileData
+    {directory :: FilePath       -- ^ All files must reside in one directory
+    ,mainFile  :: FilePath       -- ^ The main file
+    ,argFiles  :: [FilePath]     -- ^ Files given on the command line
+    ,allFiles  :: [FilePath]     -- ^ All files in the directory
+    ,flags     :: [String]       -- ^ Any flags given
+    ,darcs     :: FilePath       -- ^ The location of the _darcs directory
+    }
+    deriving Show
+
+
+
+getFileData :: [String] -> IO FileData
+getFileData args = do
+    let (opt,files) = partition ("-" `isPrefixOf`) args
+    files <- if null files then liftM (:[]) getCurrentDirectory else return files
+    (dirs,explicit,implicit) <- liftM unzip3 $ mapM f files
+    (explicit,implicit) <- return (concat explicit, concat implicit)
+
+    when (length (nub dirs) > 1) $
+        error "All files must be from the same directory"
+
+    let snub = nub . sort
+        nullErr x | null explicit = error $ "Error: No Latex files found in " ++ show (head dirs)
+                  | otherwise = x
+
+    let dir = head dirs
+    darcs <- getDarcs dir
+    
+    return $ FileData
+        dir
+        (nullErr $ head $ explicit)
+        (nullErr $ snub $ explicit)
+        (nullErr $ snub $ explicit ++ implicit)
+        (map tail opt)
+        darcs
+    where
+        -- return (directory, explicit, implicit)
+        f file = do
+            file <- canonicalizePath file
+            bDir <- doesDirectoryExist file
+            bFile <- doesFileExist file
+            if bDir then getDir file
+             else if bFile then getFile file
+             else error $ "Error: Could not find file " ++ show file
+
+
+
+getDir dir = do
+    files <- getDirectoryContents dir
+    files <- return $ filter ((==) ".tex" . takeExtension) files
+
+    -- now pick the main file
+    let mainFile = snd $ maximum [(rank x, x) | x <- files]
+        dirs = reverse $ splitDirectories dir
+        rank x = liftM negate $ findIndex (== dropExtension x) dirs
+    return (dir, [mainFile | not $ null files] ++ files, [])
+
+
+getFile file = do
+    (a,b,[]) <- getDir $ takeDirectory file
+    return (a, [file], b)
+
+
+getDarcs = f . reverse . map joinPath . tail . inits . splitDirectories
+    where
+        f [] = return $ error "Darcs repo not found"
+        f (x:xs) = do
+            b <- doesDirectoryExist (x </> "_darcs")
+            if b then return $ dropTrailingPathSeparator x else f xs
+
+
diff --git a/src/Paper/Util/IO.hs b/src/Paper/Util/IO.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Util/IO.hs
@@ -0,0 +1,11 @@
+
+module Paper.Util.IO where
+
+import System.IO
+
+readFile' x = do
+    h <- openFile x ReadMode
+    s <- hGetContents h
+    seq (length s) $ do
+        hClose h
+        return s
diff --git a/src/Paper/Util/String.hs b/src/Paper/Util/String.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/Util/String.hs
@@ -0,0 +1,30 @@
+
+module Paper.Util.String where
+
+import Data.Char
+import Data.List
+
+
+dropUntilStr s xs | s `isPrefixOf` xs = drop (length s) xs
+dropUntilStr s (x:xs) = dropUntilStr s xs
+dropUntilStr s [] = []
+
+
+
+breakStr :: String -> String -> (String,String)
+breakStr s xs | s `isPrefixOf` xs = ("",xs)
+breakStr s (x:xs) = (x:a,b)
+    where (a,b) = breakStr s xs
+breakStr s [] = ([],[])
+
+
+
+splitStr s [] = []
+splitStr s xs = a : case splitStr s (drop (length s) b) of
+                         [] -> []
+                         x:xs -> (s++x):xs
+    where (a,b) = breakStr s xs
+
+
+
+trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
diff --git a/src/Paper/WordCount.hs b/src/Paper/WordCount.hs
new file mode 100644
--- /dev/null
+++ b/src/Paper/WordCount.hs
@@ -0,0 +1,55 @@
+
+module Paper.WordCount(wordCountNorm, wordCountMin) where
+
+import Data.Char
+import Data.List
+
+
+-- Do not follow \include{} links
+wordCount :: ([String] -> Int) -> FilePath -> IO Int
+wordCount f file = return . f . dropComments . lines =<< readFile file
+
+
+dropComments :: [String] -> [String]
+dropComments (x:xs) | isBeginComment x = drop 1 $ dropWhile (not . isEndComment) xs
+dropComments (x:xs) = f x : dropComments xs
+    where
+        f ('\\':'%':xs) = '\\':'%':f xs
+        f ('%':xs) = []
+        f (x:xs) = x : f xs
+        f [] = []
+dropComments [] = []
+
+isBeginComment = isPrefixOf "\\begin{comment}"
+isEndComment   = isPrefixOf "\\end{comment}"
+
+
+wordCountNorm = wordCount wordsBlock
+    
+
+wordsBlock :: [String] -> Int
+wordsBlock = sum . map f
+    where
+        f ('\\':xs) = f (dropWhile isAlpha xs)
+        f (x:xs) | isAlpha x = 1 + f (dropWhile (not . isSpace) xs)
+        f (x:xs) = f xs
+        f [] = 0
+
+
+wordCountMin = wordCount (wordsBlock . map dropDollar . dropEqn)
+
+dropDollar ('\\':'$':xs) = '\\':'$':dropDollar xs
+dropDollar ('$':xs) = dropDollar $ drop 1 $ dropWhile (/= '$') xs
+dropDollar (x:xs) = x : dropDollar xs
+dropDollar [] = []
+
+dropEqn (x:xs) | "\\beqa" `isPrefixOf` x = dropEqn $ f (x:xs)
+    where
+        f (x:xs) | "\\eeqa" `isPrefixOf` x = drop 5 x : xs
+        f ((x:xs):ys) = f (xs:ys)
+        f ([]:ys) = f ys
+        f [] = []
+dropEqn ((x:xs):ys) = (x:a):b
+    where (a:b) = dropEqn (xs:ys)
+dropEqn ([]:ys) = [] : dropEqn ys
+dropEqn [] = []
diff --git a/src/Travis.hs b/src/Travis.hs
new file mode 100644
--- /dev/null
+++ b/src/Travis.hs
@@ -0,0 +1,89 @@
+{-# LANGUAGE RecordWildCards, PatternGuards #-}
+
+module Travis(run) where
+
+import Control.Monad
+import Data.Char
+import Data.List.Extra hiding (list)
+import System.Directory
+import System.IO.Extra
+import System.FilePath
+import System.Time.Extra
+import Text.JSON
+import Arguments
+import Util
+
+
+run :: Arguments -> Maybe (IO ())
+run Travis{..} = Just $ do
+    dir <- getCurrentDirectory
+    let name = takeFileName dir
+    createDirectoryIfMissing True "dist"
+    appendFile "dist/travis" ""
+    rel <- findRelevant
+    found <- findEntries
+
+    let more [] = more . list =<< wgetJSON wait ("https://api.travis-ci.org/repos/ndmitchell/" ++ name ++ "/builds")
+        more xs = let i = read (last xs ! "number") in if i == 1 then return xs else do
+                        new <- wgetJSON wait $ "https://api.travis-ci.org/repos/ndmitchell/" ++ name ++ "/builds?after_number=" ++ show i
+                        more $ xs ++ list new
+    builds <- more []
+    
+    forM_ (reverse builds) $ \x -> do
+        let num = x ! "number"
+        let id = show (x ! "id" :: Int)
+        let time = x ! "started_at"
+        when (num `notElem` found && x ! "result" /= JSNull) $ do
+            build <- wgetJSON wait $ "https://api.travis-ci.org/builds/" ++ id
+            sleep 2
+
+            let jobs = map (\x -> show (x ! "id" :: Int)) $ build ! "matrix"
+            forM_ jobs $ \i -> do
+                putChar '.'
+                src <- wget wait $ "https://s3.amazonaws.com/archive.travis-ci.org/jobs/" ++ i ++ "/log.txt"
+                let want = [x | x <- lines $ reps '\r' '\n' src, any (`isPrefixOf` x) rel]
+                addEntry num time want
+            when (null jobs) $ addEntry num time []
+    putStrLn "\nDone!"
+run _ = Nothing
+
+
+findRelevant :: IO [String]
+findRelevant = do
+    src <- fmap lines $ readFile ".travis.yml"
+    src <- return $ takeWhile (isPrefixOf " ") $ drop 1 $ dropWhile (not . isPrefixOf "relevant") src
+    when (null src) $ error "No relevant lines in the travis file"
+    return $ map (dropWhile isSpace) src
+
+
+findEntries :: IO [String]
+findEntries = do
+    x <- readFile "dist/travis"
+    return [takeWhile isDigit x | x <- lines x, Just x <- ["BUILD " `stripPrefix` x]]
+
+
+addEntry :: String -> String -> [String] -> IO ()
+addEntry num time xs = appendFile "dist/travis" $ unlines $ ("BUILD " ++ num ++ " " ++ time) : xs
+
+
+wget :: Double -> String -> IO String
+wget wait x = withTempFile $ \t -> do
+    putStr $ "wget " ++ x ++ " ... "
+    cmd $ "wget " ++ x ++ " -O" ++ t ++ " --no-check-certificate --quiet"
+    res <- readFile' t
+    putStrLn "done"
+    sleep wait
+    return res
+
+
+wgetJSON :: Double -> String -> IO JSValue
+wgetJSON wait x = fmap (ok . decode) $ wget wait x
+
+ok (Ok x) = x
+ok (Error x) = error x
+
+(!) :: JSON a => JSValue -> String -> a
+(!) (JSObject mp) k = ok $ valFromObj k mp
+
+list :: JSValue -> [JSValue]
+list (JSArray x) = x
diff --git a/src/Util.hs b/src/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/Util.hs
@@ -0,0 +1,34 @@
+
+module Util(cmd, cmdCode, cmdOut, cmdCodeOutErr) where
+
+import Control.Monad
+import System.Exit
+import System.IO.Extra
+import System.Cmd
+
+
+cmdCodeOutErr :: String -> IO (ExitCode, String, String)
+cmdCodeOutErr x = withTempFile $ \stderr -> withTempFile $ \stdout -> do
+    res <- system $ x ++ " > " ++ stdout ++ " 2> " ++ stderr
+    err <- readFile' stderr
+    out <- readFile' stdout
+    return (res,out,err)
+
+cmdOut :: String -> IO String
+cmdOut x = withTempFile $ \stdout -> do
+    res <- system $ x ++ " > " ++ stdout
+    out <- readFile' stdout
+    when (res /= ExitSuccess) $
+        error $ "Failed in system command: " ++ x
+    return out
+
+
+cmdCode :: String -> IO ExitCode
+cmdCode = system
+
+
+cmd :: String -> IO ()
+cmd x = do
+    res <- system x
+    when (res /= ExitSuccess) $
+        error $ "Failed in system command: " ++ x
