packages feed

cblrepo-0.6: src/PkgDB.hs

{-
 - 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

-- {{{1 imports
import Control.Exception as CE
import Control.Monad
import Data.Data
import Data.List
import Data.Maybe
import Data.Typeable
import Distribution.PackageDescription
import Distribution.Text
import System.IO.Error
import Text.JSON
import qualified Distribution.Package as P
import qualified Distribution.Version as V

-- {{{ temporary
_depName (P.Dependency (P.PackageName n) _) = n
_depVersionRange (P.Dependency _ vr) = vr

-- {{{1 types
data Pkg
    = GhcPkg { version :: V.Version }
    | DistroPkg { version :: V.Version, release :: String }
    | RepoPkg { version :: V.Version, deps :: [P.Dependency], release :: String }
    deriving (Eq, Show)

type CblPkg = (String, Pkg)
type CblDB = [CblPkg]

-- {{{1 packages
pkgName :: CblPkg -> String
pkgName (n, _) = n

pkgVersion :: CblPkg -> V.Version
pkgVersion (_, p) = version p

pkgDeps :: CblPkg -> [P.Dependency]
pkgDeps (_, RepoPkg { deps = d}) = d
pkgDeps _ = []

pkgRelease :: CblPkg -> String
pkgRelease (_, GhcPkg {}) = "xx"
pkgRelease (_, DistroPkg { release = r }) = r
pkgRelease (_, RepoPkg { release = r }) = r

createGhcPkg n v = (n, GhcPkg v)
createDistroPkg n v r = (n, DistroPkg v r)
createRepoPkg n v d r = (n, RepoPkg v d r)

createCblPkg :: PackageDescription -> CblPkg
createCblPkg pd = createRepoPkg 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)

isGhcPkg (_, GhcPkg {}) = True
isGhcPkg _ = False

isDistroPkg (_, DistroPkg {}) = True
isDistroPkg _ = False

isRepoPkg (_, RepoPkg {}) = True
isRepoPkg _ = False

isBasePkg :: CblPkg -> Bool
isBasePkg = not . isRepoPkg

-- {{{1 database
emptyPkgDB :: CblDB
emptyPkgDB = []

addPkg :: CblDB -> String -> Pkg -> CblDB
addPkg db n p = nubBy cmp newdb
    where
        cmp (n1, _) (n2, _) = n1 == n2
        newdb = (n, p):db

addPkg2 :: CblDB -> CblPkg -> CblDB
addPkg2 db (n, p) = addPkg db n p

addGhcPkg :: CblDB -> String -> V.Version -> CblDB
addGhcPkg db n v = addPkg2 db (createGhcPkg n v)

addDistroPkg :: CblDB -> String -> V.Version -> String -> CblDB
addDistroPkg db n v r = addPkg2 db (createDistroPkg n v r)

delPkg :: CblDB -> String -> CblDB
delPkg db n = filter (\ p -> n /= pkgName p) db

bumpRelease :: CblDB -> String -> CblDB
bumpRelease db n = let
        doBump (n', p@RepoPkg { release = r }) = (n', p { release = nr })
            where
                nr = show $ (read r) + 1
        doBump p = p
    in maybe db (addPkg2 db . doBump) (lookupPkg db n)

lookupPkg :: CblDB -> String -> Maybe CblPkg
lookupPkg db n = maybe Nothing (\ p -> Just (n, p)) (lookup n db)

lookupDependants db n = filter (/= n) $ map pkgName $ filter (\ p -> doesDependOn p n) db
    where
        doesDependOn p n = n `elem` (map _depName $ pkgDeps p)

transitiveDependants :: CblDB -> [String] -> [String]
transitiveDependants db names = keepLast $ concat $ map transUsersOfOne names
    where
        transUsersOfOne n = n : transitiveDependants db (lookupDependants db n)
        keepLast = reverse . nub . reverse

-- Todo: test
checkDependants :: CblDB -> String -> V.Version -> [(String, Maybe P.Dependency)]
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 . V.withinRange v . _depVersionRange . fromJust . snd) d2
    in fails

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 db

-- {{{1 JSON instances
instance JSON V.Version where
    showJSON v = makeObj [ ("Version", showJSON $ display v) ]

    readJSON object = do
        obj <- readJSON object
        version <- valFromObj "Version" obj
        maybe (fail "Not a Version object") return (simpleParse version)

instance JSON P.Dependency where
    showJSON d = makeObj [ ("Dependency", showJSON $ display d) ]

    readJSON object = do
        obj <- readJSON object
        dep <- valFromObj "Dependency" obj
        maybe (fail "Not a Dependency object") return (simpleParse dep)

instance JSON Pkg where
    showJSON p@(GhcPkg { version = v}) = makeObj [("GhcPkg", showJSON v)]
    showJSON p@(DistroPkg { version = v, release = r}) =
        makeObj [("DistroPkg", showJSON (v, r))]
    showJSON p@(RepoPkg { version = v, deps = d, release = r }) =
        makeObj [("RepoPkg", showJSON (v, d, r))]

    readJSON object = let
            readGhc = do
                obj <- readJSON object
                v <- valFromObj "GhcPkg" obj >>= readJSON
                return $ GhcPkg v

            readDistro = do
                obj <- readJSON object
                (v, r) <- valFromObj "DistroPkg" obj >>= readJSON
                return $ DistroPkg v r

            readRepo = do
                obj <- readJSON object
                (v, d, r) <- valFromObj "RepoPkg" obj >>= readJSON
                return $ RepoPkg v d r

        in readGhc `mplus` readDistro `mplus` readRepo `mplus` fail "Not a Pkg object"