packages feed

hoogle-4.2.28: src/Recipe/All.hs

{-# LANGUAGE RecordWildCards, ScopedTypeVariables, DeriveDataTypeable, GeneralizedNewtypeDeriving #-}

module Recipe.All(recipes) where

import General.Base hiding (readFile')
import General.System as Sys
import Control.Concurrent
import Control.Exception as E
import qualified Data.Map as Map
import qualified Data.Set as Set
import Development.Shake
import Development.Shake.Classes
import Development.Shake.FilePath
import Recipe.Haddock

import Recipe.Command
import Recipe.Keyword
import Recipe.Hackage
import Recipe.Cabal
import Hoogle
import qualified Paths_hoogle as V
import Data.Version
import CmdLine.All


-- CmdLine is guaranteed to be a constructor of type Data
recipes :: CmdLine -> IO ()
recipes opt@Data{..} = withModeGlobalRead $ do
    hSetBuffering stdout NoBuffering
    createDirectoryIfMissing True datadir
    withDirectory datadir $ do
        when redownload $ do
            forM_ urls $ \(file,_) -> removeFile_ $ "downloads" </> file
        when rebuild $ removeFile ".shake.database"
        (count, file) <- withWarnings $ \warn ->
            shake shakeOptions{shakeVersion=showVersion V.version, shakeThreads=threads, shakeProgress=progressSimple} $ do
                want $ map (<.> "hoo") $ if null actions then ["default"] else actions
                rules opt warn
        putStrLn $ show count ++ " warnings, saved to " ++ file
        putStrLn "Data generation complete"


newtype CabalVersion = CabalVersion String deriving (Show,Typeable,Eq,Hashable,Binary,NFData)
newtype HoogleVersion = HoogleVersion String deriving (Show,Typeable,Eq,Hashable,Binary,NFData)

rules :: CmdLine -> ([String] -> IO ()) -> Rules ()
rules Data{..} warn = do
    let srcCabal name ver = "downloads/cabal" </> name </> ver </> name <.> "cabal"
    let srcHoogle name ver = "downloads/hoogle" </> name </> ver </> "doc" </> "html" </> name <.> "txt"

    (\x -> "downloads/*" ?== x && isJust (lookup (takeFileName x) urls)) ?> \out -> do
        let Just url = lookup (takeFileName out) urls
        putNormal $ "Downloading " ++ out
        -- liftIO $ copyFile ("C:/spacework/hoogle/cache" </> takeFileName out) out
        wget url out
        putNormal $ "Downloaded " ++ out

    "downloads/*.cache" *> \out -> do
        let src = dropExtension out
        need [src]
        src <- liftIO $ readFileUtf8' src
        b <- liftIO $ Sys.doesFileExist out
        liftIO $ if not b then writeFileUtf8 out src else do
            old <- readFileUtf8' out
            when (src /= old) $ writeFileUtf8 out src

    "//*.tar" *> \out -> do
        let src = out <.> "gz"
        need [src]
        ungzip src out

    "//*.index" *> \out -> do
        let src = out -<.> "tar"
        need [src]
        putNormal $ "Extracting tar file " ++ out
        tarExtract src
        putNormal $ "Finished extracting tar file " ++ out
        writeFileChanged out . unlines =<< tarList src

    index <- newCache $ \index -> do
        xs <- readFileLines index
        let asVer = map (read :: String -> Int) . words . map (\x -> if x == '.' then ' ' else x)
        return $ Map.fromListWith (\a b -> if asVer a > asVer b then a else b)
            [(name, ver) | x <- xs, let name = takeDirectory1 x, let ver = takeDirectory1 $ dropDirectory1 x, all (\x -> isDigit x || x == '.') ver]

    verCabal  <- addOracle $ \(CabalVersion  x) -> fmap (Map.lookup x) $ index "downloads/cabal.index"
    verHoogle <- addOracle $ \(HoogleVersion x) -> fmap (Map.lookup x) $ index "downloads/hoogle.index"

    alternatives $ do -- Match *.txt
        "keyword.txt" *> \out -> do
            let src = "downloads/keyword.htm.cache"
            need [src]
            contents <- liftIO $ readFileUtf8' src
            liftIO $ writeFileUtf8 out $ translateKeywords contents

        "default.txt" *> \out -> do
            writeFileLines out ["@combine keyword","@combine package","@combine platform"]

        "platform.txt" *> \out -> do
            contents <- readFile' "downloads/platform.cabal.cache"
            writeFileLines out ["@combine " ++ x | x <- platformPackages contents]

        "package.txt" *> \out -> do
            cabs <- index "downloads/cabal.index"
            xs <- liftIO $ forM (Map.toList cabs) $ \(name,ver) -> do
                src <- try $ readCabal $ srcCabal name ver
                return $ case src of
                    Left (_ :: SomeException) -> []
                    Right src ->
                        [""] ++ zipWith (++) ("-- | " : repeat "--   ") (cabalDescription src) ++
                        ["--","-- Version " ++ ver, "@url package/" ++ name, "@entry package " ++ name]
            liftIO $ writeFileUtf8 out $ unlines $ "@url http://hackage.haskell.org/" : "@package package" : concat xs

        "*.txt" *> \out -> do
            let name = takeBaseName out
                base = name == "base"
            cab <- fmap (fmap $ srcCabal name) $ verCabal (CabalVersion name)
            hoo <- if base
                   then need ["downloads/base.txt.cache"] >> return (Just "downloads/base.txt.cache")
                   else fmap (fmap $ srcHoogle name) $ verHoogle (HoogleVersion name)
            hoo <- return $ fromMaybe (error $ "Couldn't find hoogle file for " ++ name) hoo
            hoo <- liftIO $ readFileUtf8' hoo `E.catch` \(_ :: SomeException) -> readFile hoo
            deps <- liftIO $ case cab of
                Nothing -> return []
                Just cab -> do
                    res <- try $ readCabal cab
                    case res of
                        Left (err :: SomeException) -> do warn [takeBaseName cab ++ ": failed to read cabal file, " ++ cab ++ ", " ++ show err]; return []
                        Right x -> return $ cabalDepends x
            let cleanDeps = deps \\ (name:avoid)
            loc <- liftIO $ findLocal local name
            liftIO $ writeFileUtf8 out $ unlines $
                ["@depends " ++ a | a <- cleanDeps] ++ haddockHacks loc (lines hoo)

    alternatives $ do -- Match *.hoo
        phony "all.hoo" $ do
            pkgs <- index "downloads/hoogle.index"
            need $ map (<.> "hoo") $ "default" : Map.keys pkgs

        imported <- newCache $ \file -> do
            need [file]
            xs <- liftIO $ readFileUtf8' file
            return [x | x <- lines xs, takeWhile (not . isSpace) x `elem` ["type","data","newtype","class","instance","@depends"]]
        let splitDeps = first (map $ drop 9) . span ("@depends " `isPrefixOf`)

        let genImported seen [] = return []
            genImported seen (t:odo) = do
                v <- if t `Set.member` seen then return Nothing else verHoogle $ HoogleVersion t
                if isNothing v then genImported seen odo else do
                    i <- imported $ t <.> "txt"
                    fmap (i++) $ genImported (Set.insert t seen) (fst (splitDeps i) ++ odo)

        "*.hoo" *> \out -> do
            let src = out -<.> "txt"
            need [src]
            contents <- liftIO $ fmap lines $ readFileUtf8' src
            if not (null contents) && "@combine " `isPrefixOf` head contents then do
                let deps = [x <.> "hoo" | x <- contents, Just x <- [stripPrefix "@combine " x]]
                need deps
                dbs <- liftIO $ mapM loadDatabase deps
                putNormal $ "Creating " ++ out ++ " from " ++ show (length deps) ++ " databases... "
                liftIO $ performGC
                liftIO $ saveDatabase out $ mconcat dbs
             else do
                (deps, contents) <- return $ splitDeps contents
                deps <- genImported (Set.singleton $ takeBaseName out) deps
                let (err,db) = createDatabase Haskell [snd $ createDatabase Haskell [] $ unlines deps] $ unlines contents
                liftIO $ warn [takeBaseName out ++ ": " ++ show e | e <- err]
                putNormal $ "Creating " ++ out ++ "... "
                liftIO $ performGC
                liftIO $ saveDatabase out db


urls :: [(FilePath, URL)]
urls = let (*) = (,) in
    ["keyword.htm" * "http://www.haskell.org/haskellwiki/Keywords"
    ,"platform.cabal" * "http://code.galois.com/darcs/haskell-platform/haskell-platform.cabal"
    ,"base.txt" * "http://www.haskell.org/hoogle/base.txt"
    ,"cabal.tar.gz" * "http://hackage.haskell.org/packages/index.tar.gz"
    ,"hoogle.tar.gz" * "http://hackage.haskell.org/packages/hoogle.tar.gz"]


withWarnings :: (([String] -> IO ()) -> IO ()) -> IO (Int, FilePath)
withWarnings act = do
    count <- newMVar 0
    let file = ".warnings"
    writeFile file ""
    act $ \xs -> unless (null xs) $ modifyMVar_ count $ \i -> do
        appendFile file $ unlines xs
        return $! i + length xs
    i <- readMVar count
    return (i, file)