{- | Module : Distribution.Hackage.DB License : BSD3 Maintainer : simons@cryp.to Stability : provisional Portability : portable This module provides simple access to the Hackage database by means of 'Map'. -} module Distribution.Hackage.DB ( Hackage, readHackage, readHackage', parseHackage, hackagePath , module Data.Map , module Data.Version , module Distribution.Package , module Distribution.PackageDescription ) where import Data.Map import Data.Version import Distribution.Package import Distribution.PackageDescription import qualified Codec.Archive.Tar as Tar import Data.ByteString.Lazy.Char8 ( ByteString ) import qualified Data.ByteString.Lazy.Char8 as BS8 ( readFile ) import qualified Data.ByteString.Lazy as BSC ( unpack ) import Data.String.UTF8 ( toString, fromRep ) import Data.Maybe ( fromMaybe ) import System.Directory ( getHomeDirectory ) import System.FilePath ( joinPath, splitDirectories ) import Distribution.Text ( simpleParse ) import Distribution.PackageDescription.Parse ( parsePackageDescription, ParseResult(..) ) -- | A 'Map' representation of the Hackage database. For sake of -- simplicity, we use 'String' rather than 'PackageName' to represent -- the name of a package. type Hackage = Map String (Map Version GenericPackageDescription) -- | Read the Hackage database from the location determined by 'hackagePath' -- and return a 'Map' that provides fast access to its contents. readHackage :: IO Hackage readHackage = hackagePath >>= readHackage' -- | Read the Hackage database from the given 'FilePath' and return a -- 'Hackage' map that provides fast access to its contents. readHackage' :: FilePath -> IO Hackage readHackage' = fmap parseHackage . BS8.readFile -- | Parse the contents of Hackage's @00-index.tar@ into a 'Hackage' map. parseHackage :: ByteString -> Hackage parseHackage = Tar.foldEntries addEntry empty (error . show) . Tar.read where decodeUTF8 :: ByteString -> String decodeUTF8 = toString . fromRep . BSC.unpack addEntry :: Tar.Entry -> Hackage -> Hackage addEntry e db = case splitDirectories (Tar.entryPath e) of [".",".","@LongLink"] -> db path@[name,vers,_] -> case Tar.entryContent e of Tar.NormalFile buf _ -> add name vers buf db _ -> error ("Hackage.DB.parseHackage: unexpected content type for " ++ show path) _ -> db add :: String -> String -> ByteString -> Hackage -> Hackage add name version pkg = insertWith union name (singleton (pVersion version) (pPackage name pkg)) pPackage :: String -> ByteString -> GenericPackageDescription pPackage name buf = case parsePackageDescription (decodeUTF8 buf) of ParseOk _ a -> a ParseFailed err -> error ("Hackage.DB.parseHackage: cannot parse cabal file " ++ show name ++ ": " ++ show err) pVersion :: String -> Version pVersion str = fromMaybe (error $ "Hackage.DB.parseHackage: cannot parse version " ++ show str) (simpleParse str) -- | Determine the default path of the Hackage database, which typically -- resides at @"$HOME\/.cabal\/packages\/hackage.haskell.org\/00-index.tar"@. -- Running the command @"cabal update"@ will keep that file up-to-date. hackagePath :: IO FilePath hackagePath = do homedir <- getHomeDirectory return $ joinPath [homedir, ".cabal", "packages", "hackage.haskell.org", "00-index.tar"]