module AutoPkg.Hackage ( Hackage (..) , packageData ) where import Data.Char (isDigit) import Data.List (isSuffixOf) import Network.Curl (curlGetString, CurlCode (..)) import Text.HTML.TagSoup import System.FilePath (splitPath, joinPath) import AutoPkg.Cabal import AutoPkg.Util data Hackage = Hackage { pkgName :: String , pkgVersion :: String , cabalUrl :: String , pkgVersions :: [String] , pkgData :: PackageDescription } deriving (Show) hackageBase :: String hackageBase = "http://hackage.haskell.org/" hackagePkgs :: String hackagePkgs = hackageBase ++ "cgi-bin/hackage-scripts/package/" packageData :: String -> Maybe String -> IO Hackage packageData pkgname version = do html <- hackagePage pkgname let tags = parseTags html -- mapM_ (putStrLn . show) tags let versions = reverse $ parseVersions tags let url = parseCabalFileUrl tags (ver, cabUrl) <- case version of Nothing -> return $ (head versions, url) Just v -> if elem v versions then return (v, versionReplace (head versions) v url) else errorExit $ "Bad version '" ++ v ++ "'." cabal <- curlGrabCabalFile cabUrl pkgDesc <- cabalParse cabal return $! Hackage { pkgName = pkgname , pkgVersion = ver , cabalUrl = hackageBase ++ (drop 1 cabUrl) , pkgVersions = versions , pkgData = pkgDesc } hackagePage :: String -> IO String hackagePage name = do (code, dat) <- curlGetString (hackagePkgs ++ name) [] case code of CurlOK -> return $ dat _ -> errorExit $ "Hackage couldn't find package '" ++ name ++ "'. Check the case." curlGrabCabalFile :: String -> IO String curlGrabCabalFile url = do (code, dat) <- curlGetString (hackageBase ++ url) [] case code of CurlOK -> return $ dat _ -> errorExit $ "Retreiving URL '" ++ hackageBase ++ url ++ "' failed." ------------------------------------------------------------------------------- versionReplace :: String -> String -> String -> String versionReplace oldv newv cabal = joinPath $ map (\s -> if s == oldv ++ "/" then newv ++ "/" else s ) $ splitPath cabal ------------------------------------------------------------------------------- parseCabalFileUrl :: [Tag String] -> String parseCabalFileUrl tags = head $ filter (isSuffixOf ".cabal") $ map stripHrefs $ filter isHref $ dropWhile (/= TagText "Downloads") tags parseVersions :: [Tag String] -> [String] parseVersions tags = map stripTagText $ filter isVersionTagText $ takeWhile (/= TagText "Dependencies") $ drop 1 $ dropWhile (\t -> t /= TagText "Version" && t /= TagText "Versions") tags ------------------------------------------------------------------------------- -- Tagsoup helpers. isHref :: Tag String -> Bool isHref (TagOpen "a" [("href", _)]) = True isHref _ = False stripHrefs :: Tag String -> String stripHrefs (TagOpen "a" [("href", name)]) = name stripHrefs a = errorExit $ "stripHrefs failed on : " ++ show a isVersionTagText :: Tag String -> Bool isVersionTagText (TagText (',' : _)) = False isVersionTagText (TagText (x : _)) | isDigit x = True isVersionTagText _ = False stripTagText :: Tag String -> String stripTagText (TagText s) = s stripTagText x = errorExit $ "stripTagText failed on : " ++ show x