diff --git a/LICENSE-2.0 b/LICENSE-2.0
new file mode 100644
--- /dev/null
+++ b/LICENSE-2.0
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,21 @@
+#! /usr/bin/env runhaskell
+
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+import Distribution.Simple
+
+main = defaultMain
diff --git a/cblrepo.cabal b/cblrepo.cabal
new file mode 100644
--- /dev/null
+++ b/cblrepo.cabal
@@ -0,0 +1,47 @@
+name: cblrepo
+version: 0.2.0
+cabal-version: >= 1.6
+license: OtherLicense
+license-file: LICENSE-2.0
+copyright: Copyright (c) 2011 Magnus Therning
+author: Magnus Therning
+maintainer: Magnus Therning <magnus@therning.org>
+stability: experimental
+bug-reports: mailto:magnus@therning.org
+synopsis: Tool to maintain a database of CABAL packages and their dependencies
+description:
+    Helper tool for people maintaining a set of CABAL packages for their
+    distribution.  It maintains a database of packages and verifies
+    dependencies of the entire set as packages are added or updated.  It also
+    makes it trivial to track packages as new versions are released on Hackage.
+
+    It can also be used to build source packages for the ArchLinux distribution.
+category: Utils, Distribution
+build-type: Custom
+
+flag BuildTests
+    Description: Build tests
+    Default: False
+
+executable cblrepo
+    hs-source-dirs: src
+    main-is: Main.hs
+    other-modules: Util.Misc PkgDB AddBase AddCabal BumpPkgs BuildPkgs IdxSync
+        IdxVersion Updates ListPkgs Urls
+    build-depends: base ==4.3.*, cmdargs ==0.7.*, filepath ==1.2.*,
+        directory ==1.1.*, Cabal ==1.10.*, json ==0.4.*,
+        bytestring ==0.9.*, tar ==0.3.*, zlib ==0.5.*, mtl ==2.0.*,
+        process ==1.0.*, Unixutils ==1.36.*, unix ==2.4.*,
+        ansi-wl-pprint ==0.6.*
+
+executable tests
+    hs-source-dirs: tst src
+    main-is: tst.hs
+    other-modules: TestSystem TestPkgDB
+    if flag(BuildTests)
+        build-depends: base ==4.3.*, test-framework-th ==0.1.*, HUnit ==1.2.*,
+            test-framework-hunit ==0.2.*, test-framework ==0.3.*
+    else
+        buildable: False
+
+-- vim: set tw=0 :
diff --git a/src/AddBase.hs b/src/AddBase.hs
new file mode 100644
--- /dev/null
+++ b/src/AddBase.hs
@@ -0,0 +1,50 @@
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module AddBase where
+
+import PkgDB
+import Util.Misc
+
+import Control.Monad
+import Control.Monad.Reader
+import Data.List
+import Data.Maybe
+import Distribution.Text
+import Distribution.Version
+import System.FilePath
+
+addBase :: ReaderT Cmds IO ()
+addBase = do
+    pkgs <- cfgGet pkgVers
+    dR <- cfgGet dryRun
+    guard $ isJust $ (sequence $ map (simpleParse . snd) pkgs :: Maybe [Version])
+    let ps = map (\ (n, v) -> (n, fromJust $ simpleParse v)) pkgs
+    dbFn <- cfgGet dbFile
+    db <- liftIO $ readDb dbFn
+    case doAddBase db ps of
+        Left brkOthrs -> liftIO $ mapM_ printBrksOth brkOthrs
+        Right newDb -> liftIO $ unless dR $ saveDb newDb dbFn
+
+doAddBase db pkgs = let
+        (_, fails) = partition (\ (n, v) -> canBeAdded db n v) pkgs
+        newDb = foldl (\ d (n, v) -> addBasePkg d n v) db pkgs
+        brkOthrs = map (\ (n, v) -> ((n, v), checkDependants db n v)) fails
+    in if null fails
+        then Right newDb
+        else Left brkOthrs
+
+canBeAdded db n v = null $ checkDependants db n v
diff --git a/src/AddCabal.hs b/src/AddCabal.hs
new file mode 100644
--- /dev/null
+++ b/src/AddCabal.hs
@@ -0,0 +1,87 @@
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module AddCabal where
+
+import PkgDB
+import Util.Misc
+
+import Codec.Archive.Tar as Tar
+import Codec.Compression.GZip as GZip
+import Control.Monad
+import Control.Monad.Error
+import Control.Monad.Reader
+import Data.List
+import Data.Maybe
+import Data.Version
+import Distribution.Compiler
+import Distribution.PackageDescription
+import Distribution.PackageDescription.Configuration
+import Distribution.PackageDescription.Parse
+import Distribution.System
+import Distribution.Text
+import Distribution.Verbosity
+import Distribution.Version
+import System.Directory
+import System.FilePath
+import qualified Data.ByteString.Lazy.Char8 as BS
+import qualified Distribution.Package as P
+import System.Posix.Files
+import System.Unix.Directory
+import System.Process
+import System.Exit
+import System.IO
+
+addCabal :: ReaderT Cmds IO ()
+addCabal = do
+    dbFn <- cfgGet dbFile
+    db <- liftIO $ readDb dbFn
+    pD <- cfgGet patchDir
+    cbls <- cfgGet cbls
+    dR <- cfgGet dryRun
+    genPkgs <- liftIO $ mapM (\ c -> withTemporaryDirectory "/tmp/cblrepo." (readCabal pD c)) cbls
+    let pkgNames = map ((\ (P.PackageName n) -> n ) . P.pkgName . package . packageDescription) genPkgs
+    let tmpDb = filter (\ p -> not $ pkgName p `elem` pkgNames) db
+    case doAddCabal tmpDb genPkgs of
+        Left (unSats, brksOthrs) -> liftIO (mapM_ printUnSat unSats >> mapM_ printBrksOth brksOthrs)
+        Right newDb -> liftIO $ unless dR $ saveDb newDb dbFn
+
+doAddCabal db pkgs = let
+        (succs, fails) = partition (canBeAdded db) pkgs
+        newDb = foldl addPkg2 db (map (fromJust . finalizeToCblPkg db) succs)
+        unSats = catMaybes $ map (finalizeToDeps db) fails
+        genPkgName = ((\ (P.PackageName n) -> n ) . P.pkgName . package . packageDescription)
+        genPkgVer = P.pkgVersion . package . packageDescription
+        brksOthrs = filter (not . null . snd) $ map (\ p -> ((genPkgName p, genPkgVer p), checkDependants db (genPkgName p) (genPkgVer p))) fails
+    in case (succs, fails) of
+        (_, []) -> Right newDb
+        ([], _) -> Left (unSats, brksOthrs)
+        (_, _) -> doAddCabal newDb fails
+
+canBeAdded db p = let
+        finable = either (const False) (const True) (finalizePkg db p)
+        n = ((\ (P.PackageName n) -> n ) . P.pkgName . package . packageDescription) p
+        v = P.pkgVersion $ package $ packageDescription p
+        depsOK = null $ checkDependants db n v
+    in finable && depsOK
+
+finalizeToCblPkg db p = case finalizePkg db p of
+    Right (pd, _) -> Just $ createCblPkg pd
+    _ -> Nothing
+
+finalizeToDeps db p = case finalizePkg db p of
+    Left ds -> Just $ (((\ (P.PackageName n) -> n ) . P.pkgName . package . packageDescription) p, ds)
+    _ -> Nothing
diff --git a/src/BuildPkgs.hs b/src/BuildPkgs.hs
new file mode 100644
--- /dev/null
+++ b/src/BuildPkgs.hs
@@ -0,0 +1,32 @@
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module BuildPkgs where
+
+import PkgDB
+import Util.Misc
+
+import Control.Monad
+import Control.Monad.Reader
+import Data.List
+import Data.Maybe
+import System.FilePath
+
+buildPkgs :: ReaderT Cmds IO ()
+buildPkgs = do
+    db <- cfgGet dbFile >>= liftIO . readDb
+    pkgs <- cfgGet pkgs
+    liftIO $ mapM_ putStrLn $ transitiveDependants db pkgs
diff --git a/src/BumpPkgs.hs b/src/BumpPkgs.hs
new file mode 100644
--- /dev/null
+++ b/src/BumpPkgs.hs
@@ -0,0 +1,41 @@
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module BumpPkgs where
+
+import PkgDB
+import Util.Misc
+
+import Control.Monad
+import Control.Monad.Reader
+import Data.List
+import Data.Maybe
+import System.FilePath
+
+bumpPkgs :: ReaderT Cmds IO ()
+bumpPkgs = do
+    dbFn <- cfgGet dbFile
+    db <- liftIO $ readDb dbFn
+    dR <- cfgGet dryRun
+    pkgs <- cfgGet pkgs
+    incl <- cfgGet inclusive
+    let bpkgs = transDependants db incl pkgs
+    let newDb = foldl (\ db p -> bumpRelease db p) db bpkgs
+    if dR
+        then liftIO $ putStrLn "Would bump:" >> mapM_ putStrLn bpkgs
+        else liftIO $ saveDb newDb dbFn
+
+transDependants db i pkgs = filter ((||) i . (not . flip elem pkgs)) $ transitiveDependants db pkgs
diff --git a/src/IdxSync.hs b/src/IdxSync.hs
new file mode 100644
--- /dev/null
+++ b/src/IdxSync.hs
@@ -0,0 +1,28 @@
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module IdxSync where
+
+import Util.Misc
+
+import Control.Monad.Reader
+import System.FilePath
+import Control.Monad.Error
+
+idxSync :: ReaderT Cmds IO ()
+idxSync = do
+    aD <- cfgGet appDir
+    liftIO $ getFromURL "http://hackage.haskell.org/packages/archive/00-index.tar.gz" (aD </> "00-index.tar.gz")
diff --git a/src/IdxVersion.hs b/src/IdxVersion.hs
new file mode 100644
--- /dev/null
+++ b/src/IdxVersion.hs
@@ -0,0 +1,56 @@
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module IdxVersion where
+
+import Util.Misc
+
+import Codec.Archive.Tar as Tar
+import Codec.Compression.GZip as GZip
+import Control.Monad
+import Control.Monad.Reader
+import Data.List
+import Data.Maybe
+import Distribution.Text
+import Distribution.Version
+import System.FilePath
+import qualified Data.ByteString.Lazy.Char8 as BS
+
+idxVersion :: ReaderT Cmds IO ()
+idxVersion = do
+    aD <- cfgGet appDir
+    pkgs <- cfgGet pkgs
+    liftIO $ do
+        es <- liftM (Tar.read . GZip.decompress) (BS.readFile $ aD </> "00-index.tar.gz")
+        mapM_ (printVersions . findVersions es) pkgs
+
+findVersions es p = (p, findV p es [])
+    where
+        findV pkgName (Next e es) acc = let
+                eP = entryPath e
+                (ePkg:v:_) = splitDirectories eP
+            in if ('/' `elem` eP) && (pkgName == ePkg)
+                then findV pkgName es (v:acc)
+                else findV pkgName es acc
+
+        findV _ Done acc = let
+                vs :: [Version]
+                vs = map (fromJust . simpleParse) acc
+            in map display $ sort vs
+
+printVersions (p, vs) = do
+    putStr $ p ++ " : "
+    putStrLn $ unwords vs
diff --git a/src/ListPkgs.hs b/src/ListPkgs.hs
new file mode 100644
--- /dev/null
+++ b/src/ListPkgs.hs
@@ -0,0 +1,38 @@
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module ListPkgs where
+
+import Util.Misc
+import PkgDB
+
+import Control.Monad
+import Control.Monad.Reader
+import Data.Maybe
+import Distribution.Text
+import System.FilePath
+
+listPkgs :: ReaderT Cmds IO ()
+listPkgs = do
+    iB <- cfgGet incBase
+    db <- cfgGet dbFile >>= liftIO . readDb
+    let pkgs = if not iB
+            then filter (not . isBasePkg) db
+            else db
+    liftIO $ mapM_ printCblPkgShort pkgs
+
+printCblPkgShort (p, (v, _, r)) =
+    putStrLn $ p ++ " ==" ++ (display v) ++ " (" ++ (show r) ++")"
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,135 @@
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module Main where
+
+import AddBase
+import AddCabal
+import BuildPkgs
+import BumpPkgs
+import IdxSync
+import IdxVersion
+import ListPkgs
+import Updates
+import Util.Misc
+import Urls
+import PkgBuild
+
+import Paths_cblrepo
+
+import Control.Monad
+import Control.Monad.Reader
+import Distribution.Text
+import System.Console.CmdArgs
+import System.Directory
+import System.FilePath
+
+-- {{{1 command line arguments
+argAppDir = appDir := def += explicit += name "appdir" += help "application data directory" += typDir
+argDbFile = dbFile := "cblrepo.db" += explicit += name "db" += help "package database" += typFile
+argDryRun = dryRun := False += explicit += name "n" += help "dry run"
+
+cmdAddBasePkg = record defAddBasePkg
+    [ argAppDir, argDbFile, argDryRun
+    , pkgVers := def += args += typ "STRING,STRING"
+    ] += name "addbasepkg" += help "add base packages" += details
+        [ "The format for a package is <name>,<version>." ]
+
+cmdAddPkg = record defAddPkg
+    [ argAppDir, argDbFile
+    , patchDir := "patches" += explicit += name "patchdir" += help "location of patches" += typDir
+    , argDryRun
+    , cbls := def += args += typ "CABAL"
+    ] += name "add" += help "add a package from a Cabal file" += details
+        [ "There are three ways to specify the location of the Cabal file:"
+        , " 1. The filename of the Cabal file."
+        , " 2. A URL where the Cabal can be found (for file:// URLs the full absolute path is required)."
+        , " 3. A pair, <name>,<version>, will load the Cabal file out of an index file (see idxupdate)"
+        , "All three format may be mixed on the command line."
+        ]
+
+cmdBumpPkgs = record defBumpPkgs
+    [ argAppDir, argDbFile
+    , argDryRun
+    , inclusive := False += explicit += name "inclusive" += help "include listed packages"
+    , pkgs := def += args += typ "PKG"
+    ] += name "bump" += help "bump packages that need it after updating the named packages"
+
+cmdBuildPkgs = record defBuildPkgs
+    [ argAppDir, argDbFile
+    , pkgs := def += args += typ "PKG"
+    ] += name "build" += help "list packages that need rebuilding, in order"
+
+cmdIdxSync = record defIdxSync [ argAppDir ] += help "update the index"
+
+cmdIdxVersion = record defIdxVersion
+    [ argAppDir
+    , pkgs := def += args += typ "PKG"
+    ] += help "list available versions"
+
+cmdUpdates = record defUpdates [ argAppDir , argDbFile ] += name "updates" += help "check for availabale updates"
+
+cmdListPkgs = record defListPkgs
+    [ argAppDir, argDbFile
+    , incBase := False += help "include base packages in listing"
+    ] += name "list" += help "list packages in repo"
+
+cmdUrls = record defUrls
+    [ argAppDir
+    , pkgVers := def += args += typ "STRING,STRING"
+    ] += help "list urls of cabal files for the given packages" += details
+        [ "The format for a package is <name>,<version>." ]
+
+cmdPkgBuild = record defPkgBuild
+    [ argAppDir, argDbFile
+    , patchDir := "patches" += explicit += name "patchdir" += help "location of patches (patches)" += typDir
+    , pkgs := def += args += typ "PKG"
+    ] += help "create a PKGBUILD, and other files necessary for an Arch package"
+
+cmds = cmdArgsMode_ $ modes_
+    [ cmdAddBasePkg
+    , cmdAddPkg
+    , cmdBuildPkgs
+    , cmdBumpPkgs
+    , cmdIdxSync
+    , cmdIdxVersion
+    , cmdListPkgs
+    , cmdUpdates
+    , cmdUrls
+    , cmdPkgBuild
+    ]
+    += program progName
+    += summary (progName ++ " v" ++ (display version))
+    += help "maintain a database of dependencies of CABAL packages"
+
+-- {{{1 main
+main = do
+    defAppDir <- getAppUserDataDirectory progName
+    cmdArgsRun cmds >>= \ c -> do
+        let aD = if null (appDir c) then defAppDir else (appDir c)
+        let c' = c { appDir = aD }
+        createDirectoryIfMissing True (aD)
+        case c' of
+            AddBasePkg {} -> runReaderT addBase c'
+            AddPkg {} -> runReaderT addCabal c'
+            BuildPkgs {} -> runReaderT buildPkgs c'
+            BumpPkgs {} -> runReaderT bumpPkgs c'
+            IdxSync {} -> runReaderT idxSync c'
+            IdxVersion {} -> runReaderT idxVersion c'
+            ListPkgs {} -> runReaderT listPkgs c'
+            Updates {} -> runReaderT updates c'
+            Urls {} -> runReaderT urls c'
+            PkgBuild {} -> runReaderT pkgBuild c'
diff --git a/src/PkgDB.hs b/src/PkgDB.hs
new file mode 100644
--- /dev/null
+++ b/src/PkgDB.hs
@@ -0,0 +1,145 @@
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module PkgDB where
+
+import Util.Misc
+
+import Control.Exception as CE
+import Data.List
+import Data.Maybe
+import Data.Maybe
+import Distribution.PackageDescription
+import Distribution.Text
+import Distribution.Version
+import System.IO.Error
+import Text.JSON
+import qualified Distribution.Package as P
+import qualified Distribution.Version as V
+
+type CblPkg = (String, (V.Version, [P.Dependency], Int))
+type CblDB = [CblPkg]
+
+pkgName :: CblPkg -> String
+pkgName (n, _) = n
+
+pkgVersion :: CblPkg -> V.Version
+pkgVersion (_, (v, _, _)) = v
+
+pkgDeps :: CblPkg -> [P.Dependency]
+pkgDeps (_, (_, ds, _)) = ds
+
+pkgRelease :: CblPkg -> Int
+pkgRelease (_, (_, _, i)) = i
+
+createCblPkg :: PackageDescription -> CblPkg
+createCblPkg pd = (name, (version, deps, 1))
+    where
+        name = (\ (P.PackageName n) -> n) (P.pkgName $ package pd)
+        version = P.pkgVersion $ package pd
+        deps = buildDepends pd
+
+getDependencyOn :: String -> CblPkg -> Maybe P.Dependency
+getDependencyOn n p = find (\ d -> depName d == n) (pkgDeps p)
+
+isBasePkg (_, (_, ds, _)) = null ds
+
+emptyPkgDB :: CblDB
+emptyPkgDB = []
+
+addPkg :: CblDB -> String -> V.Version -> [P.Dependency] -> Int -> CblDB
+addPkg db n v ds r = nubBy cmp newdb
+    where
+        cmp (n1, _) (n2, _) = n1 == n2
+        newdb = (n, (v, ds, r)) : db
+
+addPkg2 db (n, (v, ds, r)) = addPkg db n v ds r
+
+addBasePkg db n v = addPkg db n v [] 0
+
+delPkg :: CblDB -> String -> CblDB
+delPkg db n = filter (\ p -> n /= pkgName p) db
+
+lookupPkg :: CblDB -> String -> Maybe CblPkg
+lookupPkg db n = maybe Nothing (\ s -> Just (n, s)) (lookup n db)
+
+lookupDependencies :: CblDB -> String -> Maybe [String]
+lookupDependencies db n =
+    case lookupPkg db n of
+        Nothing -> Nothing
+        Just p -> let
+                ds = pkgDeps p
+            in Just $ map depName ds
+
+lookupDependants :: CblDB -> String -> [String]
+lookupDependants db n = map pkgName $ filter (\ p -> doesDependOn p n) db
+    where
+        doesDependOn p n = n `elem` (map depName $ pkgDeps p)
+
+checkDependants db n v = let
+        d1 = catMaybes $ map (lookupPkg db) (lookupDependants db n)
+        d2 = map (\ p -> (pkgName p, getDependencyOn n p)) d1
+        fails = filter (not . withinRange v . depVersionRange . fromJust . snd) d2
+    in fails
+
+transitiveDependants db pkgs = keepLast $ concat $ map transUsersOfOne pkgs
+    where
+        transUsersOfOne pkg = pkg : (keepLast $ concat $ map (transUsersOfOne) (lookupDependants db pkg))
+        keepLast = reverse . nub . reverse
+
+lookupRelease :: CblDB -> String -> Maybe Int
+lookupRelease db n = lookupPkg db n >>= return . pkgRelease
+
+bumpRelease db n = let
+        bump (n', (v', d', r')) = (n', (v', d', r' + 1))
+    in maybe db (addPkg2 db . bump) (lookupPkg db n)
+readDb :: FilePath -> IO CblDB
+readDb fp = (flip CE.catch)
+    (\ e -> if isDoesNotExistError e
+        then return emptyPkgDB
+        else throwIO e)
+    $ do
+        r <- readFile fp >>= return . sequence . map decode . lines
+        case r of
+            Ok a -> return a
+            Error s -> fail s
+
+saveDb :: CblDB -> FilePath -> IO ()
+saveDb db fp = writeFile fp s
+    where
+        s = unlines $ map (encode . showJSON) db
+
+instance JSON V.Version where
+    showJSON v = makeObj [ ("Version", showJSON $ display v) ]
+    readJSON (JSObject o) = let
+            jAssoc = fromJSObject o
+            resultToMaybe (Ok a) = Just a
+            resultToMaybe _ = Nothing
+            version = lookup "Version" jAssoc >>= resultToMaybe . readJSON >>= simpleParse
+        in
+            maybe (fail "Not a version object") return version
+    readJSON _ = fail "Not a version object"
+
+instance JSON P.Dependency where
+    showJSON d = makeObj [ ("Dependency", showJSON $ display d) ]
+    readJSON (JSObject o) = let
+            jAssoc = fromJSObject o
+            resultToMaybe (Ok a) = Just a
+            resultToMaybe _ = Nothing
+            dependency = lookup "Dependency" jAssoc >>= resultToMaybe . readJSON >>= simpleParse
+        in
+            maybe (fail "Not a dependency object") return dependency
+    readJSON _ = fail "Not a version object"
diff --git a/src/PkgDB.hs-boot b/src/PkgDB.hs-boot
new file mode 100644
--- /dev/null
+++ b/src/PkgDB.hs-boot
@@ -0,0 +1,9 @@
+module PkgDB where
+
+import qualified Distribution.Package as P
+import qualified Distribution.Version as V
+
+type CblPkg = (String, (V.Version, [P.Dependency], Int))
+type CblDB = [CblPkg]
+
+lookupPkg :: CblDB -> String -> Maybe CblPkg
diff --git a/src/Updates.hs b/src/Updates.hs
new file mode 100644
--- /dev/null
+++ b/src/Updates.hs
@@ -0,0 +1,70 @@
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module Updates where
+
+import PkgDB
+import Util.Misc
+
+import Codec.Archive.Tar as Tar
+import Codec.Compression.GZip as GZip
+import Control.Monad
+import Control.Monad.Reader
+import Data.Maybe
+import Distribution.Text
+import Distribution.Version
+import System.FilePath
+import qualified Data.ByteString.Lazy.Char8 as BS
+
+updates :: ReaderT Cmds IO ()
+updates = do
+    db <- cfgGet dbFile >>= liftIO . readDb
+    aD <- cfgGet appDir
+    entries <- liftIO $ liftM (Tar.read . GZip.decompress)
+        (BS.readFile $ aD </> "00-index.tar.gz")
+    let nonBasePkgs = filter (\ (_, (_, ds, _)) -> not $ null ds) db
+    let pkgsNVers = map (\ (p, (v, _, _)) -> (p, v)) nonBasePkgs
+    let availPkgs = catMaybes $ eMap extractPkgVer entries
+    let outdated = filter
+            (\ (p, v) -> maybe False (> v) (latestVer p availPkgs))
+            pkgsNVers
+    liftIO $ mapM_ (flip printOutdated availPkgs) outdated
+
+type PkgVer = (String, Version)
+
+extractPkgVer :: Entry -> Maybe PkgVer
+extractPkgVer e = let
+        ep = entryPath e
+        isCabal = '/' `elem` ep
+        (pkg:ver':_) = map dropTrailingPathSeparator $ splitPath ep
+        ver = simpleParse ver'
+    in if isCabal && isJust ver
+        then Just $ (pkg, (fromJust ver))
+        else Nothing
+
+latestVer p pvs = let
+        vs = map snd $ filter ((== p) . fst) pvs
+    in if null vs
+        then Nothing
+        else Just $ maximum vs
+
+eMap _ Done = []
+eMap f (Next e es) = (f e):(eMap f es)
+
+printOutdated (p, v) avail = let
+        l = fromJust $ latestVer p avail
+    in
+        putStrLn $ p ++ ": " ++ (display v) ++ " (" ++ (display l) ++ ")"
diff --git a/src/Urls.hs b/src/Urls.hs
new file mode 100644
--- /dev/null
+++ b/src/Urls.hs
@@ -0,0 +1,28 @@
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module Urls where
+
+import Util.Misc
+
+import Control.Monad.Reader
+
+urls :: ReaderT Cmds IO ()
+urls = do
+    pkgs <- cfgGet pkgVers
+    liftIO $ mapM_ (putStrLn . createUrl) pkgs
+
+createUrl (pkg, ver) = "http://hackage.haskell.org/packages/archive/" ++ pkg ++ "/" ++ ver ++ "/" ++ pkg ++ ".cabal"
diff --git a/src/Util/Misc.hs b/src/Util/Misc.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/Misc.hs
@@ -0,0 +1,204 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module Util.Misc where
+
+import {-# SOURCE #-} PkgDB
+
+import Codec.Archive.Tar as Tar
+import Codec.Compression.GZip as GZip
+import Control.Concurrent
+import Control.Monad
+import Control.Monad.Error
+import Control.Monad.Reader
+import Data.Data
+import Data.List
+import Data.Typeable
+import Distribution.Compiler
+import Distribution.Package
+import Distribution.PackageDescription
+import Distribution.PackageDescription.Configuration
+import Distribution.PackageDescription.Parse
+import Distribution.System
+import Distribution.Text
+import Distribution.Verbosity
+import Distribution.Version
+import System.Directory
+import System.Exit
+import System.FilePath
+import System.IO
+import System.Posix.Files
+import System.Process
+import qualified Control.Exception as CE
+import qualified Data.ByteString.Lazy.Char8 as BS
+
+-- {{{1 Dependency
+depName (Dependency (PackageName n) _) = n
+depVersionRange (Dependency _ vr) = vr
+
+-- {{{ print functions
+printUnSat (n, ds) = do
+    putStrLn $ "Failed to satisfy the following dependencies for " ++ n ++ ":"
+    mapM_ (putStrLn . ("  " ++) . display) ds
+
+printBrksOth  ((n, v), brks) = do
+    putStrLn $ "Adding " ++ n ++ " " ++ (display v) ++ " would break:"
+    mapM_ (\ (bN, (Just bD)) -> putStrLn $ "  " ++ bN ++ " : " ++ (display bD)) brks
+
+-- {{{1 program variables
+progName = "cblrepo"
+dbName = progName ++ ".db"
+
+-- {{{1 command line argument type
+data Cmds
+    = AddBasePkg { appDir :: FilePath, dbFile :: FilePath, dryRun :: Bool, pkgVers :: [(String, String)] }
+    | AddPkg { appDir :: FilePath, dbFile :: FilePath, patchDir :: FilePath, dryRun :: Bool, cbls :: [FilePath] }
+    | BuildPkgs { appDir :: FilePath, dbFile :: FilePath, pkgs :: [String] }
+    | BumpPkgs { appDir :: FilePath, dbFile :: FilePath, dryRun :: Bool, inclusive :: Bool, pkgs :: [String] }
+    | IdxSync { appDir :: FilePath }
+    | IdxVersion { appDir :: FilePath, pkgs :: [String] }
+    | ListPkgs { appDir :: FilePath, dbFile :: FilePath, incBase :: Bool }
+    | Updates { appDir :: FilePath, dbFile :: FilePath }
+    | Urls { appDir :: FilePath, pkgVers :: [(String, String)] }
+    | PkgBuild { appDir :: FilePath, dbFile :: FilePath, patchDir :: FilePath, pkgs :: [String] }
+    deriving (Show, Data, Typeable)
+
+defAddBasePkg = AddBasePkg "" "" True []
+defAddPkg = AddPkg "" "" "" True []
+defBuildPkgs =  BuildPkgs "" "" []
+defBumpPkgs =  BumpPkgs "" "" False False []
+defIdxSync =  IdxSync ""
+defIdxVersion =  IdxVersion "" []
+defListPkgs =  ListPkgs "" "" True
+defUpdates =  Updates "" ""
+defUrls =  Urls "" []
+defPkgBuild =  PkgBuild "" "" "" []
+
+cfgGet f = liftM f ask
+
+-- {{{1 getFromURL
+getFromURL url fn = do
+    (ec, _, er) <- readProcessWithExitCode "curl" ["-f", "-o", fn, url] ""
+    case ec of
+        ExitSuccess -> return ()
+        ExitFailure _ -> do
+            hPutStrLn stderr ("Failed downloading " ++ url)
+            hPutStrLn stderr er
+            exitFailure
+
+-- {{{1 applyPatchIfExist
+applyPatch origFilename patchFilename = do
+    (ec, _, err) <- readProcessWithExitCode "patch" [origFilename, patchFilename] ""
+    case ec of
+        ExitSuccess -> return ()
+        ExitFailure _ ->
+            hPutStrLn stderr "Failed patching the .cabal file" >> exitFailure
+
+applyPatchIfExist origFilename patchFilename =
+    fileExist patchFilename >>= flip when (applyPatch origFilename patchFilename)
+
+-- {{{1 package descriptions
+-- {{{2 readCabal
+data LocType = Url | Idx | File
+
+-- | Read in a Cabal file.
+readCabal patchDir loc tmpDir = let
+        locType
+            | isInfixOf "://" loc = Url
+            | ',' `elem` loc = Idx
+            | otherwise = File
+
+        copyCabal tmpDir loc = copyFile loc fn >> return fn
+            where fn = tmpDir </> takeFileName loc
+
+        downloadCabal tmpDir loc = getFromURL loc fn >> return fn
+            where
+                fn = tmpDir </> takeFileName loc
+
+        extractCabal tmpDir loc = let
+                (p, (_: v)) = span (/= ',') loc
+                path = p </> v </> p ++ ".cabal"
+                pkgStr = p ++ " " ++ v
+                fn = tmpDir </> (p ++ ".cabal")
+
+                esFindEntry p (Next e es) = if p == (entryPath e)
+                    then Just e
+                    else esFindEntry p es
+                esFindEntry _ _ = Nothing
+
+                eGetContent e = let
+                        ec = entryContent e
+                    in case ec of
+                        NormalFile c _ -> Just $ BS.unpack c
+                        _ -> Nothing
+
+            in do
+                fp <- getAppUserDataDirectory "cblrepo"
+                es <- liftM (Tar.read . GZip.decompress)
+                    (BS.readFile $ fp </> "00-index.tar.gz")
+                e <- maybe (error $ "No entry for " ++ pkgStr)
+                    return
+                    (esFindEntry path es)
+                cbl <- maybe (error $ "Failed to extract contents for " ++ pkgStr)
+                    return
+                    (eGetContent e)
+                writeFile fn cbl
+                return fn
+
+        extractName fn = liftM name $ readPackageDescription silent fn
+            where
+                packageName (PackageName s) = s
+                name = packageName . pkgName . package . packageDescription
+
+    in do
+        cblFn <- case locType of
+            File -> copyCabal tmpDir loc
+            Idx -> extractCabal tmpDir loc
+            Url -> downloadCabal tmpDir loc
+        pn <- extractName cblFn
+        let patchFn = patchDir </> pn <.> "cabal"
+        return patchFn
+        applyPatchIfExist cblFn patchFn
+        readPackageDescription silent cblFn
+
+-- {{{2 finalising
+finalizePkg db = finalizePackageDescription
+    [] -- no flags
+    (checkAgainstDb db)
+    (Platform X86_64 buildOS) -- platform
+    (CompilerId GHC (Version [7,0,2] []))  -- compiler version
+    [] -- no additional constraints
+
+checkAgainstDb db dep = let
+        dN = depName dep
+        dVR = depVersionRange dep
+    in case lookupPkg db dN of
+        Nothing -> False
+        Just (_, (v, _, _)) -> withinRange v dVR
+
+-- {{{1 allPatches
+allPatches pn patchDir = let
+        cblPatch = patchDir </> pn <.> "cabal"
+        pkgPatch = patchDir </> pn <.> "pkgbuild"
+        bldPatch = patchDir </> pn <.> "build"
+    in do
+        cE <- fileExist cblPatch
+        pE <- fileExist pkgPatch
+        bE <- fileExist bldPatch
+        return (if cE then Just cblPatch else Nothing,
+            if pE then Just pkgPatch else Nothing,
+            if bE then Just bldPatch else Nothing)
diff --git a/tst/TestPkgDB.hs b/tst/TestPkgDB.hs
new file mode 100644
--- /dev/null
+++ b/tst/TestPkgDB.hs
@@ -0,0 +1,52 @@
+{-# OPTIONS_GHC -XTemplateHaskell #-}
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module TestPkgDB
+    ( testGroup
+    ) where
+
+import PkgDB
+
+import Data.Maybe
+import Distribution.Text
+import Test.Framework.Providers.HUnit
+import Test.Framework.TH
+import Test.HUnit
+
+-- {{{1 transitiveDependants
+theDb :: CblDB
+theDb =
+    [ ("pkgA", (fromJust $ simpleParse "1.0", [])) -- nothing depends on this pkg
+    -- a chain of two
+    , ("pkgB", (fromJust $ simpleParse "1.0", []))
+    , ("pkgC", (fromJust $ simpleParse "1.0", map (fromJust . simpleParse) ["pkgB"]))
+    -- a chain of three
+    , ("pkgD", (fromJust $ simpleParse "1.0", []))
+    , ("pkgE", (fromJust $ simpleParse "1.0", map (fromJust . simpleParse) ["pkgD"]))
+    , ("pkgF", (fromJust $ simpleParse "1.0", map (fromJust . simpleParse) ["pkgE"]))
+    ]
+
+case_TD_one = do transitiveDependants theDb ["pkgA"] @=? ["pkgA"]
+case_TD_two = do transitiveDependants theDb ["pkgB"] @=? ["pkgB", "pkgC"]
+case_TD_three = do transitiveDependants theDb ["pkgD"] @=? ["pkgD", "pkgE", "pkgF"]
+-- pass in packages in correct order
+case_TD_ordering1 = do transitiveDependants theDb ["pkgD", "pkgF"] @=? ["pkgD", "pkgE", "pkgF"]
+-- pass in packages in reverse order
+case_TD_ordering2 = do transitiveDependants theDb ["pkgF", "pkgD"] @=? ["pkgD", "pkgE", "pkgF"]
+
+-- {{{1 testGroup
+testGroup = $(testGroupGenerator)
diff --git a/tst/TestSystem.hs b/tst/TestSystem.hs
new file mode 100644
--- /dev/null
+++ b/tst/TestSystem.hs
@@ -0,0 +1,31 @@
+{-# OPTIONS_GHC -XTemplateHaskell #-}
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module TestSystem
+    ( testGroup
+    ) where
+
+import Data.List
+import Test.Framework.Providers.HUnit
+import Test.Framework.TH
+import Test.HUnit
+
+-- {{{1 nub works as expected
+case_nub_order = do nub [1,2,1,3,1] @=? [1,2,3]
+
+-- {{{1 testGroup
+testGroup = $(testGroupGenerator)
diff --git a/tst/tst.hs b/tst/tst.hs
new file mode 100644
--- /dev/null
+++ b/tst/tst.hs
@@ -0,0 +1,28 @@
+{-
+ - Copyright 2011 Per Magnus Therning
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -     http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -}
+
+module Main where
+
+import qualified TestPkgDB
+import qualified TestSystem
+
+import Test.Framework
+
+main = defaultMain
+    [ TestPkgDB.testGroup
+    , TestSystem.testGroup
+    ]
+
