diff --git a/DiffDump.hs b/DiffDump.hs
new file mode 100644
--- /dev/null
+++ b/DiffDump.hs
@@ -0,0 +1,370 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE CPP #-}
+
+import System.Console.CmdArgs
+  ( cmdArgs
+  , args -- remaining arguments
+  , opt  -- optional
+  , (&=)
+  , def
+  , typ
+  , typFile
+  , typDir
+  , argPos
+  , summary
+  , help
+  , name
+  , Data
+  , Typeable
+  )
+import System.FilePath
+  ( (</>)
+  , takeDirectory
+  , dropTrailingPathSeparator
+  )
+import System.FilePath.Find  -- From the 'filemanip' package
+  ( find
+  , extension
+  , (==?)
+  , (&&?)
+  , (||?)
+  , FilterPredicate
+  , filePath
+  , fileName
+  )
+import System.Process.ByteString
+  ( readProcessWithExitCode
+  )
+import System.Directory
+  ( createDirectoryIfMissing
+  , createDirectory
+  , doesFileExist
+  , doesDirectoryExist
+  , removeDirectoryRecursive
+  , getTemporaryDirectory
+  , getCurrentDirectory
+  , setCurrentDirectory
+  )
+import System.Exit
+  ( ExitCode(ExitSuccess)
+  , exitFailure
+  )
+import System.IO
+  ( stderr
+  , hPutStrLn
+  )
+import System.IO.Unsafe
+  ( unsafePerformIO
+  )
+import GHC.Conc
+  ( getNumProcessors
+  )
+import Data.List
+  ( stripPrefix
+  )
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as B8
+import Control.Monad
+  ( filterM
+  , when
+  , unless
+  )
+import Control.Concurrent.ParallelIO.Local
+  ( parallel_
+  , withPool
+  , Pool
+  )
+import Control.Exception
+  ( finally
+  , bracket
+  , throwIO
+  )
+import System.IO.Error
+  ( isAlreadyExistsError
+  )
+import System.Posix.IO
+  ( stdOutput
+  )
+import System.Posix.Terminal
+  ( queryTerminal
+  )
+import System.Console.ANSI
+  ( SGR(SetColor, Reset)
+  , ConsoleLayer(Foreground)
+  , ColorIntensity(Dull)
+  , Color(Red, Cyan, Green)
+  , setSGR
+  )
+import Paths_diffdump
+  ( version
+  )
+import Data.Version
+  ( showVersion
+  )
+import System.IO.Error
+  ( catchIOError  -- Use this instead of 'catch' to support GHC 7.6.1 and 7.4.x
+  )
+
+data DiffDump = DiffDump {
+    outfile       :: FilePath
+  , outdir        :: DirPath
+  , olddir        :: DirPath
+  , newdir        :: DirPath
+  , func          :: FilePath
+  , arg_list      :: [String]
+  , cmd           :: FilePath
+  , arg           :: [String]
+  , cmd1          :: FilePath
+  , arg1          :: [String]
+  , cmd2          :: FilePath
+  , arg2          :: [String]
+  , filetype      :: String
+  , fileext       :: String
+  , diff          :: FilePath
+  , diff_arg      :: [String]
+  , force         :: Bool
+  , ignore        :: [String]
+  , recursive     :: Bool
+  , unified       :: Bool
+  , color         :: Bool
+  , no_color      :: Bool
+  , verbose       :: Bool
+  , keep_files    :: Bool
+  , succeed_on_diff :: Bool
+} deriving (Show, Data, Typeable)
+
+type DirPath = FilePath
+
+diffDumpOpts :: DirPath -> DiffDump
+diffDumpOpts defOutdir = DiffDump {
+   -----------------------------------------------------------------------
+   -- name          default                type             description --
+   -----------------------------------------------------------------------
+     olddir       = def    &= argPos 0  &= typ "OLD_DIR" &= opt ("-" :: FilePath)
+   , newdir       = def    &= argPos 1  &= typ "NEW_DIR" &= opt ("" :: FilePath)
+   , func         = def    &= argPos 2  &= typFile       &= opt ("" :: FilePath)
+   , arg_list     = def    &= args      &= typ "ARG"
+   , outfile      = def    &= name "o"  &= typFile       &= help "Output path."
+   , outdir       = defOutdir           &= typDir        &= help "Directory for tempory files."
+   , diff         = "diff"              &= typFile       &= help "Path to diff."
+   , cmd          = def                 &= typFile       &= help "Command to run on both arguments."
+   , arg          = def                 &= typ "ARG"     &= help "Argument to pass to both commands."
+   , cmd1         = def                 &= typFile       &= help "Command to run on first argument.  Overrides --cmd"
+   , cmd2         = def                 &= typFile       &= help "Command to run on second argument.  Overrides --cmd"
+   , arg1         = def                 &= typ "ARG"     &= help "Argument appended to first command."
+   , arg2         = def                 &= typ "ARG"     &= help "Argument appended to second command."
+   , filetype     = def                                  &= help "Filter by files that include this string in the output of the 'file' command."
+   , fileext      = def                                  &= help "Filter by files that have this file extension."
+   , ignore       = def                 &= typDir        &= help "Ignore contents of the given directory."
+   , diff_arg     = def                                  &= help "Pass <arg> on to diff."
+   , force        = def    &= name "f"                   &= help "Run despite the output directories already existing."
+   , recursive    = def    &= name "r"                   &= help "Recursively compare any subdirectories found."
+   , unified      = def    &= name "u"                   &= help "Output 3 lines of unified context."
+   , color        = def                                  &= help "Force colorized diff in terminal output."
+   , no_color     = def                                  &= help "Do not colorize diff in terminal output."
+   , verbose      = def                                  &= help "Verbose output."
+   , keep_files   = def                                  &= help "Keep intermediary files."
+   , succeed_on_diff = def                               &= help "Unlike diff, return zero exit code when there are differences."
+   } &= summary ("diffdump " ++ showVersion version)
+
+data DumpCfg = DumpCfg {
+    dumpPath        :: FilePath
+  , dumpArgs        :: [String]
+  } deriving (Show, Eq)
+
+data DiffCfg = DiffCfg {
+    diffPath :: FilePath
+  , diffArgs :: [String]
+  } deriving (Show, Eq)
+
+main :: IO ()
+main = do
+    tmpDir <- getTemporaryDirectory
+    defaultOutputDir <- createUniqueDirectory 0 (tmpDir </> ".diffdump")
+
+    as <- cmdArgs (diffDumpOpts defaultOutputDir)
+
+    n <- getNumProcessors
+    when (verbose as) $ do
+      hPutStrLn stderr $ "diffdump: Utilizing " ++ show n ++ " CPU(s)."
+
+    finally (withPool n (parallelMain as)) $
+        removeDirectoryRecursive defaultOutputDir
+
+parallelMain :: DiffDump -> Pool -> IO ()
+parallelMain as threadPool = do
+    let oldRoot   = dropTrailingPathSeparator (olddir as)
+    let newRoot   = if null (newdir as) then oldRoot else dropTrailingPathSeparator (newdir as)
+    let outputDir = dropTrailingPathSeparator (outdir as)
+
+    let firstCfg  = mkDumpCfg (cmd1 as) (func as ++ cmd as) (arg_list as ++ arg as ++ arg1 as)
+    let secondCfg = mkDumpCfg (cmd2 as) (func as ++ cmd as) (arg_list as ++ arg as ++ arg2 as)
+
+    let recArgs   = if recursive as then ["-r"] else []
+    let uniArgs   = if   unified as then ["-u"] else []
+    let diffCfg   = DiffCfg (diff as) (recArgs ++ uniArgs ++ diff_arg as)
+
+    let aDir = outputDir </> "a"
+    let bDir = outputDir </> "b"
+
+    createTempDirectory (force as) aDir
+    createTempDirectory (force as) bDir
+
+    oldRootIsDir <- doesDirectoryExist oldRoot
+    newRootIsDir <- doesDirectoryExist newRoot
+    
+    let diffdump = do
+        if not oldRootIsDir || not newRootIsDir
+           then do
+             when (recursive as) $ do
+                if not oldRootIsDir
+                  then hPutStrLn stderr $ "diffdump: error: " ++ oldRoot ++ " is not a directory"
+                  else hPutStrLn stderr $ "diffdump: error: " ++ newRoot ++ " is not a directory"
+                exitFailure
+                
+             dumpObjFiles aDir bDir (firstCfg, secondCfg) oldRoot newRoot
+           else do
+             when (verbose as) $ do
+               hPutStrLn stderr $ "diffdump: Scanning for files..."
+
+             oldPaths    <- getRelPaths (recursive as) (filetype as) (fileext as) (ignore as) oldRoot
+             commonPaths <- filterM (doesFileExist . (newRoot </>)) oldPaths
+    
+             when (verbose as) $ do
+               hPutStrLn stderr $ "diffdump: Found " ++ show (length commonPaths) ++ " file pairs to compare."
+               hPutStrLn stderr $ "diffdump: Generating dump files... "
+    
+             let dumpOps = map (dumpObj aDir bDir (firstCfg, secondCfg) oldRoot newRoot) commonPaths
+             parallel_ threadPool dumpOps
+
+        when (verbose as) $ do
+          hPutStrLn stderr $ "diffdump: Comparing files... "
+
+        runDiff diffCfg outputDir "a" "b"
+
+    diffMaybe <- finally diffdump $
+        unless (keep_files as) $ do
+            removeDirectoryRecursive aDir
+            removeDirectoryRecursive bDir
+
+    case diffMaybe of
+       Nothing -> return ()
+       Just s  -> do
+                    isatty <- queryTerminal stdOutput
+                    let isColor = not (no_color as) && (color as || isatty)
+                    writeDiff isColor (unified as) (outfile as) s
+
+                    when (verbose as) $ do
+                      hPutStrLn stderr $ "diffdump: Done."
+    
+                    unless (succeed_on_diff as) exitFailure
+
+mkDumpCfg :: String -> String -> [String] -> DumpCfg
+mkDumpCfg "" "" = DumpCfg "cat"  -- Default command, plus custom options
+mkDumpCfg ""  x = DumpCfg x      -- --cmd as command, plus custom options
+mkDumpCfg  x  _ = DumpCfg x      -- Override all defaults
+
+createTempDirectory :: Bool -> DirPath -> IO ()
+createTempDirectory frce p = do
+    pExists <- doesDirectoryExist p
+    when (pExists && not frce) $ do
+        hPutStrLn stderr $ "error: the temporary directory '" ++ p ++ "' already exists.  Use '-f' to proceed anyway."
+        exitFailure
+    createDirectoryIfMissing True p
+
+writeDiff :: Bool -> Bool -> FilePath -> B.ByteString -> IO ()
+writeDiff True   isUni "" = putColorizedDiff isUni
+writeDiff False _isUni "" = B.putStr
+writeDiff _color _isUni p = B.writeFile p
+
+putColorizedDiff :: Bool -> B.ByteString -> IO ()
+putColorizedDiff isUni s = do
+    mapM_ (putColorizedLine isUni) (B8.lines s)
+    setSGR [Reset]  -- cleanup
+
+putColorizedLine :: Bool -> B.ByteString -> IO ()
+putColorizedLine True x  = setSGR [getUnifiedColor (safeHead ' ' x)] >> B8.putStrLn x
+putColorizedLine False x = setSGR [       getColor (safeHead ' ' x)] >> B8.putStrLn x
+
+safeHead :: Char -> B.ByteString -> Char
+safeHead x xs
+  | B.null xs = x
+  | otherwise = B8.head xs
+
+getColor :: Char -> SGR
+getColor '>' = SetColor Foreground Dull Green
+getColor '<' = SetColor Foreground Dull Red
+getColor  _  = Reset
+
+getUnifiedColor :: Char -> SGR
+getUnifiedColor '@' = SetColor Foreground Dull Cyan
+getUnifiedColor '+' = SetColor Foreground Dull Green
+getUnifiedColor '-' = SetColor Foreground Dull Red
+getUnifiedColor  _  = Reset
+
+dumpObj :: DirPath -> DirPath -> (DumpCfg, DumpCfg) -> DirPath -> DirPath -> FilePath -> IO ()
+dumpObj aDir bDir (cfg1, cfg2) oldRoot newRoot p = do
+    runDump cfg1 (aDir</>p) (oldRoot</>p) ""
+    runDump cfg2 (bDir</>p) (newRoot</>p) ""
+
+dumpObjFiles :: DirPath -> DirPath -> (DumpCfg, DumpCfg) -> FilePath -> FilePath -> IO ()
+dumpObjFiles aDir bDir (cfg1, cfg2) oldPath newPath = do
+    input <- if oldPath == "-" || newPath == "-" then B.getContents else return ""
+    runDump cfg1 (aDir</>"dump.txt") oldPath input
+    runDump cfg2 (bDir</>"dump.txt") newPath input
+
+runDiff :: DiffCfg -> DirPath -> FilePath -> FilePath -> IO (Maybe B.ByteString)
+runDiff cfg outputDir p1 p2 = do
+   bracket getCurrentDirectory setCurrentDirectory $ const $ do
+       setCurrentDirectory outputDir
+       (exitCode, s, err) <- readProcessWithExitCode (diffPath cfg) (diffArgs cfg ++ [p1, p2]) ""
+       B.hPutStr stderr err
+       return $ case exitCode of
+          ExitSuccess -> Nothing
+          _           -> Just s
+
+createUniqueDirectory :: Int -> FilePath -> IO FilePath
+createUniqueDirectory n p = do
+      (createDirectory tmp >> return tmp)
+         `catchIOError` (\e -> if isAlreadyExistsError e
+                               then createUniqueDirectory (succ n) p
+                               else throwIO e)
+  where
+    tmp = p ++ show n
+
+runDump :: DumpCfg -> FilePath -> FilePath -> B.ByteString -> IO ()
+runDump cfg outPath p input = do
+    let as = dumpArgs cfg ++ (if p == "-" then [] else [p])
+    s <- readProcess (dumpPath cfg) as input
+    createDirectoryIfMissing True (takeDirectory outPath)
+    B.writeFile outPath s
+
+readProcess :: FilePath -> [String] -> B.ByteString -> IO B.ByteString
+readProcess nm as input = do
+    (exitCode, s, err) <- readProcessWithExitCode nm as input
+    case exitCode of
+      ExitSuccess -> return s
+      _ -> do
+        hPutStrLn stderr $ "diffdump: error executing: " ++ unwords (nm : as)
+        B8.hPutStrLn stderr err
+        exitFailure
+
+getRelPaths :: Bool -> String -> String -> [String] -> DirPath -> IO [FilePath]
+getRelPaths isRecursive fileTy fileExt ignoreDirs p = do
+    ps <- find ((return isRecursive &&? isGoodDir) ||? filePath ==? p) ((return (null fileExt) ||? extension ==? fileExt) &&? expectedFileType fileTy) p
+    return $ map (stripRoot p) ps
+  where
+    isGoodDir = do
+        nm <- fileName
+        return $ nm `notElem` ignoreDirs
+
+expectedFileType :: String -> FilterPredicate
+expectedFileType "" = return True
+expectedFileType s  = do
+    p <- filePath
+    return $ B8.pack s `B8.isInfixOf` unsafePerformIO (readProcess "file" ["--brief", p] "")
+
+stripRoot :: DirPath -> FilePath -> FilePath
+stripRoot pre p = maybe p id (stripPrefix (pre ++ "/") p)
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright 2012 Greg Fitzgerald
+
+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 AUTHORS ``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 AUTHORS 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/PatchDump.hs b/PatchDump.hs
new file mode 100644
--- /dev/null
+++ b/PatchDump.hs
@@ -0,0 +1,54 @@
+import System.Process
+import System.Environment
+import System.Directory
+import System.IO
+import Control.Exception
+
+main :: IO ()
+main = do
+    withTempFile "patchdump" patch
+
+patch :: FilePath -> Handle -> IO ()
+patch tmpFile hdl = do
+    cnts <- getContents
+    hPutStr hdl cnts
+    hClose hdl
+
+    args <- getArgs
+    _ <- readProcess "patch" (args ++ [tmpFile]) ""
+    cnts' <- readFile tmpFile
+    putStr cnts'
+
+{- Borrowed from Real World Haskell -}
+
+{- This function takes two parameters: a filename pattern and another
+   function.  It will create a temporary file, and pass the name and Handle
+   of that file to the given function.
+
+   The temporary file is created with openTempFile.  The directory is the one
+   indicated by getTemporaryDirectory, or, if the system has no notion of
+   a temporary directory, "." is used.  The given pattern is passed to
+   openTempFile.
+
+   After the given function terminates, even if it terminates due to an
+   exception, the Handle is closed and the file is deleted. -}
+withTempFile :: String -> (FilePath -> Handle -> IO a) -> IO a
+withTempFile pattern func =
+    do -- The library ref says that getTemporaryDirectory may raise on
+       -- exception on systems that have no notion of a temporary directory.
+       -- So, we run getTemporaryDirectory under catch.  catch takes
+       -- two functions: one to run, and a different one to run if the
+       -- first raised an exception.  If getTemporaryDirectory raised an
+       -- exception, just use "." (the current working directory).
+       tempdir <- getTemporaryDirectory `onException` return "."
+       (tempfile, temph) <- openTempFile tempdir pattern 
+
+       -- Call (func tempfile temph) to perform the action on the temporary
+       -- file.  finally takes two actions.  The first is the action to run.
+       -- The second is an action to run after the first, regardless of
+       -- whether the first action raised an exception.  This way, we ensure
+       -- the temporary file is always deleted.  The return value from finally
+       -- is the first action's return value.
+       finally (func tempfile temph) 
+               (do hClose temph
+                   removeFile tempfile)
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,7 @@
+module Main (main) where
+
+import Distribution.Simple
+
+main :: IO ()
+main = defaultMain
+
diff --git a/diffdump.cabal b/diffdump.cabal
new file mode 100644
--- /dev/null
+++ b/diffdump.cabal
@@ -0,0 +1,33 @@
+Name:              diffdump
+Synopsis:          Tools for diffing stdout
+Description:       Command-line tools for diffing and patching stdout
+Version:           0.7.5
+License:           BSD3
+License-file:      LICENSE
+Build-type:        Simple
+Maintainer:        Greg Fitzgerald <garious@gmail.com>
+Category:          System
+Cabal-version:     >= 1.6
+
+source-repository head
+  Type:             git
+  Location:         https://github.com/garious/DiffDump
+
+Executable diffdump
+  Main-is:         DiffDump.hs
+  Build-depends:   base >= 4 && < 5
+                 , filemanip >= 0.3.6.2
+                 , ansi-terminal >= 0.5.5
+                 , parallel-io >= 0.3.2
+                 , cmdargs >= 0.10
+                 , bytestring >= 0.10.0.0
+                 , process-extras >= 0.2.0
+                 , directory >= 1.2.0.0
+                 , filepath >= 1.3.0.1
+                 , unix >= 2.6.0.0
+  Ghc-options:     -Wall -threaded
+
+Executable patchdump
+  Main-is:         PatchDump.hs
+  Build-depends:   base
+                 , process >= 1.1.0.2
