diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 David Lazar <lazar6@illinois.edu>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,10 @@
+# About
+
+**hpygments** is a Haskell library for highlighting source code using [Pygments](http://pygments.org).
+See the [Hackage page](http://hackage.haskell.org/package/hpygments) for documentation and examples.
+
+# Contributing
+
+This project is available on [GitHub](https://github.com/davidlazar/hpygments) and [Bitbucket](https://bitbucket.org/davidlazar/hpygments/). You may contribute changes using either.
+
+Please report bugs and feature requests using the [GitHub issue tracker](https://github.com/davidlazar/hpygments/issues).
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/hpygments.cabal b/hpygments.cabal
new file mode 100644
--- /dev/null
+++ b/hpygments.cabal
@@ -0,0 +1,44 @@
+Name:               hpygments
+Version:            0.1.0
+Synopsis:           Highlight source code using Pygments
+Description:
+    Highlight source code using Pygments <http://pygments.org>. This package
+    depends on Pygments and its accompanying @pygmentize@ script.
+License:            MIT
+License-file:       LICENSE
+Author:             David Lazar
+Maintainer:         David Lazar <lazar6@illinois.edu>
+Category:           Text
+Build-type:         Simple
+Cabal-version:      >=1.6
+
+Extra-source-files:
+    README.md
+
+Data-files:
+    pygments_dump_json.py
+
+source-repository head
+  Type:             git
+  Location:         https://github.com/davidlazar/hpygments
+
+Library
+  ghc-options:      -Wall
+
+  Hs-source-dirs:   src
+
+  Exposed-modules:
+    Text.Highlighting.Pygments
+    Text.Highlighting.Pygments.Lexers
+    Text.Highlighting.Pygments.Formatters
+
+  Other-modules:
+    Text.Highlighting.Pygments.JSON
+    Paths_hpygments
+
+  Build-depends:
+    base >= 4 && < 5,
+    bytestring,
+    process,
+    process-extras >= 0.1.3,
+    aeson == 0.6.0.*
diff --git a/pygments_dump_json.py b/pygments_dump_json.py
new file mode 100644
--- /dev/null
+++ b/pygments_dump_json.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+import sys
+import json
+from pygments.lexers import get_all_lexers
+from pygments.formatters import get_all_formatters
+
+def lexer_dict(name, aliases, filetypes, mimetypes):
+    return { '_lexerName': name
+           , '_lexerAliases': list(aliases)
+           , '_lexerFileTypes': list(filetypes)
+           , '_lexerMimeTypes': list(mimetypes)
+           }
+
+def formatter_dict(f):
+    return { '_formatterName': f.name
+           , '_formatterAliases': list(f.aliases)
+           }
+
+if len(sys.argv) < 2:
+    print('Usage: {} (lexers|formatters)'.format(sys.argv[0]))
+    sys.exit(1)
+
+if sys.argv[1] == "lexers":
+    lexers = [lexer_dict(*x) for x in get_all_lexers()]
+    json.dump(lexers, sys.stdout)
+
+if sys.argv[1] == "formatters":
+    formatters = [formatter_dict(x) for x in get_all_formatters()]
+    json.dump(formatters, sys.stdout)
diff --git a/src/Text/Highlighting/Pygments.hs b/src/Text/Highlighting/Pygments.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Highlighting/Pygments.hs
@@ -0,0 +1,99 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Text.Highlighting.Pygments
+-- Copyright   :  (c) David Lazar, 2012
+-- License     :  MIT
+--
+-- Maintainer  :  lazar6@illinois.edu
+-- Stability   :  experimental
+-- Portability :  unknown
+--
+-- This library uses the @pygmentize@ script that comes with Pygments to
+-- highlight code in many languages. For documentation on the various lexers,
+-- formatters, and options, see the Pygments documentation
+-- <http://pygments.org/docs/>.
+-----------------------------------------------------------------------------
+
+module Text.Highlighting.Pygments
+    (
+      highlight
+    , pygmentize
+
+    , module Text.Highlighting.Pygments.Lexers
+    , module Text.Highlighting.Pygments.Formatters
+
+    -- * Options
+    , Option
+    , Options
+
+    -- * Examples
+    -- $examples
+    ) where
+
+import System.Process (readProcess)
+
+import Text.Highlighting.Pygments.Lexers
+import Text.Highlighting.Pygments.Formatters
+
+-- | Highlight code robustly. This function is more robust than the
+-- lower-level 'pygmentize' function since this library forbids the 
+-- construction of invalid 'Lexer' and 'Formatter' values. Invalid
+-- 'Options' may still cause this function to raise an exception.
+highlight :: Lexer -> Formatter -> Options -> String -> IO String
+highlight lexer formatter options code = do
+    let (lexerAlias : _) = lexerAliases lexer
+    let (formatterAlias : _) = formatterAliases formatter
+    pygmentize lexerAlias formatterAlias options code
+
+-- | Highlight code (less robustly) using the @pygmentize@ script that comes
+-- with Pygments. Invalid values for 'LexerAlias', 'FormatterAlias', or
+-- 'Options' will cause this function to raise an exception.
+pygmentize :: LexerAlias -> FormatterAlias -> Options -> String -> IO String
+pygmentize lexer formatter options code = do
+    let args = ["-l", lexer, "-f", formatter] ++ optionsToArgs options
+    readProcess "pygmentize" args code
+
+-- | The lexer/formatter option @(key, value)@ is passed to the @pygmentize@ 
+-- script via the command-line flag @-P key=value@.
+--
+-- Examples:
+--
+-- > [("hl_lines", "16,23,42"), ("encoding", "utf-8"), ("anchorlines", "True")]
+--
+type Option = (String, String)
+type Options = [Option]
+
+optionsToArgs :: Options -> [String]
+optionsToArgs options = concatMap optionToArg options
+
+optionToArg :: Option -> [String]
+optionToArg (name, value) = ["-P", name ++ "=" ++ value]
+
+{- $examples
+
+Highlight a proposition:
+
+>>> Just coqLexer <- getLexerByName "coq"
+>>> highlight coqLexer terminalFormatter [("encoding", "utf-8")] "∀ x y : Z, x * y = 0 -> x = 0 \\/ y = 0" >>= putStr
+∀ x y : Z, x * y = 0 -> x = 0 \/ y = 0
+
+Output a complete HTML document:
+
+>>> highlight haskellLexer htmlFormatter [("full", "True"), ("linenos", "table"), ("style", "emacs")] "fix f = let x = f x in x" >>= writeFile "fix.html"
+
+Self-highlighting quine:
+
+> quine = pygmentize "hs" "terminal" [] (s ++ show s) >>= putStr
+>   where s = "quine = pygmentize \"hs\" \"terminal\" [] (s ++ show s) >>= putStr\n  where s = "
+
+Highlight the code \"answer = 42\" using every language Pygments knows about:
+
+>>> lexers <- getAllLexers
+>>> forM_ lexers $ \l -> highlight l terminalFormatter [] "answer = 42" >>= printf "(%s) %s" (lexerName l)
+...
+(Prolog) answer = 42
+(CSS+Django/Jinja) answer = 42
+(Smalltalk) answer = 42
+...
+
+-}
diff --git a/src/Text/Highlighting/Pygments/Formatters.hs b/src/Text/Highlighting/Pygments/Formatters.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Highlighting/Pygments/Formatters.hs
@@ -0,0 +1,58 @@
+{-# LANGUAGE TemplateHaskell #-}
+module Text.Highlighting.Pygments.Formatters
+    (
+      FormatterAlias
+    , Formatter
+    , getAllFormatters
+    , getFormatterByName
+
+    -- ** Accessors
+    , formatterName
+    , formatterAliases
+
+    -- ** Common formatters
+    , htmlFormatter
+    , terminalFormatter
+    ) where
+
+import Data.Aeson.TH (deriveJSON)
+import Data.Maybe (listToMaybe)
+
+import Text.Highlighting.Pygments.JSON
+
+type FormatterAlias = String
+
+data Formatter = Formatter
+    { _formatterName    :: String
+    , _formatterAliases :: [FormatterAlias]
+    } deriving (Eq, Ord, Show)
+
+$(deriveJSON id ''Formatter)
+
+getAllFormatters :: IO [Formatter]
+getAllFormatters = getPygmentsJSON "formatters"
+
+-- | Similar to the @get_formatter_by_name()@ function in Pygments
+getFormatterByName :: FormatterAlias -> IO (Maybe Formatter)
+getFormatterByName name = do
+    formatters <- getAllFormatters
+    let fs = filter (\f -> name `elem` _formatterAliases f) formatters
+    return $ listToMaybe fs
+
+formatterName :: Formatter -> String
+formatterName = _formatterName
+
+formatterAliases :: Formatter -> [FormatterAlias]
+formatterAliases = _formatterAliases
+
+htmlFormatter :: Formatter
+htmlFormatter = Formatter
+    { _formatterName = "HTML"
+    , _formatterAliases = ["html"]
+    }
+
+terminalFormatter :: Formatter
+terminalFormatter = Formatter
+    { _formatterName = "Terminal"
+    , _formatterAliases = ["terminal", "console"]
+    }
diff --git a/src/Text/Highlighting/Pygments/JSON.hs b/src/Text/Highlighting/Pygments/JSON.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Highlighting/Pygments/JSON.hs
@@ -0,0 +1,27 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Text.Highlighting.Pygments.JSON 
+    (
+      getPygmentsJSON
+    ) where
+
+import Control.Monad (when)
+import Data.Aeson (decode, FromJSON)
+import Data.ByteString.Lazy.Char8 ()
+import System.Exit
+import System.Process.ByteString.Lazy
+
+import Paths_hpygments
+
+getJSONDumper :: IO FilePath
+getJSONDumper = getDataFileName "pygments_dump_json.py"
+
+getPygmentsJSON :: FromJSON a => String -> IO a
+getPygmentsJSON what = do
+    jsonDumper <- getJSONDumper
+    (exitCode, stdout, _) <- readProcessWithExitCode jsonDumper [what] ""
+    when (exitCode /= ExitSuccess) $
+        fail $ jsonDumper ++ " failed: " ++ show exitCode
+    case decode stdout of
+        Nothing -> fail $ "failed to decode " ++ what
+        Just ls -> return ls
+
diff --git a/src/Text/Highlighting/Pygments/Lexers.hs b/src/Text/Highlighting/Pygments/Lexers.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Highlighting/Pygments/Lexers.hs
@@ -0,0 +1,82 @@
+{-# LANGUAGE TemplateHaskell #-}
+module Text.Highlighting.Pygments.Lexers
+    (
+      LexerAlias
+    , Lexer
+    , getAllLexers
+    , getLexerByName
+
+    -- ** Accessors
+    , lexerName
+    , lexerAliases
+    , lexerFileTypes
+    , lexerMimeTypes
+
+    -- ** Common lexers
+    , haskellLexer
+    , literateHaskellLexer
+    , textLexer
+    ) where
+
+import Data.Aeson.TH (deriveJSON)
+import Data.Maybe (listToMaybe)
+
+import Text.Highlighting.Pygments.JSON
+
+type LexerAlias = String
+
+data Lexer = Lexer
+    { _lexerName      :: String
+    , _lexerAliases   :: [LexerAlias]
+    , _lexerFileTypes :: [String]
+    , _lexerMimeTypes :: [String]
+    } deriving (Eq, Ord, Show)
+
+$(deriveJSON id ''Lexer)
+
+getAllLexers :: IO [Lexer]
+getAllLexers = getPygmentsJSON "lexers"
+
+-- | Similar to the @get_lexer_by_name()@ function in Pygments
+getLexerByName :: LexerAlias -> IO (Maybe Lexer)
+getLexerByName name = do
+    lexers <- getAllLexers
+    let ls = filter (\l -> name `elem` _lexerAliases l) lexers
+    return $ listToMaybe ls
+
+lexerName :: Lexer -> String
+lexerName = _lexerName
+
+lexerAliases :: Lexer -> [LexerAlias]
+lexerAliases = _lexerAliases
+
+lexerFileTypes :: Lexer -> [String]
+lexerFileTypes = _lexerFileTypes
+
+lexerMimeTypes :: Lexer -> [String]
+lexerMimeTypes = _lexerMimeTypes
+
+haskellLexer :: Lexer
+haskellLexer = Lexer
+    { _lexerName = "Haskell"
+    , _lexerAliases = ["haskell","hs"]
+    , _lexerFileTypes = ["*.hs"]
+    , _lexerMimeTypes = ["text/x-haskell"]
+    }
+
+literateHaskellLexer :: Lexer
+literateHaskellLexer = Lexer 
+    { _lexerName = "Literate Haskell"
+    , _lexerAliases = ["lhs","literate-haskell"]
+    , _lexerFileTypes = ["*.lhs"]
+    , _lexerMimeTypes = ["text/x-literate-haskell"]
+    }
+
+-- | No highlighting
+textLexer :: Lexer
+textLexer = Lexer
+    { _lexerName = "Text only"
+    , _lexerAliases = ["text"]
+    , _lexerFileTypes = ["*.txt"]
+    , _lexerMimeTypes = ["text/plain"]
+    }
