diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 Bernard James Pope 
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of the author nor the names of his contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+> import Distribution.Simple
+> main = defaultMain
diff --git a/language-python-colour.cabal b/language-python-colour.cabal
new file mode 100644
--- /dev/null
+++ b/language-python-colour.cabal
@@ -0,0 +1,23 @@
+name:                language-python-colour 
+version:             0.1 
+cabal-version:       >= 1.6
+synopsis:            Generate coloured XHTML for Python code. 
+description:         Generate coloured XHTML for Python code. 
+category:            Language
+license:             BSD3
+license-file:        LICENSE
+copyright:           (c) 2009 Bernard James Pope
+author:              Bernard James Pope 
+maintainer:          bjpop@csse.unimelb.edu.au
+homepage:            http://www.cs.mu.oz.au/~bjpop/
+build-type:          Simple
+stability:           experimental
+tested-with:         GHC==6.10.4
+
+Executable pycol 
+  main-is:         Main.hs
+  hs-source-dirs:  src
+  build-depends:   haskell98, base >= 4 && < 5, language-python, xhtml
+  other-modules:
+     Language.Python.Colour.Colourise
+     Language.Python.Colour.Options
diff --git a/src/Language/Python/Colour/Colourise.hs b/src/Language/Python/Colour/Colourise.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Python/Colour/Colourise.hs
@@ -0,0 +1,63 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      : Language.Python.Colour.Colourise
+-- Copyright   : (c) 2009 Bernie Pope 
+-- License     : BSD-style
+-- Maintainer  : bjpop@csse.unimelb.edu.au
+-- Stability   : experimental
+-- Portability : ghc
+--
+-- Turn Python tokens into HTML (for colouring), matching the layout of 
+-- the input program.
+-----------------------------------------------------------------------------
+module Language.Python.Colour.Colourise ( colourise ) where
+
+import Language.Python.Common
+import Text.XHtml.Transitional
+
+colourise :: String -> [Token] -> String
+colourise progName tokens = showHtml $ makeHtml progName tokens
+
+makeHtml :: String -> [Token] -> Html
+makeHtml progName tokens = theHead progName +++ theBody tokens
+
+theHead :: String -> Html
+theHead progName = 
+   header << (title +++ link)
+   where 
+   title = thetitle << progName 
+   link = (thelink << noHtml) ! 
+          [ thetype "text/css", rel "stylesheet", href "pycol.css" ] 
+
+theBody :: [Token] -> Html
+theBody tokens = pre << renderTokens tokens
+
+renderTokens :: [Token] -> Html 
+renderTokens [] = noHtml  
+renderTokens [t] = tokenSpan t 
+renderTokens (t1:t2:ts)
+   | getSpan t1 == SpanEmpty = renderTokens (t2:ts)
+   | getSpan t2 == SpanEmpty = renderTokens (t1:ts)
+   | otherwise = 
+        tokenSpan t1 +++
+        (toHtml $ pad t1EndRow t1EndCol t2StartRow t2StartCol) +++
+        renderTokens (t2:ts)
+   where
+   t1Span = getSpan t1
+   t2Span = getSpan t2
+   t1EndRow = endRow t1Span 
+   t1EndCol = endCol t1Span 
+   t2StartRow = startRow t2Span 
+   t2StartCol = startCol t2Span 
+
+pad :: Int -> Int -> Int -> Int -> String
+pad endRow endCol startRow startCol
+   | endRow /= startRow = 
+        replicate (startRow - endRow) '\n' ++ 
+        replicate (startCol-1) ' '
+   | otherwise = replicate (startCol - endCol - 1) ' '
+
+tokenSpan :: Token -> Html
+tokenSpan token =
+   (thespan << tokenString token) ! 
+   [ theclass $ show $ classifyToken token ]
diff --git a/src/Language/Python/Colour/Options.hs b/src/Language/Python/Colour/Options.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Python/Colour/Options.hs
@@ -0,0 +1,109 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      : Language.Python.Colour.Options
+-- Copyright   : (c) 2009 Bernie Pope 
+-- License     : BSD-style
+-- Maintainer  : bjpop@csse.unimelb.edu.au
+-- Stability   : experimental
+-- Portability : ghc
+--
+-- Command line option processing for the Python colouring program.
+-----------------------------------------------------------------------------
+module Language.Python.Colour.Options 
+   ( processOptions
+   , programName
+   , Flag (..)
+   , PythonVersion (..)
+   , getPyVersion
+   , probeFlags
+   , probeFlagsFirst
+   , existsFlag
+   , defaultPyVersion
+   )
+   where
+
+import System.Console.GetOpt 
+import Data.Maybe (fromMaybe)
+import Data.Char (toLower, isDigit)
+import Data.Maybe (catMaybes)
+import IO (stderr, hPutStrLn)
+import System
+
+programName :: String
+programName = "pycol"
+
+-- This should really come from the cabal file somehow.
+versionNumber :: String
+versionNumber = "0.1"
+
+versionInfo :: String
+versionInfo = unwords [programName, "version", versionNumber]
+
+processOptions :: [String] -> IO ([Flag], String)
+processOptions argv = 
+   case getOpt RequireOrder options argv of
+      (flags, nonOpts, []) 
+         | existsFlag flags Help -> do 
+              putStrLn $ usageInfo header options
+              exitWith ExitSuccess
+         | existsFlag flags Version -> do 
+              putStrLn versionInfo 
+              exitWith ExitSuccess
+         | length nonOpts /= 1 ->
+              raiseError ["You must specify a single input Python file.\n"]  
+         | otherwise -> return (flags, head nonOpts)
+      (_, _, errs) -> raiseError errs
+      where 
+      header = "Usage: " ++ programName ++ " [OPTION...] file"
+      failureMsg = programName ++ ": command line error.\n"
+      raiseError errs = do
+         hPutStrLn stderr $ failureMsg ++ concat errs ++ usageInfo header options
+         exitFailure
+
+probeFlags :: [Flag] -> (Flag -> Maybe a) -> [a]
+probeFlags flags probe = catMaybes (map probe flags)
+
+probeFlagsFirst :: [Flag] -> (Flag -> Maybe a) -> a -> a
+probeFlagsFirst flags probe defaultValue
+   | null probed = defaultValue 
+   | otherwise = head probed
+   where
+   probed = probeFlags flags probe
+
+existsFlag :: [Flag] -> Flag -> Bool
+existsFlag flags f
+   = probeFlagsFirst flags probe False 
+   where
+   probe someFlag = if f == someFlag then Just True else Nothing 
+    
+data Flag 
+  = PyVersion PythonVersion  -- ^ Which version of Python to parse (2 or 3).
+  | Help                   -- ^ Print a help message and exit.
+  | Version                -- ^ Print the version number. 
+  deriving (Eq, Ord, Show)
+
+getPyVersion :: Flag -> Maybe PythonVersion
+getPyVersion (PyVersion v) = Just v
+getPyVersion _ = Nothing
+
+data PythonVersion
+   = V2 
+   | V3 
+   deriving (Eq, Ord, Show)
+
+options :: [OptDescr Flag]
+options =
+ [ Option ['p']     ["pyver"]     (ReqArg mkPyVersion "VERSION") "VERSION of Python to parse: 2 or 3 (default is 2)"
+ , Option ['v']     ["version"]   (NoArg Version)            "show version number"
+ , Option ['h']     ["help"]      (NoArg Help)               "get help about using this program"
+ ]
+
+defaultPyVersion :: PythonVersion 
+defaultPyVersion = V2 
+
+mkPyVersion :: String -> Flag
+mkPyVersion = normalMkVersion . map toLower
+   where
+   normalMkVersion "2"   = PyVersion V2 
+   normalMkVersion "3"   = PyVersion V3 
+   normalMkVersion other = PyVersion defaultPyVersion
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,61 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      : Main
+-- Copyright   : (c) 2009 Bernie Pope 
+-- License     : BSD-style
+-- Maintainer  : bjpop@csse.unimelb.edu.au
+-- Stability   : experimental
+-- Portability : ghc
+--
+-- Generate coloured XHTML for a Python source file. Supports version 2 and
+-- 3 of Python. The name of the file is given as a command line argument
+-- and an XHTML file is produced on the standard output. 
+--
+-----------------------------------------------------------------------------
+
+module Main where
+
+import Language.Python.Colour.Options 
+import Language.Python.Colour.Colourise
+import qualified Language.Python.Version2 as V2
+import qualified Language.Python.Version3 as V3
+import Language.Python.Common
+import Prelude hiding (lex)
+import System 
+import Control.Monad (when, liftM)
+import IO
+
+main :: IO ()
+main = do
+   args <- getArgs
+   (flags, filename) <- processOptions args
+   tryContents <- safeReadFile filename
+   case tryContents of
+      Left error -> putStrLn error >> exitFailure
+      Right contents -> do
+         -- decide which parser and lexer to use (version 2 or 3?)
+         let (parser, lexer) = getParserLexer flags
+         case lexer contents filename of
+            Left e -> hPutStrLn stderr (prettyText e) >> exitFailure
+            Right tokens -> putStrLn $ colourise filename tokens
+
+type Parser = String -> String -> Either ParseError (ModuleSpan, [Token])
+type Lexer = String -> String -> Either ParseError [Token]
+
+-- decide which parser and lexer to use based on command line arguments (version 2 or 3?)
+getParserLexer :: [Flag] -> (Parser, Lexer)
+getParserLexer [] = (V2.parseModule, V2.lex)
+getParserLexer flags =
+   case pyVersion of
+      V2 -> (V2.parseModule, V2.lex)
+      V3 -> (V3.parseModule, V3.lex)
+   where
+   pyVersion = probeFlagsFirst flags getPyVersion defaultPyVersion
+
+-- Read a file and check for exceptions.
+safeReadFile :: FilePath -> IO (Either String String)
+safeReadFile file
+   = catch (rightReadFile file) $ \error -> return $ Left $ show error
+   where
+   rightReadFile :: FilePath -> IO (Either String String)
+   rightReadFile file = liftM Right $ readFile file
