Merge branch 'master' into new-upload

Conflicts:
	Application.hs
	Handler/Haddock.hs
	Handler/StackageHome.hs
	Import.hs
	cabal.config
	config/routes
	stackage-server.cabal
	templates/doc-list.hamlet
This commit is contained in:
Michael Snoyman 2015-03-13 14:44:41 +02:00
commit f4a0d6d61e
34 changed files with 1209 additions and 289 deletions

View File

@ -13,6 +13,7 @@ import Control.Monad.Logger (runLoggingT, LoggingT, defaultLogStr)
import Data.BlobStore (fileStore, storeWrite, cachedS3Store) import Data.BlobStore (fileStore, storeWrite, cachedS3Store)
import Data.Hackage import Data.Hackage
import Data.Hackage.Views import Data.Hackage.Views
import Data.Unpacking (newDocUnpacker, createHoogleDatabases)
import Data.WebsiteContent import Data.WebsiteContent
import Data.Slug (SnapSlug (..), safeMakeSlug, HasGenIO) import Data.Slug (SnapSlug (..), safeMakeSlug, HasGenIO)
import Data.Time (diffUTCTime) import Data.Time (diffUTCTime)
@ -69,6 +70,9 @@ import Handler.Tag
import Handler.BannedTags import Handler.BannedTags
import Handler.RefreshDeprecated import Handler.RefreshDeprecated
import Handler.UploadV2 import Handler.UploadV2
import Handler.Hoogle
import Handler.BuildVersion
import Handler.PackageCounts
-- This line actually creates our YesodDispatch instance. It is the second half -- This line actually creates our YesodDispatch instance. It is the second half
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the -- of the call to mkYesodData which occurs in Foundation.hs. Please see the
@ -148,17 +152,12 @@ makeFoundation useEcho conf = do
(getter, _) <- clockDateCacher (getter, _) <- clockDateCacher
gen <- MWC.createSystemRandom gen <- MWC.createSystemRandom
progressMap' <- newIORef mempty
nextProgressKey' <- newIORef 0
blobStore' <- loadBlobStore manager conf blobStore' <- loadBlobStore manager conf
let haddockRootDir' = "/tmp/stackage-server-haddocks2" let haddockRootDir' = "/tmp/stackage-server-haddocks2"
(statusRef, unpacker) <- createHaddockUnpacker haddockRootDir' blobStore'
(flip (Database.Persist.runPool dbconf) p)
widgetCache' <- newIORef mempty widgetCache' <- newIORef mempty
#if MIN_VERSION_yesod_gitrepo(0,1,1)
websiteContent' <- if development websiteContent' <- if development
then do then do
void $ rawSystem "git" void $ rawSystem "git"
@ -170,23 +169,12 @@ makeFoundation useEcho conf = do
"https://github.com/fpco/stackage-content.git" "https://github.com/fpco/stackage-content.git"
"master" "master"
loadWebsiteContent loadWebsiteContent
#else
websiteContent' <- if development env <- getEnvironment
then do
void $ rawSystem "git" let runDB' :: (MonadIO m, MonadBaseControl IO m) => SqlPersistT m a -> m a
[ "clone" runDB' = flip (Database.Persist.runPool dbconf) p
, "https://github.com/fpco/stackage-content.git" docUnpacker <- newDocUnpacker haddockRootDir' blobStore' runDB'
]
tmp <- gitRepo "stackage-content" "master" loadWebsiteContent
return tmp
{ grRefresh = return ()
, grContent = loadWebsiteContent "stackage-content"
}
else gitRepo
"https://github.com/fpco/stackage-content.git"
"master"
loadWebsiteContent
#endif
snapshotInfoCache' <- newIORef mempty snapshotInfoCache' <- newIORef mempty
@ -200,17 +188,14 @@ makeFoundation useEcho conf = do
, appLogger = logger , appLogger = logger
, genIO = gen , genIO = gen
, blobStore = blobStore' , blobStore = blobStore'
, progressMap = progressMap'
, nextProgressKey = nextProgressKey'
, haddockRootDir = haddockRootDir' , haddockRootDir = haddockRootDir'
, haddockUnpacker = unpacker , appDocUnpacker = docUnpacker
, widgetCache = widgetCache' , widgetCache = widgetCache'
, compressorStatus = statusRef
, websiteContent = websiteContent' , websiteContent = websiteContent'
, snapshotInfoCache = snapshotInfoCache' , snapshotInfoCache = snapshotInfoCache'
} }
env <- getEnvironment let urlRender' = yesodRender foundation (appRoot conf)
-- Perform database migration using our application's logging settings. -- Perform database migration using our application's logging settings.
when (lookup "STACKAGE_SKIP_MIGRATION" env /= Just "1") $ when (lookup "STACKAGE_SKIP_MIGRATION" env /= Just "1") $
@ -224,6 +209,7 @@ makeFoundation useEcho conf = do
let updateDB = lookup "STACKAGE_CABAL_LOADER" env /= Just "0" let updateDB = lookup "STACKAGE_CABAL_LOADER" env /= Just "0"
hoogleGen = lookup "STACKAGE_HOOGLE_GEN" env /= Just "0"
forceUpdate = lookup "STACKAGE_FORCE_UPDATE" env == Just "1" forceUpdate = lookup "STACKAGE_FORCE_UPDATE" env == Just "1"
loadCabalFiles' = appLoadCabalFiles updateDB forceUpdate foundation dbconf p loadCabalFiles' = appLoadCabalFiles updateDB forceUpdate foundation dbconf p
@ -236,6 +222,8 @@ makeFoundation useEcho conf = do
loadCabalFiles' loadCabalFiles'
when hoogleGen $ liftIO $ createHoogleDatabases blobStore' runDB' putStrLn urlRender'
liftIO $ threadDelay $ 30 * 60 * 1000000 liftIO $ threadDelay $ 30 * 60 * 1000000
return foundation return foundation
where ifRunCabalLoader m = where ifRunCabalLoader m =
@ -276,6 +264,26 @@ cabalLoaderMain = do
} }
dbconf dbconf
pool pool
let foundation = App
{ settings = conf
, getStatic = error "getStatic"
, connPool = pool
, httpManager = manager
, persistConfig = dbconf
, appLogger = error "appLogger"
, genIO = error "genIO"
, blobStore = bs
, haddockRootDir = error "haddockRootDir"
, appDocUnpacker = error "appDocUnpacker"
, widgetCache = error "widgetCache"
, websiteContent = error "websiteContent"
}
createHoogleDatabases
bs
(flip (Database.Persist.runPool dbconf) pool)
putStrLn
(yesodRender foundation (appRoot conf))
where where
logFunc loc src level str logFunc loc src level str
| level > LevelDebug = S.hPutStr stdout $ fromLogStr $ defaultLogStr loc src level str | level > LevelDebug = S.hPutStr stdout $ fromLogStr $ defaultLogStr loc src level str

View File

@ -23,8 +23,8 @@ instance FromJSON HackageDeprecationInfo where
} }
data DeprecationRecord = DeprecationRecord { data DeprecationRecord = DeprecationRecord {
deprecatedPackage :: PackageName, _deprecatedPackage :: PackageName,
deprecatedInFavourOf :: [PackageName] _deprecatedInFavourOf :: [PackageName]
} }
instance FromJSON DeprecationRecord where instance FromJSON DeprecationRecord where

View File

@ -18,7 +18,7 @@ import GHC.Prim (RealWorld)
import Text.Blaze (ToMarkup) import Text.Blaze (ToMarkup)
newtype Slug = Slug { unSlug :: Text } newtype Slug = Slug { unSlug :: Text }
deriving (Show, Read, Eq, Typeable, PersistField, ToMarkup) deriving (Show, Read, Eq, Typeable, PersistField, ToMarkup, Ord, Hashable)
instance PersistFieldSql Slug where instance PersistFieldSql Slug where
sqlType = sqlType . liftM unSlug sqlType = sqlType . liftM unSlug
@ -101,6 +101,6 @@ slugField =
-- | Unique identifier for a snapshot. -- | Unique identifier for a snapshot.
newtype SnapSlug = SnapSlug { unSnapSlug :: Slug } newtype SnapSlug = SnapSlug { unSnapSlug :: Slug }
deriving (Show, Read, Eq, Typeable, PersistField, ToMarkup, PathPiece) deriving (Show, Read, Eq, Typeable, PersistField, ToMarkup, PathPiece, Ord, Hashable)
instance PersistFieldSql SnapSlug where instance PersistFieldSql SnapSlug where
sqlType = sqlType . liftM unSnapSlug sqlType = sqlType . liftM unSnapSlug

494
Data/Unpacking.hs Normal file
View File

@ -0,0 +1,494 @@
-- | Code for unpacking documentation bundles, building the Hoogle databases,
-- and compressing/deduping contents.
module Data.Unpacking
( newDocUnpacker
, getHoogleDB
, makeHoogle
, createHoogleDatabases
) where
import Import hiding (runDB)
import Data.BlobStore
import Handler.Haddock
import Filesystem (createTree, isFile, removeTree, isDirectory, listDirectory, removeDirectory, removeFile, rename)
import System.Posix.Files (createLink)
import Crypto.Hash.Conduit (sinkHash)
import Control.Concurrent (forkIO)
import Control.Monad.Trans.Resource (allocate, release)
import Data.Char (isAlpha)
import qualified Hoogle
import qualified Data.Text as T
import qualified Data.Yaml as Y
import System.IO (IOMode (ReadMode), withBinaryFile, openBinaryFile)
import System.IO.Temp (withSystemTempFile, withTempFile, withSystemTempDirectory)
import System.Exit (ExitCode (ExitSuccess))
import System.Process (createProcess, proc, cwd, waitForProcess)
import qualified Filesystem.Path.CurrentOS as F
import Data.Conduit.Zlib (gzip, ungzip)
import qualified Data.ByteString.Base16 as B16
import Data.Byteable (toBytes)
import Crypto.Hash (Digest, SHA1)
newDocUnpacker
:: FilePath -- ^ haddock root
-> BlobStore StoreKey
-> (forall a m. (MonadIO m, MonadBaseControl IO m) => SqlPersistT m a -> m a)
-> IO DocUnpacker
newDocUnpacker root store runDB = do
createDirs dirs
statusMapVar <- newTVarIO $ asMap mempty
messageVar <- newTVarIO "Inactive"
workChan <- atomically newTChan
let requestDocs forceUnpack ent = atomically $ do
var <- newTVar USBusy
modifyTVar statusMapVar
$ insertMap (stackageSlug $ entityVal ent) var
writeTChan workChan (forceUnpack, ent, var)
forkForever $ unpackWorker dirs runDB store messageVar workChan
return DocUnpacker
{ duRequestDocs = \ent -> do
m <- readTVarIO statusMapVar
case lookup (stackageSlug $ entityVal ent) m of
Nothing -> do
b <- isUnpacked dirs (entityVal ent)
if b
then return USReady
else do
requestDocs False ent
return USBusy
Just us -> readTVarIO us
, duGetStatus = readTVarIO messageVar
, duForceReload = \ent -> do
atomically $ modifyTVar statusMapVar
$ deleteMap (stackageSlug $ entityVal ent)
requestDocs True ent
}
where
dirs = mkDirs root
createDirs :: Dirs -> IO ()
createDirs dirs = do
createTree $ dirCacheRoot dirs
createTree $ dirRawRoot dirs
createTree $ dirGzRoot dirs
createTree $ dirHoogleRoot dirs
-- | Check for the presence of file system artifacts indicating that the docs
-- have been unpacked.
isUnpacked :: Dirs -> Stackage -> IO Bool
isUnpacked dirs stackage = isFile $ completeUnpackFile dirs stackage
defaultHooDest :: Dirs -> Stackage -> FilePath
defaultHooDest dirs stackage = dirHoogleFp dirs (stackageIdent stackage)
["default-" ++ VERSION_hoogle ++ ".hoo"]
forkForever :: IO () -> IO ()
forkForever inner = mask $ \restore ->
void $ forkIO $ forever $ handleAny print $ restore $ forever inner
unpackWorker
:: Dirs
-> (forall a m. (MonadIO m, MonadBaseControl IO m) => SqlPersistT m a -> m a)
-> BlobStore StoreKey
-> TVar Text
-> TChan (Bool, Entity Stackage, TVar UnpackStatus)
-> IO ()
unpackWorker dirs runDB store messageVar workChan = do
let say' = atomically . writeTVar messageVar
say' "Running the compressor"
let shouldStop = fmap not $ atomically $ isEmptyTChan workChan
handleAny print $ runCompressor shouldStop say' dirs
say' "Waiting for new work item"
(forceUnpack, ent, resVar) <- atomically $ readTChan workChan
shouldUnpack <-
if forceUnpack
then return True
else not <$> isUnpacked dirs (entityVal ent)
let say msg = atomically $ writeTVar messageVar $ concat
[ toPathPiece (stackageSlug $ entityVal ent)
, ": "
, msg
]
when shouldUnpack $ do
say "Beginning of processing"
-- As soon as the raw unpack is complete, start serving docs
let onRawComplete = atomically $ writeTVar resVar USReady
eres <- tryAny $ unpacker dirs runDB store say onRawComplete ent
atomically $ writeTVar resVar $ case eres of
Left e -> USFailed $ tshow e
Right () -> USReady
removeTreeIfExists :: FilePath -> IO ()
removeTreeIfExists fp = whenM (isDirectory fp) (removeTree fp)
unpackRawDocsTo
:: BlobStore StoreKey
-> PackageSetIdent
-> (Text -> IO ())
-> FilePath
-> IO ()
unpackRawDocsTo store ident say destdir =
withSystemTempFile "haddock-bundle.tar.xz" $ \tempfp temph -> do
say "Downloading raw doc tarball"
withAcquire (storeRead' store (HaddockBundle ident)) $ \msrc ->
case msrc of
Nothing -> error "No haddocks exist for that snapshot"
Just src -> src $$ sinkHandle temph
hClose temph
createTree destdir
say "Unpacking tarball"
(Nothing, Nothing, Nothing, ph) <- createProcess
(proc "tar" ["xf", tempfp])
{ cwd = Just $ fpToString destdir
}
ec <- waitForProcess ph
if ec == ExitSuccess then return () else throwM ec
unpacker
:: Dirs
-> (forall a m. (MonadIO m, MonadBaseControl IO m) => SqlPersistT m a -> m a)
-> BlobStore StoreKey
-> (Text -> IO ())
-> IO () -- ^ onRawComplete
-> Entity Stackage
-> IO ()
unpacker dirs runDB store say onRawComplete (Entity sid stackage@Stackage {..}) = do
say "Removing old directories, if they exist"
removeTreeIfExists $ dirRawIdent dirs stackageIdent
removeTreeIfExists $ dirGzIdent dirs stackageIdent
removeTreeIfExists $ dirHoogleIdent dirs stackageIdent
let destdir = dirRawIdent dirs stackageIdent
unpackRawDocsTo store stackageIdent say destdir
onRawComplete
createTree $ dirHoogleIdent dirs stackageIdent
-- Determine which packages have documentation and update the
-- database appropriately
say "Updating database for available documentation"
runResourceT $ runDB $ do
updateWhere
[PackageStackage ==. sid]
[PackageHasHaddocks =. False]
sourceDirectory destdir $$ mapM_C (\fp -> do
let mnv = nameAndVersionFromPath fp
forM_ mnv $ \(name, version) -> updateWhere
[ PackageStackage ==. sid
, PackageName' ==. PackageName name
, PackageVersion ==. Version version
]
[PackageHasHaddocks =. True]
)
say "Unpack complete"
let completeFP = completeUnpackFile dirs stackage
liftIO $ do
createTree $ F.parent completeFP
writeFile completeFP ("Complete" :: ByteString)
completeUnpackFile :: Dirs -> Stackage -> FilePath
completeUnpackFile dirs stackage =
dirGzIdent dirs (stackageIdent stackage) </> "unpack-complete"
-- | Get the path to the Hoogle database, downloading from persistent storage
-- if necessary. This function will /not/ generate a new database, and
-- therefore is safe to run on a live web server.
getHoogleDB :: Dirs
-> Stackage
-> Handler (Maybe FilePath)
getHoogleDB dirs stackage = do
exists <- liftIO $ isFile fp
if exists
then return $ Just fp
else do
msrc <- storeRead key
case msrc of
Nothing -> return Nothing
Just src -> do
liftIO $ createTree $ F.parent fp
let tmpfp = fp <.> "tmp" -- FIXME add something random
src $$ ungzip =$ sinkFile tmpfp
liftIO $ rename tmpfp fp
return $ Just fp
where
fp = defaultHooDest dirs stackage
key = HoogleDB (stackageIdent stackage) $ HoogleVersion VERSION_hoogle
-- | Make sure that the last 5 LTS and last 5 Nightly releases all have Hoogle
-- databases available.
createHoogleDatabases
:: BlobStore StoreKey
-> (forall a m. (MonadIO m, MonadBaseControl IO m) => SqlPersistT m a -> m a)
-> (Text -> IO ())
-> (Route App -> [(Text, Text)] -> Text)
-> IO ()
createHoogleDatabases store runDB say urlRender = do
stackages <- runDB $ do
sids <- (++)
<$> fmap (map $ ltsStackage . entityVal)
(selectList [] [Desc LtsMajor, Desc LtsMinor, LimitTo 5])
<*> fmap (map $ nightlyStackage . entityVal)
(selectList [] [Desc NightlyDay, LimitTo 5])
catMaybes <$> mapM get sids
forM_ stackages $ \stackage -> do
let say' x = say $ concat
[ toPathPiece $ stackageSlug stackage
, ": "
, x
]
handleAny (say' . tshow) $ makeHoogle store say' urlRender stackage
-- | Either download the Hoogle database from persistent storage, or create it.
makeHoogle
:: BlobStore StoreKey
-> (Text -> IO ())
-> (Route App -> [(Text, Text)] -> Text)
-> Stackage
-> IO ()
makeHoogle store say urlRender stackage = do
say "Making hoogle database"
exists <- storeExists' store hoogleKey
if exists
then say "Hoogle database already exists, skipping"
else do
say "Generating Hoogle database"
generate
where
ident = stackageIdent stackage
hoogleKey = HoogleDB ident $ HoogleVersion VERSION_hoogle
generate = withSystemTempDirectory "hoogle-database-gen" $ \hoogletemp' -> do
let hoogletemp = fpFromString hoogletemp'
rawdocs = hoogletemp </> "rawdocs"
unpackRawDocsTo store ident say rawdocs
say "Copying Hoogle text files to temp directory"
runResourceT $ copyHoogleTextFiles say rawdocs hoogletemp
say "Creating Hoogle database"
withSystemTempFile "default.hoo" $ \dstFP' dstH -> do
let dstFP = fpFromString dstFP'
hClose dstH
createHoogleDb say dstFP stackage hoogletemp urlRender
say "Uploading database to persistent storage"
withAcquire (storeWrite' store hoogleKey) $ \sink ->
runResourceT $ sourceFile dstFP $$ gzip =$ sink
runCompressor :: IO Bool -- ^ should stop early?
-> (Text -> IO ()) -> Dirs -> IO ()
runCompressor shouldStop say dirs =
handle (\EarlyStop -> return ()) $ runResourceT $ goDir $ dirRawRoot dirs
where
goDir dir = do
liftIO $ whenM shouldStop $ do
say "Stopping compressor early"
throwIO EarlyStop
liftIO $ say $ "Compressing directory: " ++ fpToText dir
sourceDirectory dir $$ mapM_C goFP
liftIO $ void $ tryIO $ removeDirectory dir
goFP fp = do
e <- liftIO $ isFile fp
if e
then liftIO $ do
liftIO $ say $ "Compressing file: " ++ fpToText fp
handle (print . asSomeException)
$ gzipHash dirs suffix
else goDir fp
where
Just suffix = F.stripPrefix (dirRawRoot dirs </> "") fp
data EarlyStop = EarlyStop
deriving (Show, Typeable)
instance Exception EarlyStop
-- Procedure is to:
--
-- * Gzip the src file to a temp file, and get a hash of the gzipped contents
-- * If that hash doesn't exist in the cache, move the new file to the cache
-- * Create a hard link from dst to the file in the cache
-- * Delete src
gzipHash :: Dirs
-> FilePath -- ^ suffix
-> IO ()
gzipHash dirs suffix = do
withTempFile (fpToString $ dirCacheRoot dirs) "haddock-file.gz" $ \tempfp temph -> do
digest <- withBinaryFile (fpToString src) ReadMode $ \inh ->
sourceHandle inh
$= gzip
$$ (getZipSink $
ZipSink (sinkHandle temph) *>
ZipSink sinkHash)
hClose temph
let fpcache = dirCacheFp dirs digest
unlessM (isFile fpcache) $ do
createTree $ F.parent fpcache
rename (fpFromString tempfp) fpcache
createTree $ F.parent dst
createLink (fpToString fpcache) (fpToString dst)
removeFile src
where
src = dirRawRoot dirs </> suffix
dst = dirGzRoot dirs </> suffix
dirCacheFp :: Dirs -> Digest SHA1 -> FilePath
dirCacheFp dirs digest =
dirCacheRoot dirs </> fpFromText x </> fpFromText y <.> "gz"
where
name = decodeUtf8 $ B16.encode $ toBytes digest
(x, y) = splitAt 2 name
copyHoogleTextFiles :: (Text -> IO ()) -- ^ log
-> FilePath -- ^ raw unpacked Haddock files
-> FilePath -- ^ temporary work directory
-> ResourceT IO ()
copyHoogleTextFiles say raw tmp = do
let tmptext = tmp </> "text"
liftIO $ createTree tmptext
sourceDirectory raw $$ mapM_C (\fp ->
forM_ (nameAndVersionFromPath fp) $ \(name, version) -> do
let src = fp </> fpFromText name <.> "txt"
dst = tmptext </> fpFromText (name ++ "-" ++ version)
exists <- liftIO $ isFile src
if exists
then sourceFile src $$ (sinkFile dst :: Sink ByteString (ResourceT IO) ())
else liftIO $ appendHoogleErrors say $ HoogleErrors
{ packageName = name
, packageVersion = version
, errors = ["No textual Hoogle DB (use \"cabal haddock --hoogle\")"]
}
)
createHoogleDb :: (Text -> IO ())
-> FilePath -- ^ default.hoo output location
-> Stackage
-> FilePath -- ^ temp directory
-> (Route App -> [(Text, Text)] -> Text)
-> IO ()
createHoogleDb say dstDefaultHoo stackage tmpdir urlRender = do
let tmpbin = tmpdir </> "binary"
createTree tmpbin
eres <- tryAny $ runResourceT $ do
-- Create hoogle binary databases for each package.
sourceDirectory (tmpdir </> "text") $$ mapM_C
( \fp -> do
(releaseKey, srcH) <- allocate (openBinaryFile (fpToString fp) ReadMode) hClose
forM_ (nameAndVersionFromPath fp) $ \(name, version) -> liftIO $ do
say $ concat
[ "Creating Hoogle database for: "
, name
, "-"
, version
]
src <- unpack . decodeUtf8 . asLByteString <$> hGetContents srcH
let -- Preprocess the haddock-generated manifest file.
src' = unlines $ haddockHacks (Just (unpack docsUrl)) $ lines src
docsUrl = urlRender (HaddockR (stackageSlug stackage) urlPieces) []
urlPieces = [name <> "-" <> version, "index.html"]
-- Compute the filepath of the resulting hoogle
-- database.
out = fpToString $ tmpbin </> fpFromText base
base = name <> "-" <> version <> ".hoo"
errs <- Hoogle.createDatabase "" Hoogle.Haskell [] src' out
when (not $ null errs) $ do
-- TODO: remove this printing once errors are yielded
-- to the user.
putStrLn $ concat
[ base
, " Hoogle errors: "
, tshow errs
]
appendHoogleErrors say $ HoogleErrors
{ packageName = name
, packageVersion = version
, errors = map show errs
}
release releaseKey
)
-- Merge the individual binary databases into one big database.
liftIO $ do
say "Merging all Hoogle databases"
dbs <- listDirectory tmpbin
Hoogle.mergeDatabase
(map fpToString dbs)
(fpToString dstDefaultHoo)
case eres of
Right () -> return ()
Left err -> liftIO $ appendHoogleErrors say $ HoogleErrors
{ packageName = "Exception thrown while building hoogle DB"
, packageVersion = ""
, errors = [show err]
}
data HoogleErrors = HoogleErrors
{ packageName :: Text
, packageVersion :: Text
, errors :: [String]
} deriving (Generic)
instance ToJSON HoogleErrors where
instance FromJSON HoogleErrors where
-- Appends hoogle errors to a log file. By encoding within a single
-- list, the resulting file can be decoded as [HoogleErrors].
appendHoogleErrors :: (Text -> IO ()) -> HoogleErrors -> IO ()
appendHoogleErrors say errs = say $ decodeUtf8 $ Y.encode [errs]
nameAndVersionFromPath :: FilePath -> Maybe (Text, Text)
nameAndVersionFromPath fp =
(\name -> (name, version)) <$> stripSuffix "-" name'
where
(name', version) = T.breakOnEnd "-" $ fpToText $ filename fp
---------------------------------------------------------------------
-- HADDOCK HACKS
-- (Copied from hoogle-4.2.36/src/Recipe/Haddock.hs)
-- Modifications:
-- 1) Some name qualification
-- 2) Explicit type sig due to polymorphic elem
-- 3) Fixed an unused binding warning
-- Eliminate @version
-- Change :*: to (:*:), Haddock bug
-- Change !!Int to !Int, Haddock bug
-- Change instance [overlap ok] to instance, Haddock bug
-- Change instance [incoherent] to instance, Haddock bug
-- Change instance [safe] to instance, Haddock bug
-- Change !Int to Int, HSE bug
-- Drop {-# UNPACK #-}, Haddock bug
-- Drop everything after where, Haddock bug
haddockHacks :: Maybe Hoogle.URL -> [String] -> [String]
haddockHacks loc src = maybe id haddockPackageUrl loc (translate src)
where
translate :: [String] -> [String]
translate = map (unwords . g . map f . words) . filter (not . isPrefixOf "@version ")
f "::" = "::"
f (':':xs) = "(:" ++ xs ++ ")"
f ('!':'!':x:xs) | isAlpha x = xs
f ('!':x:xs) | isAlpha x || x `elem` ("[(" :: String) = x:xs
f x | x `elem` ["[overlap","ok]","[incoherent]","[safe]"] = ""
f x | x `elem` ["{-#","UNPACK","#-}"] = ""
f x = x
g ("where":_) = []
g (x:xs) = x : g xs
g [] = []
haddockPackageUrl :: Hoogle.URL -> [String] -> [String]
haddockPackageUrl x = concatMap f
where f y | "@package " `isPrefixOf` y = ["@url " ++ x, y]
| otherwise = [y]

View File

@ -5,6 +5,7 @@ import Data.BlobStore
import Data.Slug (safeMakeSlug, HasGenIO (getGenIO), randomSlug, Slug, SnapSlug) import Data.Slug (safeMakeSlug, HasGenIO (getGenIO), randomSlug, Slug, SnapSlug)
import Data.WebsiteContent import Data.WebsiteContent
import qualified Database.Persist import qualified Database.Persist
import Database.Persist.Sql (PersistentSqlException (Couldn'tGetSQLConnection))
import Model import Model
import qualified Settings import qualified Settings
import Settings (widgetFile, Extra (..), GoogleAuth (..)) import Settings (widgetFile, Extra (..), GoogleAuth (..))
@ -36,18 +37,15 @@ data App = App
, httpManager :: Manager , httpManager :: Manager
, persistConfig :: Settings.PersistConf , persistConfig :: Settings.PersistConf
, appLogger :: Logger , appLogger :: Logger
, genIO :: !MWC.GenIO , genIO :: MWC.GenIO
, blobStore :: !(BlobStore StoreKey) , blobStore :: BlobStore StoreKey
, progressMap :: !(IORef (IntMap Progress)) , haddockRootDir :: FilePath
, nextProgressKey :: !(IORef Int) , appDocUnpacker :: DocUnpacker
, haddockRootDir :: !FilePath
, haddockUnpacker :: !(ForceUnpack -> PackageSetIdent -> IO ())
-- ^ We have a dedicated thread so that (1) we don't try to unpack too many -- ^ We have a dedicated thread so that (1) we don't try to unpack too many
-- things at once, (2) we never unpack the same thing twice at the same -- things at once, (2) we never unpack the same thing twice at the same
-- time, and (3) so that even if the client connection dies, we finish the -- time, and (3) so that even if the client connection dies, we finish the
-- unpack job. -- unpack job.
, widgetCache :: !(IORef (HashMap Text (UTCTime, GWData (Route App)))) , widgetCache :: IORef (HashMap Text (UTCTime, GWData (Route App)))
, compressorStatus :: !(IORef Text)
, websiteContent :: GitRepo WebsiteContent , websiteContent :: GitRepo WebsiteContent
, snapshotInfoCache :: !(IORef (HashMap PackageSetIdent SnapshotInfo)) , snapshotInfoCache :: !(IORef (HashMap PackageSetIdent SnapshotInfo))
} }
@ -58,7 +56,11 @@ data SnapshotInfo = SnapshotInfo
, siDocMap :: !DocMap , siDocMap :: !DocMap
} }
type ForceUnpack = Bool data DocUnpacker = DocUnpacker
{ duRequestDocs :: Entity Stackage -> IO UnpackStatus
, duGetStatus :: IO Text
, duForceReload :: Entity Stackage -> IO ()
}
data Progress = ProgressWorking !Text data Progress = ProgressWorking !Text
| ProgressDone !Text !(Route App) | ProgressDone !Text !(Route App)
@ -101,7 +103,9 @@ instance Yesod App where
defaultLayout widget = do defaultLayout widget = do
mmsg <- getMessage mmsg <- getMessage
muser <- maybeAuth muser <- catch maybeAuth $ \e -> case e of
Couldn'tGetSQLConnection -> return Nothing
_ -> throwM e
-- We break up the default layout into two components: -- We break up the default layout into two components:
-- default-layout is the contents of the body tag, and -- default-layout is the contents of the body tag, and
@ -136,6 +140,7 @@ instance Yesod App where
-- The page to be redirected to when authentication is required. -- The page to be redirected to when authentication is required.
authRoute _ = Just $ AuthR LoginR authRoute _ = Just $ AuthR LoginR
{- Temporarily disable to allow for horizontal scaling
-- This function creates static content files in the static folder -- This function creates static content files in the static folder
-- and names them based on a hash of their content. This allows -- and names them based on a hash of their content. This allows
-- expiration dates to be set far in the future without worry of -- expiration dates to be set far in the future without worry of
@ -147,6 +152,7 @@ instance Yesod App where
genFileName lbs genFileName lbs
| development = "autogen-" ++ base64md5 lbs | development = "autogen-" ++ base64md5 lbs
| otherwise = base64md5 lbs | otherwise = base64md5 lbs
-}
-- Place Javascript at bottom of the body tag so the rest of the page loads first -- Place Javascript at bottom of the body tag so the rest of the page loads first
jsLoader _ = BottomOfBody jsLoader _ = BottomOfBody

View File

@ -9,6 +9,7 @@ import Data.Slug (Slug)
import Handler.StackageHome (getStackageHomeR, getStackageMetadataR, getStackageCabalConfigR, getSnapshotPackagesR, getDocsR) import Handler.StackageHome (getStackageHomeR, getStackageMetadataR, getStackageCabalConfigR, getSnapshotPackagesR, getDocsR)
import Handler.StackageIndex (getStackageIndexR, getStackageBundleR) import Handler.StackageIndex (getStackageIndexR, getStackageBundleR)
import Handler.StackageSdist (getStackageSdistR) import Handler.StackageSdist (getStackageSdistR)
import Handler.Hoogle (getHoogleR)
handleAliasR :: Slug -> Slug -> [Text] -> Handler () handleAliasR :: Slug -> Slug -> [Text] -> Handler ()
handleAliasR user name pieces = do handleAliasR user name pieces = do
@ -77,4 +78,5 @@ goSid sid pieces = do
StackageSdistR pnv -> getStackageSdistR slug pnv >>= sendResponse StackageSdistR pnv -> getStackageSdistR slug pnv >>= sendResponse
SnapshotPackagesR -> getSnapshotPackagesR slug >>= sendResponse SnapshotPackagesR -> getSnapshotPackagesR slug >>= sendResponse
DocsR -> getDocsR slug >>= sendResponse DocsR -> getDocsR slug >>= sendResponse
HoogleR -> getHoogleR slug >>= sendResponse
_ -> notFound _ -> notFound

29
Handler/BuildVersion.hs Normal file
View File

@ -0,0 +1,29 @@
module Handler.BuildVersion where
import Import hiding (lift)
import Language.Haskell.TH.Syntax
import System.Process (rawSystem)
import System.Exit
getBuildVersionR :: Handler Text
getBuildVersionR = return $ pack $(do
let headFile = ".git/HEAD"
qAddDependentFile headFile
ehead <- qRunIO $ tryIO $ readFile $ fpFromString headFile
case decodeUtf8 <$> ehead of
Left e -> lift $ ".git/HEAD not read: " ++ show e
Right raw ->
case takeWhile (/= '\n') <$> stripPrefix "ref: " raw of
Nothing -> lift $ ".git/HEAD not in expected format: " ++ show raw
Just fp' -> do
let fp = ".git" </> fpFromText fp'
qAddDependentFile $ fpToString fp
bs <- qRunIO $ readFile fp
isDirty <- qRunIO
$ (/= ExitSuccess)
<$> rawSystem "git" ["diff-files", "--quiet"]
lift $ unpack $ unlines
[ "Most recent commit: " ++ asText (decodeUtf8 bs)
, "Working tree is " ++ (if isDirty then "dirty" else "clean")
]
)

View File

@ -4,7 +4,7 @@ import Import
getCompressorStatusR :: Handler Html getCompressorStatusR :: Handler Html
getCompressorStatusR = do getCompressorStatusR = do
status <- getYesod >>= readIORef . compressorStatus status <- getYesod >>= liftIO . duGetStatus . appDocUnpacker
defaultLayout $ do defaultLayout $ do
setTitle "Compressor thread status" setTitle "Compressor thread status"
[whamlet| [whamlet|

View File

@ -1,26 +1,35 @@
module Handler.Haddock where module Handler.Haddock
( getUploadHaddockR
, putUploadHaddockR
, getHaddockR
, getUploadDocMapR
, putUploadDocMapR
-- Exported for use in Handler.Hoogle
, Dirs (..), getDirs, dirHoogleFp, mkDirs
, dirRawIdent
, dirGzIdent
, dirHoogleIdent
, createCompressor
) where
import Import import Control.Concurrent (forkIO)
import Data.BlobStore import Crypto.Hash (Digest, SHA1)
import Filesystem (removeTree, isDirectory, createTree, isFile, rename, removeFile, removeDirectory) import Crypto.Hash.Conduit (sinkHash)
import Control.Concurrent (forkIO) import Data.Aeson (withObject)
import System.IO.Temp (withSystemTempFile, withTempFile) import Data.BlobStore
import System.Process (createProcess, proc, cwd, waitForProcess)
import System.Exit (ExitCode (ExitSuccess))
import Network.Mime (defaultMimeLookup)
import Crypto.Hash.Conduit (sinkHash)
import System.IO (IOMode (ReadMode), withBinaryFile)
import Data.Conduit.Zlib (gzip)
import System.Posix.Files (createLink)
import qualified Data.ByteString.Base16 as B16 import qualified Data.ByteString.Base16 as B16
import Data.Byteable (toBytes) import Data.Byteable (toBytes)
import Crypto.Hash (Digest, SHA1) import Data.Conduit.Zlib (gzip)
import qualified Filesystem.Path.CurrentOS as F import Data.Slug (SnapSlug, unSlug)
import Data.Slug (SnapSlug)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Slug (unSlug)
import qualified Data.Yaml as Y import qualified Data.Yaml as Y
import Data.Aeson (withObject) import Filesystem (isDirectory, createTree, isFile, rename, removeFile, removeDirectory)
import qualified Filesystem.Path.CurrentOS as F
import Import
import Network.Mime (defaultMimeLookup)
import System.IO (IOMode (ReadMode), withBinaryFile)
import System.IO.Temp (withTempFile)
import System.Posix.Files (createLink)
form :: Form FileInfo form :: Form FileInfo
form = renderDivs $ areq fileField "tarball containing docs" form = renderDivs $ areq fileField "tarball containing docs"
@ -30,7 +39,7 @@ form = renderDivs $ areq fileField "tarball containing docs"
getUploadHaddockR, putUploadHaddockR :: Text -> Handler Html getUploadHaddockR, putUploadHaddockR :: Text -> Handler Html
getUploadHaddockR slug0 = do getUploadHaddockR slug0 = do
uid <- requireAuthIdOrToken uid <- requireAuthIdOrToken
Entity sid Stackage {..} <- runDB $ do stackageEnt@(Entity sid Stackage {..}) <- runDB $ do
-- Provide fallback for old URLs -- Provide fallback for old URLs
ment <- getBy $ UniqueStackage $ PackageSetIdent slug0 ment <- getBy $ UniqueStackage $ PackageSetIdent slug0
case ment of case ment of
@ -47,7 +56,7 @@ getUploadHaddockR slug0 = do
fileSource fileInfo $$ storeWrite (HaddockBundle ident) fileSource fileInfo $$ storeWrite (HaddockBundle ident)
runDB $ update sid [StackageHasHaddocks =. True] runDB $ update sid [StackageHasHaddocks =. True]
master <- getYesod master <- getYesod
void $ liftIO $ forkIO $ haddockUnpacker master True ident liftIO $ duForceReload (appDocUnpacker master) stackageEnt
setMessage "Haddocks uploaded" setMessage "Haddocks uploaded"
redirect $ SnapshotR slug StackageHomeR redirect $ SnapshotR slug StackageHomeR
_ -> defaultLayout $ do _ -> defaultLayout $ do
@ -58,7 +67,7 @@ putUploadHaddockR = getUploadHaddockR
getHaddockR :: SnapSlug -> [Text] -> Handler () getHaddockR :: SnapSlug -> [Text] -> Handler ()
getHaddockR slug rest = do getHaddockR slug rest = do
ident <- runDB $ do stackageEnt <- runDB $ do
ment <- getBy $ UniqueSnapshot slug ment <- getBy $ UniqueSnapshot slug
case ment of case ment of
Just ent -> do Just ent -> do
@ -66,7 +75,7 @@ getHaddockR slug rest = do
[pkgver] -> tryContentsRedirect ent pkgver [pkgver] -> tryContentsRedirect ent pkgver
[pkgver, "index.html"] -> tryContentsRedirect ent pkgver [pkgver, "index.html"] -> tryContentsRedirect ent pkgver
_ -> return () _ -> return ()
return $ stackageIdent $ entityVal ent return ent
Nothing -> do Nothing -> do
Entity _ stackage <- getBy404 Entity _ stackage <- getBy404
$ UniqueStackage $ UniqueStackage
@ -74,11 +83,11 @@ getHaddockR slug rest = do
$ toPathPiece slug $ toPathPiece slug
redirectWith status301 $ HaddockR (stackageSlug stackage) rest redirectWith status301 $ HaddockR (stackageSlug stackage) rest
mapM_ sanitize rest mapM_ sanitize rest
dirs <- getDirs -- (gzdir, rawdir) <- getHaddockDir ident dirs <- getDirs
master <- getYesod requireDocs stackageEnt
liftIO $ haddockUnpacker master False ident
let rawfp = dirRawFp dirs ident rest let ident = stackageIdent (entityVal stackageEnt)
rawfp = dirRawFp dirs ident rest
gzfp = dirGzFp dirs ident rest gzfp = dirGzFp dirs ident rest
mime = defaultMimeLookup $ fpToText $ filename rawfp mime = defaultMimeLookup $ fpToText $ filename rawfp
@ -124,19 +133,6 @@ tryContentsRedirect (Entity sid Stackage {..}) pkgver = do
dropDash :: Text -> Text dropDash :: Text -> Text
dropDash t = fromMaybe t $ stripSuffix "-" t dropDash t = fromMaybe t $ stripSuffix "-" t
getHaddockDir :: PackageSetIdent -> Handler (FilePath, FilePath)
getHaddockDir ident = do
master <- getYesod
return $ mkDirPair (haddockRootDir master) ident
mkDirPair :: FilePath -- ^ root
-> PackageSetIdent
-> (FilePath, FilePath) -- ^ compressed, uncompressed
mkDirPair root ident =
( root </> "idents-raw" </> fpFromText (toPathPiece ident)
, root </> "idents-gz" </> fpFromText (toPathPiece ident)
)
createCompressor createCompressor
:: Dirs :: Dirs
-> IO (IORef Text, IO ()) -- ^ action to kick off compressor again -> IO (IORef Text, IO ()) -- ^ action to kick off compressor again
@ -209,93 +205,6 @@ dirCacheFp dirs digest =
name = decodeUtf8 $ B16.encode $ toBytes digest name = decodeUtf8 $ B16.encode $ toBytes digest
(x, y) = splitAt 2 name (x, y) = splitAt 2 name
-- Should have two threads: one to unpack, one to convert. Never serve the
-- uncompressed files, only the compressed files. When serving, convert on
-- demand.
createHaddockUnpacker :: FilePath -- ^ haddock root
-> BlobStore StoreKey
-> (forall a m. (MonadIO m, MonadBaseControl IO m)
=> SqlPersistT m a -> m a)
-> IO (IORef Text, ForceUnpack -> PackageSetIdent -> IO ())
createHaddockUnpacker root store runDB' = do
createTree $ dirCacheRoot dirs
createTree $ dirRawRoot dirs
createTree $ dirGzRoot dirs
chan <- newChan
(statusRef, compressor) <- createCompressor dirs
mask $ \restore -> void $ forkIO $ forever $ do
(forceUnpack, ident, res) <- readChan chan
try (restore $ go forceUnpack ident) >>= putMVar res
compressor
return (statusRef, \forceUnpack ident -> do
shouldAct <-
if forceUnpack
then return True
else not <$> doDirsExist ident
if shouldAct
then do
res <- newEmptyMVar
writeChan chan (forceUnpack, ident, res)
takeMVar res >>= either (throwM . asSomeException) return
else return ())
where
dirs = mkDirs root
removeTreeIfExists fp = whenM (isDirectory fp) (removeTree fp)
doDirsExist ident = do
e1 <- isDirectory $ dirGzIdent dirs ident
if e1
then return True
else isDirectory $ dirRawIdent dirs ident
go forceUnpack ident = do
toRun <-
if forceUnpack
then do
removeTreeIfExists $ dirRawIdent dirs ident
removeTreeIfExists $ dirGzIdent dirs ident
return True
else not <$> doDirsExist ident
when toRun $ do
withSystemTempFile "haddock-bundle.tar.xz" $ \tempfp temph -> do
withAcquire (storeRead' store (HaddockBundle ident)) $ \msrc ->
case msrc of
Nothing -> error "No haddocks exist for that snapshot"
Just src -> src $$ sinkHandle temph
hClose temph
createTree $ dirRawIdent dirs ident
let destdir = dirRawIdent dirs ident
(Nothing, Nothing, Nothing, ph) <- createProcess
(proc "tar" ["xf", tempfp])
{ cwd = Just $ fpToString destdir
}
ec <- waitForProcess ph
if ec == ExitSuccess then return () else throwM ec
-- Determine which packages have documentation and update the
-- database appropriately
runResourceT $ runDB' $ do
ment <- getBy $ UniqueStackage ident
forM_ ment $ \(Entity sid _) -> do
updateWhere
[PackageStackage ==. sid]
[PackageHasHaddocks =. False]
sourceDirectory destdir $$ mapM_C (\fp -> do
let (name', version) =
T.breakOnEnd "-"
$ fpToText
$ filename fp
mname = stripSuffix "-" name'
forM_ mname $ \name -> updateWhere
[ PackageStackage ==. sid
, PackageName' ==. PackageName name
, PackageVersion ==. Version version
]
[PackageHasHaddocks =. True]
)
data DocInfo = DocInfo Version (Map Text [Text]) data DocInfo = DocInfo Version (Map Text [Text])
instance FromJSON DocInfo where instance FromJSON DocInfo where
parseJSON = withObject "DocInfo" $ \o -> DocInfo parseJSON = withObject "DocInfo" $ \o -> DocInfo

157
Handler/Hoogle.hs Normal file
View File

@ -0,0 +1,157 @@
module Handler.Hoogle where
import Control.DeepSeq (NFData(..))
import Control.DeepSeq.Generics (genericRnf)
import Control.Spoon (spoon)
import Data.Data (Data (..))
import Data.Slug (SnapSlug)
import Data.Text.Read (decimal)
import Data.Unpacking (getHoogleDB)
import Handler.Haddock (getDirs)
import qualified Hoogle
import Import
import Text.Blaze.Html (preEscapedToHtml)
getHoogleR :: SnapSlug -> Handler Html
getHoogleR slug = do
dirs <- getDirs
mquery <- lookupGetParam "q"
mpage <- lookupGetParam "page"
exact <- maybe False (const True) <$> lookupGetParam "exact"
mresults' <- lookupGetParam "results"
let count' =
case decimal <$> mresults' of
Just (Right (i, "")) -> min perPage i
_ -> perPage
page =
case decimal <$> mpage of
Just (Right (i, "")) -> i
_ -> 1
offset = (page - 1) * perPage
Entity _ stackage <- runDB $ getBy404 $ UniqueSnapshot slug
mdatabasePath <- getHoogleDB dirs stackage
heDatabase <- case mdatabasePath of
Just x -> return $ liftIO $ Hoogle.loadDatabase $ fpToString x
Nothing -> (>>= sendResponse) $ defaultLayout $ do
setTitle "Hoogle database not available"
[whamlet|
<div .container>
<p>The given Hoogle database is not available.
<p>
<a href=@{SnapshotR slug StackageHomeR}>Return to snapshot homepage
|]
mresults <- case mquery of
Just query -> runHoogleQuery heDatabase HoogleQueryInput
{ hqiQueryInput = query
, hqiExactSearch = if exact then Just query else Nothing
, hqiLimitTo = count'
, hqiOffsetBy = offset
}
Nothing -> return $ HoogleQueryOutput "" [] Nothing
let queryText = fromMaybe "" mquery
pageLink p = (SnapshotR slug HoogleR
, (if exact then (("exact", "true"):) else id)
$ (maybe id (\q' -> (("q", q'):)) mquery)
[("page", tshow p)])
snapshotLink = SnapshotR slug StackageHomeR
hoogleForm = $(widgetFile "hoogle-form")
defaultLayout $ do
setTitle "Hoogle Search"
$(widgetFile "hoogle")
getPageCount :: Int -> Int
getPageCount totalCount = 1 + div totalCount perPage
perPage :: Int
perPage = 10
data HoogleQueryInput = HoogleQueryInput
{ hqiQueryInput :: Text
, hqiExactSearch :: Maybe Text
, hqiLimitTo :: Int
, hqiOffsetBy :: Int
}
deriving (Eq, Read, Show, Data, Typeable, Ord, Generic)
data HoogleQueryOutput = HoogleQueryBad Text
| HoogleQueryOutput Text [HoogleResult] (Maybe Int) -- ^ Text == HTML version of query, Int == total count
deriving (Read, Typeable, Data, Show, Eq)
data HoogleResult = HoogleResult
{ hrURL :: String
, hrSources :: [(PackageLink, [ModuleLink])]
, hrTitle :: String -- ^ HTML
, hrBody :: String -- ^ plain text
}
deriving (Eq, Read, Show, Data, Typeable, Ord, Generic)
data PackageLink = PackageLink
{ plName :: String
, plURL :: String
}
deriving (Eq, Read, Show, Data, Typeable, Ord, Generic)
data ModuleLink = ModuleLink
{ mlName :: String
, mlURL :: String
}
deriving (Eq, Read, Show, Data, Typeable, Ord, Generic)
instance NFData HoogleResult where rnf = genericRnf
instance NFData PackageLink where rnf = genericRnf
instance NFData ModuleLink where rnf = genericRnf
runHoogleQuery :: Monad m
=> m Hoogle.Database
-> HoogleQueryInput
-> m HoogleQueryOutput
runHoogleQuery heDatabase HoogleQueryInput {..} =
runQuery $ Hoogle.parseQuery Hoogle.Haskell query
where
query = unpack hqiQueryInput
runQuery (Left err) = return $ HoogleQueryBad (tshow err)
runQuery (Right query') = do
hoogledb <- heDatabase
let query'' = Hoogle.queryExact classifier query'
rawRes = concatMap fixResult
$ Hoogle.search hoogledb query''
mres = spoon
$ take (min 100 hqiLimitTo)
$ drop hqiOffsetBy rawRes
mcount = spoon $ limitedLength 0 rawRes
limitedLength x [] = Just x
limitedLength x (_:rest)
| x >= 100 = Nothing
| otherwise = limitedLength (x + 1) rest
rendered = pack $ Hoogle.showTagHTML $ Hoogle.renderQuery query''
return $ case (,) <$> mres <*> mcount of
Nothing ->
HoogleQueryOutput rendered [] (Just 0)
Just (results, mcount') ->
HoogleQueryOutput rendered (take hqiLimitTo results) mcount'
classifier = maybe Nothing
(const (Just Hoogle.UnclassifiedItem))
hqiExactSearch
fixResult (_, Hoogle.Result locs self docs) = do
(loc, _) <- take 1 locs
let sources' = unionsWith (++) $
mapMaybe (getPkgModPair . snd) locs
return HoogleResult
{ hrURL = loc
, hrSources = mapToList sources'
, hrTitle = Hoogle.showTagHTML self
, hrBody = fromMaybe "Problem loading documentation" $
spoon $ Hoogle.showTagText docs
}
getPkgModPair :: [(String, String)]
-> Maybe (Map PackageLink [ModuleLink])
getPkgModPair [(pkg, pkgname), (modu, moduname)] = do
let pkg' = PackageLink pkgname pkg
modu' = ModuleLink moduname modu
return $ asMap $ singletonMap pkg' [modu']
getPkgModPair _ = Nothing

39
Handler/PackageCounts.hs Normal file
View File

@ -0,0 +1,39 @@
module Handler.PackageCounts
( getPackageCountsR
) where
import Import hiding (Value (..), groupBy, (==.))
import Data.Slug (mkSlug)
import Database.Esqueleto
data Count = Count
{ name :: Text
, date :: Day
, packages :: Int
}
toCount :: (Value Text, Value UTCTime, Value Int) -> Count
toCount (Value x, Value y, Value z) =
Count x (utctDay y) z
getPackageCountsR :: Handler Html
getPackageCountsR = do
admins <- adminUsers <$> getExtra
counts <- runDB $ do
let slugs = mapMaybe mkSlug $ setToList admins
adminUids <- selectKeysList [UserHandle <-. slugs] []
fmap (map toCount) $ select $ from $ \(s, p) -> do
where_ $
(not_ $ s ^. StackageTitle `like` val "%inclusive") &&.
(s ^. StackageId ==. p ^. PackageStackage) &&.
(s ^. StackageUser `in_` valList adminUids)
groupBy (s ^. StackageTitle, s ^. StackageUploaded)
orderBy [desc $ s ^. StackageUploaded]
return
( s ^. StackageTitle
, s ^. StackageUploaded
, countRows
)
defaultLayout $ do
setTitle "Package counts"
$(widgetFile "package-counts")

View File

@ -1,10 +1,10 @@
module Handler.PackageList where module Handler.PackageList where
import qualified Data.HashMap.Strict as M import qualified Data.HashMap.Strict as M
import Data.Time (NominalDiffTime, addUTCTime) import Data.Time (NominalDiffTime)
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
import Import import Import
import Yesod.Core.Types (WidgetT (WidgetT), unWidgetT)
-- FIXME maybe just redirect to the LTS or nightly package list -- FIXME maybe just redirect to the LTS or nightly package list
getPackageListR :: Handler Html getPackageListR :: Handler Html
@ -29,7 +29,10 @@ getPackageListR = defaultLayout $ do
-- FIXME move somewhere else, maybe even yesod-core -- FIXME move somewhere else, maybe even yesod-core
cachedWidget :: NominalDiffTime -> Text -> Widget -> Widget cachedWidget :: NominalDiffTime -> Text -> Widget -> Widget
cachedWidget diff key widget = do cachedWidget _diff _key widget = do
-- Temporarily disabled, seems to be eating up too much memory
widget
{-
ref <- widgetCache <$> getYesod ref <- widgetCache <$> getYesod
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
mpair <- lookup key <$> readIORef ref mpair <- lookup key <$> readIORef ref
@ -44,3 +47,4 @@ cachedWidget diff key widget = do
-- FIXME render the builders in gw for more efficiency -- FIXME render the builders in gw for more efficiency
atomicModifyIORef' ref $ \m -> (insertMap key (addUTCTime diff now, gw) m, ()) atomicModifyIORef' ref $ \m -> (insertMap key (addUTCTime diff now, gw) m, ())
return ((), gw) return ((), gw)
-}

View File

@ -2,16 +2,14 @@ module Handler.Progress where
import Import import Import
getProgressR :: Int -> Handler Html getProgressR :: UploadProgressId -> Handler Html
getProgressR key = do getProgressR key = do
app <- getYesod UploadProgress text mdest <- runDB $ get404 key
m <- readIORef $ progressMap app case mdest of
case lookup key m of Nothing -> defaultLayout $ do
Nothing -> notFound
Just (ProgressWorking text) -> defaultLayout $ do
addHeader "Refresh" "1" addHeader "Refresh" "1"
setTitle "Working..." setTitle "Working..."
[whamlet|<p>#{text}|] [whamlet|<p>#{text}|]
Just (ProgressDone text url) -> do Just url -> do
setMessage $ toHtml text setMessage $ toHtml text
redirect url redirect url

View File

@ -31,6 +31,12 @@ getStackageHomeR slug = do
else Nothing else Nothing
base = maybe 0 (const 1) minclusive :: Int base = maybe 0 (const 1) minclusive :: Int
hoogleForm =
let queryText = "" :: Text
exact = False
in $(widgetFile "hoogle-form")
Entity sid _stackage <- runDB $ getBy404 $ UniqueSnapshot slug
defaultLayout $ do defaultLayout $ do
setTitle $ toHtml $ stackageTitle stackage setTitle $ toHtml $ stackageTitle stackage
cachedWidget (20 * 60) ("package-list-" ++ toPathPiece slug) $ do cachedWidget (20 * 60) ("package-list-" ++ toPathPiece slug) $ do

View File

@ -20,6 +20,7 @@ import System.Directory (removeFile, getTemporaryDirectory)
import System.Process (runProcess, waitForProcess) import System.Process (runProcess, waitForProcess)
import System.Exit (ExitCode (ExitSuccess)) import System.Exit (ExitCode (ExitSuccess))
import Data.Slug (mkSlug, SnapSlug (..), safeMakeSlug, unSlug) import Data.Slug (mkSlug, SnapSlug (..), safeMakeSlug, unSlug)
import Control.Debounce
fileKey :: Text fileKey :: Text
fileKey = "stackage" fileKey = "stackage"
@ -78,12 +79,28 @@ putUploadStackageR = do
when (isJust mstackage) $ invalidArgs ["Stackage already exists"] when (isJust mstackage) $ invalidArgs ["Stackage already exists"]
app <- getYesod app <- getYesod
key <- atomicModifyIORef (nextProgressKey app) $ \i -> (i + 1, i + 1) let initProgress = UploadProgress "Upload starting" Nothing
let updateHelper :: MonadBase IO m => Progress -> m () key <- runDB $ insert initProgress
updateHelper p = atomicModifyIORef (progressMap app) $ \m -> (insertMap key p m, ())
-- We don't want to be writing progress updates to the database too
-- frequently, so let's just do it once per second at most.
-- Debounce to the rescue!
statusRef <- newIORef initProgress
writeToDB <- liftIO $ mkDebounce defaultDebounceSettings
{ debounceAction = do
up <- readIORef statusRef
runPool (persistConfig app) (replace key up) (connPool app)
}
let updateHelper :: MonadBase IO m => UploadProgress -> m ()
updateHelper p = do
writeIORef statusRef p
liftBase writeToDB
update :: MonadBase IO m => Text -> m () update :: MonadBase IO m => Text -> m ()
update msg = updateHelper (ProgressWorking msg) update msg = updateHelper (UploadProgress msg Nothing)
done msg url = updateHelper (ProgressDone msg url) done msg route = do
render <- getUrlRender
updateHelper (UploadProgress msg $ Just $ render route)
onExc e = done ("Exception occurred: " ++ tshow e) ProfileR onExc e = done ("Exception occurred: " ++ tshow e) ProfileR
setAlias = do setAlias = do
forM_ (malias >>= mkSlug) $ \alias -> do forM_ (malias >>= mkSlug) $ \alias -> do
@ -167,8 +184,7 @@ putUploadStackageR = do
return slug return slug
done "Stackage created" $ SnapshotR slug StackageHomeR done "Stackage created" $ SnapshotR slug StackageHomeR
else do else done "Error creating index file" ProfileR
done "Error creating index file" ProfileR
addHeader "X-Stackage-Ident" $ toPathPiece ident addHeader "X-Stackage-Ident" $ toPathPiece ident
redirect $ ProgressR key redirect $ ProgressR key

View File

@ -86,11 +86,11 @@ getSnapshotInfoByIdent ident = withCache $ do
atomicModifyIORef' cacheRef $ \m -> atomicModifyIORef' cacheRef $ \m ->
(insertMap ident x m, x) (insertMap ident x m, x)
data Dirs = Dirs data Dirs = Dirs
{ dirRawRoot :: !FilePath { dirRawRoot :: !FilePath
, dirGzRoot :: !FilePath , dirGzRoot :: !FilePath
, dirCacheRoot :: !FilePath , dirCacheRoot :: !FilePath
, dirHoogleRoot :: !FilePath
} }
getDirs :: Handler Dirs getDirs :: Handler Dirs
@ -101,12 +101,35 @@ mkDirs dir = Dirs
{ dirRawRoot = dir </> "idents-raw" { dirRawRoot = dir </> "idents-raw"
, dirGzRoot = dir </> "idents-gz" , dirGzRoot = dir </> "idents-gz"
, dirCacheRoot = dir </> "cachedir" , dirCacheRoot = dir </> "cachedir"
, dirHoogleRoot = dir </> "hoogle"
} }
dirGzIdent, dirRawIdent :: Dirs -> PackageSetIdent -> FilePath dirGzIdent, dirRawIdent, dirHoogleIdent :: Dirs -> PackageSetIdent -> FilePath
dirGzIdent dirs ident = dirGzRoot dirs </> fpFromText (toPathPiece ident) dirGzIdent dirs ident = dirGzRoot dirs </> fpFromText (toPathPiece ident)
dirRawIdent dirs ident = dirRawRoot dirs </> fpFromText (toPathPiece ident) dirRawIdent dirs ident = dirRawRoot dirs </> fpFromText (toPathPiece ident)
dirHoogleIdent dirs ident = dirHoogleRoot dirs </> fpFromText (toPathPiece ident)
dirGzFp, dirRawFp :: Dirs -> PackageSetIdent -> [Text] -> FilePath dirGzFp, dirRawFp, dirHoogleFp :: Dirs -> PackageSetIdent -> [Text] -> FilePath
dirGzFp dirs ident rest = dirGzIdent dirs ident </> mconcat (map fpFromText rest) dirGzFp dirs ident rest = dirGzIdent dirs ident </> mconcat (map fpFromText rest)
dirRawFp dirs ident rest = dirRawIdent dirs ident </> mconcat (map fpFromText rest) dirRawFp dirs ident rest = dirRawIdent dirs ident </> mconcat (map fpFromText rest)
dirHoogleFp dirs ident rest = dirHoogleIdent dirs ident </> mconcat (map fpFromText rest)
requireDocs :: Entity Stackage -> Handler ()
requireDocs stackageEnt = do
master <- getYesod
status <- liftIO $ duRequestDocs (appDocUnpacker master) stackageEnt
case status of
USReady -> return ()
USBusy -> (>>= sendResponse) $ defaultLayout $ do
setTitle "Docs unpacking, please wait"
addHeader "Refresh" "1"
msg <- liftIO $ duGetStatus $ appDocUnpacker master
[whamlet|
<div .container>
<p>Docs are currently being unpacked, please wait.
<p>This page will automatically reload every second.
<p>Current status: #{msg}
|]
USFailed e -> invalidArgs
[ "Docs not available: " ++ e
]

View File

@ -58,8 +58,14 @@ data StoreKey = HackageCabal !PackageName !Version
| HackageViewIndex !HackageView | HackageViewIndex !HackageView
| SnapshotBundle !PackageSetIdent | SnapshotBundle !PackageSetIdent
| HaddockBundle !PackageSetIdent | HaddockBundle !PackageSetIdent
| HoogleDB !PackageSetIdent !HoogleVersion
deriving (Show, Eq, Ord, Typeable) deriving (Show, Eq, Ord, Typeable)
newtype HoogleVersion = HoogleVersion Text
deriving (Show, Eq, Ord, Typeable, PathPiece)
currentHoogleVersion :: HoogleVersion
currentHoogleVersion = HoogleVersion VERSION_hoogle
instance ToPath StoreKey where instance ToPath StoreKey where
toPath (HackageCabal name version) = ["hackage", toPathPiece name, toPathPiece version ++ ".cabal"] toPath (HackageCabal name version) = ["hackage", toPathPiece name, toPathPiece version ++ ".cabal"]
toPath (HackageSdist name version) = ["hackage", toPathPiece name, toPathPiece version ++ ".tar.gz"] toPath (HackageSdist name version) = ["hackage", toPathPiece name, toPathPiece version ++ ".tar.gz"]
@ -95,6 +101,11 @@ instance ToPath StoreKey where
[ "haddock" [ "haddock"
, toPathPiece ident ++ ".tar.xz" , toPathPiece ident ++ ".tar.xz"
] ]
toPath (HoogleDB ident ver) =
[ "hoogle"
, toPathPiece ver
, toPathPiece ident ++ ".hoo.gz"
]
instance BackupToS3 StoreKey where instance BackupToS3 StoreKey where
shouldBackup HackageCabal{} = False shouldBackup HackageCabal{} = False
shouldBackup HackageSdist{} = False shouldBackup HackageSdist{} = False
@ -105,6 +116,7 @@ instance BackupToS3 StoreKey where
shouldBackup HackageViewIndex{} = False shouldBackup HackageViewIndex{} = False
shouldBackup SnapshotBundle{} = True shouldBackup SnapshotBundle{} = True
shouldBackup HaddockBundle{} = True shouldBackup HaddockBundle{} = True
shouldBackup HoogleDB{} = True
newtype HackageRoot = HackageRoot { unHackageRoot :: Text } newtype HackageRoot = HackageRoot { unHackageRoot :: Text }
deriving (Show, Read, Typeable, Eq, Ord, Hashable, PathPiece, ToMarkup) deriving (Show, Read, Typeable, Eq, Ord, Hashable, PathPiece, ToMarkup)
@ -113,3 +125,7 @@ class HasHackageRoot a where
getHackageRoot :: a -> HackageRoot getHackageRoot :: a -> HackageRoot
instance HasHackageRoot HackageRoot where instance HasHackageRoot HackageRoot where
getHackageRoot = id getHackageRoot = id
data UnpackStatus = USReady
| USBusy
| USFailed !Text

View File

@ -1,7 +1,7 @@
-- Stackage snapshot from: http://www.stackage.org/snapshot/nightly-2014-12-22 -- Stackage snapshot from: http://www.stackage.org/snapshot/lts-1.0
-- Please place this file next to your .cabal file as cabal.config -- Please place this file next to your .cabal file as cabal.config
-- To only use tested packages, uncomment the following line: -- To only use tested packages, uncomment the following line:
-- remote-repo: stackage-nightly-2014-12-22:http://www.stackage.org/snapshot/nightly-2014-12-22 -- remote-repo: stackage-lts-1.0:http://www.stackage.org/snapshot/lts-1.0
constraints: abstract-deque ==0.3, constraints: abstract-deque ==0.3,
abstract-par ==0.3.3, abstract-par ==0.3.3,
accelerate ==0.15.0.0, accelerate ==0.15.0.0,
@ -15,6 +15,7 @@ constraints: abstract-deque ==0.3,
aeson-pretty ==0.7.2, aeson-pretty ==0.7.2,
aeson-qq ==0.7.4, aeson-qq ==0.7.4,
aeson-utils ==0.2.2.1, aeson-utils ==0.2.2.1,
alarmclock ==0.2.0.5,
alex ==3.1.3, alex ==3.1.3,
amqp ==0.10.1, amqp ==0.10.1,
ansi-terminal ==0.6.2.1, ansi-terminal ==0.6.2.1,
@ -29,7 +30,7 @@ constraints: abstract-deque ==0.3,
asn1-encoding ==0.9.0, asn1-encoding ==0.9.0,
asn1-parse ==0.9.0, asn1-parse ==0.9.0,
asn1-types ==0.3.0, asn1-types ==0.3.0,
async ==2.0.1.6, async ==2.0.2,
atto-lisp ==0.2.2, atto-lisp ==0.2.2,
attoparsec ==0.12.1.2, attoparsec ==0.12.1.2,
attoparsec-conduit ==1.1.0, attoparsec-conduit ==1.1.0,
@ -39,16 +40,18 @@ constraints: abstract-deque ==0.3,
auto-update ==0.1.2.1, auto-update ==0.1.2.1,
aws ==0.11, aws ==0.11,
bake ==0.2, bake ==0.2,
bank-holidays-england ==0.1.0.2,
barecheck ==0.2.0.6, barecheck ==0.2.0.6,
base installed, base installed,
base16-bytestring ==0.1.1.6, base16-bytestring ==0.1.1.6,
base64-bytestring ==1.0.0.1, base64-bytestring ==1.0.0.1,
base-compat ==0.5.0, base-compat ==0.5.0,
base-prelude ==0.1.8, base-prelude ==0.1.11,
base-unicode-symbols ==0.2.2.4, base-unicode-symbols ==0.2.2.4,
basic-prelude ==0.3.10, basic-prelude ==0.3.10,
bifunctors ==4.2, bifunctors ==4.2,
binary installed, binary installed,
binary-conduit ==1.2.3,
binary-list ==1.0.1.0, binary-list ==1.0.1.0,
bindings-DSL ==1.0.21, bindings-DSL ==1.0.21,
bioace ==0.0.1, bioace ==0.0.1,
@ -65,7 +68,7 @@ constraints: abstract-deque ==0.3,
blaze-builder ==0.3.3.4, blaze-builder ==0.3.3.4,
blaze-builder-enumerator ==0.2.0.6, blaze-builder-enumerator ==0.2.0.6,
blaze-html ==0.7.0.3, blaze-html ==0.7.0.3,
blaze-markup ==0.6.1.1, blaze-markup ==0.6.2.0,
blaze-svg ==0.3.4, blaze-svg ==0.3.4,
blaze-textual ==0.2.0.9, blaze-textual ==0.2.0.9,
BlogLiterately ==0.7.1.7, BlogLiterately ==0.7.1.7,
@ -76,6 +79,7 @@ constraints: abstract-deque ==0.3,
bool-extras ==0.4.0, bool-extras ==0.4.0,
bound ==1.0.4, bound ==1.0.4,
BoundedChan ==1.0.3.0, BoundedChan ==1.0.3.0,
broadcast-chan ==0.1.0,
bson ==0.3.1, bson ==0.3.1,
bumper ==0.6.0.2, bumper ==0.6.0.2,
byteable ==0.1.1, byteable ==0.1.1,
@ -87,17 +91,18 @@ constraints: abstract-deque ==0.3,
bytestring-lexing ==0.4.3.2, bytestring-lexing ==0.4.3.2,
bytestring-mmap ==0.2.2, bytestring-mmap ==0.2.2,
bytestring-progress ==1.0.3, bytestring-progress ==1.0.3,
bytestring-show ==0.3.5.6,
bytestring-trie ==0.2.4, bytestring-trie ==0.2.4,
bzlib ==0.5.0.4, bzlib ==0.5.0.4,
bzlib-conduit ==0.2.1.3, bzlib-conduit ==0.2.1.3,
c2hs ==0.20.1, c2hs ==0.20.1,
Cabal installed, Cabal installed,
cabal-install ==1.18.0.6, cabal-install ==1.18.0.7,
cabal-src ==0.2.5, cabal-src ==0.2.5,
cairo ==0.13.0.5, cairo ==0.13.0.6,
case-insensitive ==1.2.0.3, case-insensitive ==1.2.0.3,
cases ==0.1.2, cases ==0.1.2,
cassava ==0.4.2.0, cassava ==0.4.2.1,
cautious-file ==1.0.2, cautious-file ==1.0.2,
cereal ==0.4.1.0, cereal ==0.4.1.0,
cereal-conduit ==0.7.2.3, cereal-conduit ==0.7.2.3,
@ -105,7 +110,7 @@ constraints: abstract-deque ==0.3,
charset ==0.3.7, charset ==0.3.7,
Chart ==1.3.2, Chart ==1.3.2,
Chart-diagrams ==1.3.2, Chart-diagrams ==1.3.2,
ChasingBottoms ==1.3.0.8, ChasingBottoms ==1.3.0.9,
check-email ==1.0, check-email ==1.0,
checkers ==0.4.1, checkers ==0.4.1,
chell ==0.4, chell ==0.4,
@ -116,7 +121,7 @@ constraints: abstract-deque ==0.3,
cipher-camellia ==0.0.2, cipher-camellia ==0.0.2,
cipher-des ==0.0.6, cipher-des ==0.0.6,
cipher-rc4 ==0.1.4, cipher-rc4 ==0.1.4,
circle-packing ==0.1.0.3, circle-packing ==0.1.0.4,
classy-prelude ==0.10.2, classy-prelude ==0.10.2,
classy-prelude-conduit ==0.10.2, classy-prelude-conduit ==0.10.2,
classy-prelude-yesod ==0.10.2, classy-prelude-yesod ==0.10.2,
@ -137,14 +142,14 @@ constraints: abstract-deque ==0.3,
concurrent-supply ==0.1.7, concurrent-supply ==0.1.7,
cond ==0.4.1.1, cond ==0.4.1.1,
conduit ==1.2.3.1, conduit ==1.2.3.1,
conduit-combinators ==0.3.0.4, conduit-combinators ==0.3.0.5,
conduit-extra ==1.1.5.1, conduit-extra ==1.1.6,
configurator ==0.3.0.0, configurator ==0.3.0.0,
connection ==0.2.3, connection ==0.2.3,
constraints ==0.4.1.1, constraints ==0.4.1.2,
containers installed, containers installed,
containers-unicode-symbols ==0.3.1.1, containers-unicode-symbols ==0.3.1.1,
contravariant ==1.2, contravariant ==1.2.0.1,
control-monad-free ==0.5.3, control-monad-free ==0.5.3,
control-monad-loop ==0.1, control-monad-loop ==0.1,
convertible ==1.1.0.0, convertible ==1.1.0.0,
@ -161,8 +166,8 @@ constraints: abstract-deque ==0.3,
cryptohash ==0.11.6, cryptohash ==0.11.6,
cryptohash-conduit ==0.1.1, cryptohash-conduit ==0.1.1,
cryptohash-cryptoapi ==0.1.3, cryptohash-cryptoapi ==0.1.3,
crypto-numbers ==0.2.3, crypto-numbers ==0.2.7,
crypto-pubkey ==0.2.6, crypto-pubkey ==0.2.7,
crypto-pubkey-types ==0.4.2.3, crypto-pubkey-types ==0.4.2.3,
crypto-random ==0.0.8, crypto-random ==0.0.8,
crypto-random-api ==0.2.0, crypto-random-api ==0.2.0,
@ -184,12 +189,13 @@ constraints: abstract-deque ==0.3,
data-memocombinators ==0.5.1, data-memocombinators ==0.5.1,
data-reify ==0.6, data-reify ==0.6,
DAV ==1.0.3, DAV ==1.0.3,
Decimal ==0.4.2,
deepseq installed, deepseq installed,
deepseq-generics ==0.1.1.2, deepseq-generics ==0.1.1.2,
derive ==2.5.18, derive ==2.5.18,
diagrams ==1.2, diagrams ==1.2,
diagrams-builder ==0.6.0.2, diagrams-builder ==0.6.0.2,
diagrams-cairo ==1.2.0.4, diagrams-cairo ==1.2.0.5,
diagrams-contrib ==1.1.2.4, diagrams-contrib ==1.1.2.4,
diagrams-core ==1.2.0.4, diagrams-core ==1.2.0.4,
diagrams-haddock ==0.2.2.12, diagrams-haddock ==0.2.2.12,
@ -198,13 +204,19 @@ constraints: abstract-deque ==0.3,
diagrams-svg ==1.1.0.3, diagrams-svg ==1.1.0.3,
Diff ==0.3.0, Diff ==0.3.0,
digest ==0.0.1.2, digest ==0.0.1.2,
digestive-functors ==0.7.1.1, digestive-functors ==0.7.1.3,
dimensional ==0.13.0.1, dimensional ==0.13.0.1,
directory installed, directory installed,
directory-tree ==0.12.0, directory-tree ==0.12.0,
direct-sqlite ==2.3.14, direct-sqlite ==2.3.14,
distributed-process ==0.5.3, distributed-process ==0.5.3,
distributed-process-async ==0.2.1,
distributed-process-client-server ==0.1.2,
distributed-process-execution ==0.1.1,
distributed-process-extras ==0.2.0,
distributed-process-simplelocalnet ==0.2.2.0, distributed-process-simplelocalnet ==0.2.2.0,
distributed-process-supervisor ==0.1.2,
distributed-process-task ==0.1.1,
distributed-static ==0.3.1.0, distributed-static ==0.3.1.0,
distributive ==0.4.4, distributive ==0.4.4,
djinn-ghc ==0.0.2.2, djinn-ghc ==0.0.2.2,
@ -219,6 +231,7 @@ constraints: abstract-deque ==0.3,
elm-build-lib ==0.14.0.0, elm-build-lib ==0.14.0.0,
elm-compiler ==0.14, elm-compiler ==0.14,
elm-core-sources ==1.0.0, elm-core-sources ==1.0.0,
elm-package ==0.2.2,
email-validate ==2.0.1, email-validate ==2.0.1,
enclosed-exceptions ==1.0.1, enclosed-exceptions ==1.0.1,
entropy ==0.3.4.1, entropy ==0.3.4.1,
@ -232,7 +245,6 @@ constraints: abstract-deque ==0.3,
exceptions ==0.6.1, exceptions ==0.6.1,
exception-transformers ==0.3.0.4, exception-transformers ==0.3.0.4,
executable-path ==0.0.3, executable-path ==0.0.3,
ex-pool ==0.2,
extensible-exceptions ==0.1.1.4, extensible-exceptions ==0.1.1.4,
extra ==1.0, extra ==1.0,
failure ==0.2.0.3, failure ==0.2.0.3,
@ -247,7 +259,7 @@ constraints: abstract-deque ==0.3,
fb ==1.0.7, fb ==1.0.7,
fb-persistent ==0.3.4, fb-persistent ==0.3.4,
fclabels ==2.0.2, fclabels ==2.0.2,
FenwickTree ==0.1.1, FenwickTree ==0.1.2,
fgl ==5.5.0.1, fgl ==5.5.0.1,
file-embed ==0.0.7, file-embed ==0.0.7,
file-location ==0.4.5.3, file-location ==0.4.5.3,
@ -259,6 +271,7 @@ constraints: abstract-deque ==0.3,
flexible-defaults ==0.0.1.1, flexible-defaults ==0.0.1.1,
focus ==0.1.3, focus ==0.1.3,
foldl ==1.0.7, foldl ==1.0.7,
FontyFruity ==0.4,
force-layout ==0.3.0.8, force-layout ==0.3.0.8,
foreign-store ==0.1, foreign-store ==0.1,
formatting ==6.0.0, formatting ==6.0.0,
@ -271,24 +284,24 @@ constraints: abstract-deque ==0.3,
gd ==3000.7.3, gd ==3000.7.3,
generic-aeson ==0.2.0.2, generic-aeson ==0.2.0.2,
generic-deriving ==1.6.3, generic-deriving ==1.6.3,
GenericPretty ==1.2.1,
generics-sop ==0.1.0.4, generics-sop ==0.1.0.4,
ghc-heap-view ==0.5.3, ghc-heap-view ==0.5.3,
ghcid ==0.3.3, ghcid ==0.3.4,
ghc-mod ==5.2.1.1, ghc-mod ==5.2.1.2,
ghc-mtl ==1.2.1.0, ghc-mtl ==1.2.1.0,
ghc-paths ==0.1.0.9, ghc-paths ==0.1.0.9,
ghc-prim installed, ghc-prim installed,
ghc-syb-utils ==0.2.2, ghc-syb-utils ==0.2.2,
gio ==0.13.0.3, gio ==0.13.0.4,
git-embed ==0.1.0, git-embed ==0.1.0,
gl ==0.6.2, gl ==0.6.2,
glib ==0.13.0.6, glib ==0.13.0.7,
Glob ==0.7.5, Glob ==0.7.5,
GLURaw ==1.4.0.1, GLURaw ==1.4.0.1,
GLUT ==2.5.1.1, GLUT ==2.5.1.1,
graph-core ==0.2.1.0, graph-core ==0.2.1.0,
graphs ==0.5.0.1, graphs ==0.5.0.1,
graphviz ==2999.17.0.1,
gravatar ==0.6, gravatar ==0.6,
groundhog ==0.7.0.1, groundhog ==0.7.0.1,
groundhog-mysql ==0.7.0.1, groundhog-mysql ==0.7.0.1,
@ -297,15 +310,15 @@ constraints: abstract-deque ==0.3,
groundhog-th ==0.7.0, groundhog-th ==0.7.0,
groupoids ==4.0, groupoids ==4.0,
groups ==0.4.0.0, groups ==0.4.0.0,
gtk ==0.13.3, gtk ==0.13.4,
gtk2hs-buildtools ==0.13.0.3, gtk2hs-buildtools ==0.13.0.3,
haddock-api ==2.15.0, haddock-api ==2.15.0.2,
haddock-library ==1.1.1, haddock-library ==1.1.1,
half ==0.2.0.1, half ==0.2.0.1,
HandsomeSoup ==0.3.5, HandsomeSoup ==0.3.5,
happstack-server ==7.3.9, happstack-server ==7.3.9,
happy ==1.19.4, happy ==1.19.4,
hashable ==1.2.3.0, hashable ==1.2.3.1,
hashable-extras ==0.2.0.1, hashable-extras ==0.2.0.1,
hashmap ==1.3.0.1, hashmap ==1.3.0.1,
hashtables ==1.2.0.1, hashtables ==1.2.0.1,
@ -318,12 +331,12 @@ constraints: abstract-deque ==0.3,
haskell-src ==1.0.1.6, haskell-src ==1.0.1.6,
haskell-src-exts ==1.16.0.1, haskell-src-exts ==1.16.0.1,
haskell-src-meta ==0.6.0.8, haskell-src-meta ==0.6.0.8,
hasql ==0.4.1, hasql ==0.7.1,
hasql-backend ==0.2.2, hasql-backend ==0.4.0,
hasql-postgres ==0.9.0, hasql-postgres ==0.10.1,
hastache ==0.6.0, hastache ==0.6.1,
HaTeX ==3.16.0.0, HaTeX ==3.16.0.0,
HaXml ==1.24.1, HaXml ==1.25,
haxr ==3000.10.3.1, haxr ==3000.10.3.1,
HCodecs ==0.5, HCodecs ==0.5,
hdaemonize ==0.5.0.0, hdaemonize ==0.5.0.0,
@ -333,29 +346,30 @@ constraints: abstract-deque ==0.3,
heist ==0.14.0.1, heist ==0.14.0.1,
here ==1.2.6, here ==1.2.6,
heredoc ==0.2.0.0, heredoc ==0.2.0.0,
hflags ==0.4,
highlighting-kate ==0.5.11.1, highlighting-kate ==0.5.11.1,
hinotify ==0.3.7, hinotify ==0.3.7,
hint ==0.4.2.1, hint ==0.4.2.1,
histogram-fill ==0.8.3.0, histogram-fill ==0.8.3.0,
hit ==0.6.2, hit ==0.6.2,
hjsmin ==0.1.4.7, hjsmin ==0.1.4.7,
hledger ==0.23.3, hledger ==0.24,
hledger-lib ==0.23.3, hledger-lib ==0.24,
hlibgit2 ==0.18.0.13, hlibgit2 ==0.18.0.13,
hlint ==1.9.13, hlint ==1.9.14,
hmatrix ==0.16.1.2, hmatrix ==0.16.1.3,
hmatrix-gsl ==0.16.0.2, hmatrix-gsl ==0.16.0.2,
hoauth2 ==0.4.3,
holy-project ==0.1.1.1, holy-project ==0.1.1.1,
hoogle ==4.2.36, hoogle ==4.2.36,
hoopl installed, hoopl installed,
hOpenPGP ==1.11, hOpenPGP ==1.11,
hopenpgp-tools ==0.13,
hostname ==1.0, hostname ==1.0,
hostname-validate ==1.0.0, hostname-validate ==1.0.0,
hourglass ==0.2.6, hourglass ==0.2.6,
hpc installed, hpc installed,
hPDB ==1.2.0, hPDB ==1.2.0.2,
hPDB-examples ==1.1.2, hPDB-examples ==1.2.0.1,
hs-bibutils ==5.5, hs-bibutils ==5.5,
hscolour ==1.20.3, hscolour ==1.20.3,
hse-cpp ==0.1, hse-cpp ==0.1,
@ -365,7 +379,7 @@ constraints: abstract-deque ==0.3,
hspec2 ==0.6.1, hspec2 ==0.6.1,
hspec-core ==2.1.2, hspec-core ==2.1.2,
hspec-discover ==2.1.2, hspec-discover ==2.1.2,
hspec-expectations ==0.6.1, hspec-expectations ==0.6.1.1,
hspec-meta ==2.0.0, hspec-meta ==2.0.0,
hspec-wai ==0.6.2, hspec-wai ==0.6.2,
hspec-wai-json ==0.6.0, hspec-wai-json ==0.6.0,
@ -383,7 +397,7 @@ constraints: abstract-deque ==0.3,
http-types ==0.8.5, http-types ==0.8.5,
HUnit ==1.2.5.2, HUnit ==1.2.5.2,
hweblib ==0.6.3, hweblib ==0.6.3,
hxt ==9.3.1.7, hxt ==9.3.1.10,
hxt-charproperties ==9.2.0.0, hxt-charproperties ==9.2.0.0,
hxt-http ==9.1.5, hxt-http ==9.1.5,
hxt-pickle-utils ==0.1.0.2, hxt-pickle-utils ==0.1.0.2,
@ -394,11 +408,12 @@ constraints: abstract-deque ==0.3,
hyphenation ==0.4, hyphenation ==0.4,
idna ==0.3.0, idna ==0.3.0,
ieee754 ==0.7.4, ieee754 ==0.7.4,
IfElse ==0.85,
imagesize-conduit ==1.0.0.4, imagesize-conduit ==1.0.0.4,
immortal ==0.2, immortal ==0.2,
incremental-parser ==0.2.3.3, incremental-parser ==0.2.3.3,
indents ==0.3.3, indents ==0.3.3,
ini ==0.2.2, ini ==0.3.0,
integer-gmp installed, integer-gmp installed,
integration ==0.2.0.1, integration ==0.2.0.1,
interpolate ==0.1.0, interpolate ==0.1.0,
@ -411,15 +426,16 @@ constraints: abstract-deque ==0.3,
iterable ==3.0, iterable ==3.0,
ixset ==1.0.6, ixset ==1.0.6,
js-flot ==0.8.3, js-flot ==0.8.3,
js-jquery ==1.11.1, js-jquery ==1.11.2,
json-autotype ==0.2.5.4,
json-schema ==0.7.3.0, json-schema ==0.7.3.0,
JuicyPixels ==3.1.7.1, JuicyPixels ==3.2.1,
JuicyPixels-repa ==0.7, JuicyPixels-repa ==0.7,
kan-extensions ==4.1.1, kan-extensions ==4.2,
kdt ==0.2.2, kdt ==0.2.2,
keter ==1.3.7.1, keter ==1.3.7.1,
keys ==3.10.1, keys ==3.10.1,
kure ==2.4.10, kure ==2.16.4,
language-c ==0.4.7, language-c ==0.4.7,
language-ecmascript ==0.16.2, language-ecmascript ==0.16.2,
language-glsl ==0.1.1, language-glsl ==0.1.1,
@ -438,13 +454,14 @@ constraints: abstract-deque ==0.3,
lifted-base ==0.2.3.3, lifted-base ==0.2.3.3,
linear ==1.15.5, linear ==1.15.5,
linear-accelerate ==0.2, linear-accelerate ==0.2,
list-t ==0.3.1, list-t ==0.4.2,
loch-th ==0.2.1, loch-th ==0.2.1,
log-domain ==0.9.3, log-domain ==0.9.3,
logfloat ==0.12.1, logfloat ==0.12.1,
logict ==0.6.0.2, logict ==0.6.0.2,
loop ==0.2.0, loop ==0.2.0,
lucid ==2.5, lucid ==2.5,
lzma-conduit ==1.1.1,
machines ==0.4.1, machines ==0.4.1,
mandrill ==0.1.1.0, mandrill ==0.1.1.0,
map-syntax ==0.2, map-syntax ==0.2,
@ -464,13 +481,13 @@ constraints: abstract-deque ==0.3,
MissingH ==1.3.0.1, MissingH ==1.3.0.1,
mmap ==0.5.9, mmap ==0.5.9,
mmorph ==1.0.4, mmorph ==1.0.4,
MonadCatchIO-transformers ==0.3.1.2, MonadCatchIO-transformers ==0.3.1.3,
monad-control ==0.3.3.0, monad-control ==0.3.3.0,
monad-coroutine ==0.8.0.1, monad-coroutine ==0.8.0.1,
monadcryptorandom ==0.6.1, monadcryptorandom ==0.6.1,
monad-extras ==0.5.9, monad-extras ==0.5.9,
monadic-arrays ==0.2.1.3, monadic-arrays ==0.2.1.3,
monad-journal ==0.6.0.1, monad-journal ==0.6.0.2,
monad-logger ==0.3.11.1, monad-logger ==0.3.11.1,
monad-loops ==0.4.2.1, monad-loops ==0.4.2.1,
monad-par ==0.3.4.7, monad-par ==0.3.4.7,
@ -488,7 +505,7 @@ constraints: abstract-deque ==0.3,
mono-traversable ==0.7.0, mono-traversable ==0.7.0,
mtl ==2.1.3.1, mtl ==2.1.3.1,
mtlparse ==0.1.2, mtlparse ==0.1.2,
mtl-prelude ==1.0.1, mtl-prelude ==1.0.2,
multimap ==1.2.1, multimap ==1.2.1,
multipart ==0.1.2, multipart ==0.1.2,
MusicBrainz ==0.2.2, MusicBrainz ==0.2.2,
@ -506,7 +523,7 @@ constraints: abstract-deque ==0.3,
network-simple ==0.4.0.2, network-simple ==0.4.0.2,
network-transport ==0.4.1.0, network-transport ==0.4.1.0,
network-transport-tcp ==0.4.1, network-transport-tcp ==0.4.1,
network-transport-tests ==0.2.1.0, network-transport-tests ==0.2.2.0,
network-uri ==2.6.0.1, network-uri ==2.6.0.1,
newtype ==0.2, newtype ==0.2,
nsis ==0.2.4, nsis ==0.2.4,
@ -514,7 +531,7 @@ constraints: abstract-deque ==0.3,
numeric-extras ==0.0.3, numeric-extras ==0.0.3,
NumInstances ==1.4, NumInstances ==1.4,
numtype ==1.1, numtype ==1.1,
Octree ==0.5.3, Octree ==0.5.4.2,
old-locale installed, old-locale installed,
old-time installed, old-time installed,
OneTuple ==0.2.1, OneTuple ==0.2.1,
@ -529,8 +546,8 @@ constraints: abstract-deque ==0.3,
pandoc ==1.13.2, pandoc ==1.13.2,
pandoc-citeproc ==0.6, pandoc-citeproc ==0.6,
pandoc-types ==1.12.4.1, pandoc-types ==1.12.4.1,
pango ==0.13.0.4, pango ==0.13.0.5,
parallel ==3.2.0.5, parallel ==3.2.0.6,
parallel-io ==0.3.3, parallel-io ==0.3.3,
parseargs ==0.1.5.2, parseargs ==0.1.5.2,
parsec ==3.1.7, parsec ==3.1.7,
@ -541,23 +558,23 @@ constraints: abstract-deque ==0.3,
pcre-light ==0.4.0.3, pcre-light ==0.4.0.3,
pdfinfo ==1.5.1, pdfinfo ==1.5.1,
pem ==0.2.2, pem ==0.2.2,
persistent ==2.1.1.2, persistent ==2.1.1.3,
persistent-mongoDB ==2.1.2, persistent-mongoDB ==2.1.2,
persistent-mysql ==2.1.2, persistent-mysql ==2.1.2,
persistent-postgresql ==2.1.2, persistent-postgresql ==2.1.2,
persistent-sqlite ==2.1.1.1, persistent-sqlite ==2.1.1.2,
persistent-template ==2.1.0.1, persistent-template ==2.1.0.1,
phantom-state ==0.2.0.2, phantom-state ==0.2.0.2,
pipes ==4.1.4, pipes ==4.1.4,
pipes-concurrency ==2.0.2, pipes-concurrency ==2.0.2,
pipes-parse ==3.0.2, pipes-parse ==3.0.2,
placeholders ==0.1, placeholders ==0.1,
pointed ==4.1.1, pointed ==4.2,
polyparse ==1.9, polyparse ==1.10,
pool-conduit ==0.1.2.3, pool-conduit ==0.1.2.3,
postgresql-binary ==0.5.0, postgresql-binary ==0.5.0,
postgresql-libpq ==0.9.0.1, postgresql-libpq ==0.9.0.1,
postgresql-simple ==0.4.8.0, postgresql-simple ==0.4.9.0,
pqueue ==1.2.1, pqueue ==1.2.1,
prefix-units ==0.1.0.2, prefix-units ==0.1.0.2,
prelude-extras ==0.4, prelude-extras ==0.4,
@ -584,8 +601,9 @@ constraints: abstract-deque ==0.3,
QuasiText ==0.1.2.5, QuasiText ==0.1.2.5,
QuickCheck ==2.7.6, QuickCheck ==2.7.6,
quickcheck-assertions ==0.1.1, quickcheck-assertions ==0.1.1,
quickcheck-instances ==0.3.9, quickcheck-instances ==0.3.10,
quickcheck-io ==0.1.1, quickcheck-io ==0.1.1,
quickcheck-unicode ==1.0.0.0,
quickpull ==0.4.0.0, quickpull ==0.4.0.0,
rainbow ==0.20.0.4, rainbow ==0.20.0.4,
rainbow-tests ==0.20.0.4, rainbow-tests ==0.20.0.4,
@ -594,6 +612,7 @@ constraints: abstract-deque ==0.3,
random-shuffle ==0.0.4, random-shuffle ==0.0.4,
random-source ==0.3.0.6, random-source ==0.3.0.6,
rank1dynamic ==0.2.0.1, rank1dynamic ==0.2.0.1,
Rasterific ==0.4,
raw-strings-qq ==1.0.2, raw-strings-qq ==1.0.2,
ReadArgs ==1.2.2, ReadArgs ==1.2.2,
reducers ==3.10.3, reducers ==3.10.3,
@ -606,8 +625,9 @@ constraints: abstract-deque ==0.3,
regexpr ==0.5.4, regexpr ==0.5.4,
regex-tdfa ==1.2.0, regex-tdfa ==1.2.0,
regex-tdfa-rc ==1.1.8.3, regex-tdfa-rc ==1.1.8.3,
regular ==0.3.4.3, regular ==0.3.4.4,
regular-xmlpickler ==0.2, regular-xmlpickler ==0.2,
rematch ==0.2.0.0,
repa ==3.3.1.2, repa ==3.3.1.2,
repa-algorithms ==3.3.1.2, repa-algorithms ==3.3.1.2,
repa-devil ==0.3.2.2, repa-devil ==0.3.2.2,
@ -615,7 +635,7 @@ constraints: abstract-deque ==0.3,
reroute ==0.2.2.1, reroute ==0.2.2.1,
resource-pool ==0.2.3.2, resource-pool ==0.2.3.2,
resourcet ==1.1.3.3, resourcet ==1.1.3.3,
rest-client ==0.4.0.1, rest-client ==0.4.0.2,
rest-core ==0.33.1.2, rest-core ==0.33.1.2,
rest-gen ==0.16.1.3, rest-gen ==0.16.1.3,
rest-happstack ==0.2.10.3, rest-happstack ==0.2.10.3,
@ -641,12 +661,12 @@ constraints: abstract-deque ==0.3,
setenv ==0.1.1.1, setenv ==0.1.1.1,
SHA ==1.6.4.1, SHA ==1.6.4.1,
shake ==0.14.2, shake ==0.14.2,
shake-language-c ==0.6.2, shake-language-c ==0.6.3,
shakespeare ==2.0.2.1, shakespeare ==2.0.2.1,
shakespeare-i18n ==1.1.0, shakespeare-i18n ==1.1.0,
shakespeare-text ==1.1.0, shakespeare-text ==1.1.0,
shell-conduit ==4.5, shell-conduit ==4.5,
shelly ==1.5.6, shelly ==1.5.7,
silently ==1.2.4.1, silently ==1.2.4.1,
simple-reflect ==0.3.2, simple-reflect ==0.3.2,
simple-sendfile ==0.2.18, simple-sendfile ==0.2.18,
@ -657,16 +677,18 @@ constraints: abstract-deque ==0.3,
smallcheck ==1.1.1, smallcheck ==1.1.1,
smtLib ==1.0.7, smtLib ==1.0.7,
snap ==0.13.3.2, snap ==0.13.3.2,
snap-core ==0.9.6.3, snap-core ==0.9.6.4,
snaplet-fay ==0.3.3.8, snaplet-fay ==0.3.3.8,
snap-server ==0.9.4.5, snap-server ==0.9.4.6,
socks ==0.5.4, socks ==0.5.4,
sodium ==0.11.0.2, sodium ==0.11.0.3,
sourcemap ==0.1.3.0, sourcemap ==0.1.3.0,
speculation ==1.5.0.1, speculation ==1.5.0.1,
sphinx ==0.6.0.1, sphinx ==0.6.0.1,
split ==0.2.2, split ==0.2.2,
Spock ==0.7.5.1, Spock ==0.7.6.0,
Spock-digestive ==0.1.0.0,
Spock-worker ==0.2.1.3,
spoon ==0.3.1, spoon ==0.3.1,
sqlite-simple ==0.4.8.0, sqlite-simple ==0.4.8.0,
stateref ==0.3, stateref ==0.3,
@ -675,7 +697,7 @@ constraints: abstract-deque ==0.3,
statistics-linreg ==0.3, statistics-linreg ==0.3,
stm ==2.4.4, stm ==2.4.4,
stm-chans ==3.0.0.2, stm-chans ==3.0.0.2,
stm-conduit ==2.5.2, stm-conduit ==2.5.3,
stm-containers ==0.2.7, stm-containers ==0.2.7,
stm-stats ==0.2.0.0, stm-stats ==0.2.0.0,
storable-complex ==0.2.1, storable-complex ==0.2.1,
@ -689,11 +711,11 @@ constraints: abstract-deque ==0.3,
stringsearch ==0.3.6.5, stringsearch ==0.3.6.5,
stylish-haskell ==0.5.11.0, stylish-haskell ==0.5.11.0,
SVGFonts ==1.4.0.3, SVGFonts ==1.4.0.3,
syb ==0.4.2, syb ==0.4.3,
syb-with-class ==0.6.1.5, syb-with-class ==0.6.1.5,
system-canonicalpath ==0.2.0.0, system-canonicalpath ==0.2.0.0,
system-fileio ==0.3.16, system-fileio ==0.3.16,
system-filepath ==0.4.13, system-filepath ==0.4.13.1,
system-posix-redirect ==1.1.0.1, system-posix-redirect ==1.1.0.1,
tabular ==0.2.2.5, tabular ==0.2.2.5,
tagged ==0.7.3, tagged ==0.7.3,
@ -722,7 +744,7 @@ constraints: abstract-deque ==0.3,
testing-feat ==0.4.0.2, testing-feat ==0.4.0.2,
testpack ==2.1.3.0, testpack ==2.1.3.0,
texmath ==0.8.0.1, texmath ==0.8.0.1,
text ==1.1.1.3, text ==1.2.0.3,
text-binary ==0.1.0, text-binary ==0.1.0,
text-format ==0.3.1.1, text-format ==0.3.1.1,
text-icu ==0.7.0.0, text-icu ==0.7.0.0,
@ -731,7 +753,7 @@ constraints: abstract-deque ==0.3,
th-expand-syns ==0.3.0.4, th-expand-syns ==0.3.0.4,
th-extras ==0.0.0.2, th-extras ==0.0.0.2,
th-lift ==0.7, th-lift ==0.7,
th-orphans ==0.8.2, th-orphans ==0.8.3,
threads ==0.5.1.2, threads ==0.5.1.2,
th-reify-many ==0.1.2, th-reify-many ==0.1.2,
thyme ==0.3.5.5, thyme ==0.3.5.5,
@ -752,7 +774,7 @@ constraints: abstract-deque ==0.3,
type-eq ==0.4.2, type-eq ==0.4.2,
type-list ==0.0.0.0, type-list ==0.0.0.0,
udbus ==0.2.1, udbus ==0.2.1,
unbounded-delays ==0.1.0.8, unbounded-delays ==0.1.0.9,
union-find ==0.2, union-find ==0.2,
uniplate ==1.6.12, uniplate ==1.6.12,
unix installed, unix installed,
@ -763,7 +785,7 @@ constraints: abstract-deque ==0.3,
url ==2.1.3, url ==2.1.3,
utf8-light ==0.4.2, utf8-light ==0.4.2,
utf8-string ==0.3.8, utf8-string ==0.3.8,
uuid ==1.3.7, uuid ==1.3.8,
vault ==0.3.0.4, vault ==0.3.0.4,
vector ==0.10.12.2, vector ==0.10.12.2,
vector-algorithms ==0.6.0.3, vector-algorithms ==0.6.0.3,
@ -778,21 +800,22 @@ constraints: abstract-deque ==0.3,
wai-app-static ==3.0.0.5, wai-app-static ==3.0.0.5,
wai-conduit ==3.0.0.2, wai-conduit ==3.0.0.2,
wai-eventsource ==3.0.0, wai-eventsource ==3.0.0,
wai-extra ==3.0.3.1, wai-extra ==3.0.3.2,
wai-logger ==2.2.3, wai-logger ==2.2.3,
wai-middleware-static ==0.6.0.1, wai-middleware-static ==0.6.0.1,
wai-websockets ==3.0.0.3, wai-websockets ==3.0.0.3,
warp ==3.0.4.1, warp ==3.0.5,
warp-tls ==3.0.1.1, warp-tls ==3.0.1.1,
webdriver ==0.6.0.3, webdriver ==0.6.0.3,
web-fpco ==0.1.1.0, web-fpco ==0.1.1.0,
websockets ==0.9.2.1, websockets ==0.9.2.2,
wizards ==1.0.1, wizards ==1.0.1,
wl-pprint ==1.1, wl-pprint ==1.1,
wl-pprint-extras ==3.5.0.3, wl-pprint-extras ==3.5.0.3,
wl-pprint-terminfo ==3.7.1.3, wl-pprint-terminfo ==3.7.1.3,
wl-pprint-text ==1.1.0.2, wl-pprint-text ==1.1.0.3,
word8 ==0.1.1, word8 ==0.1.1,
wordpass ==1.0.0.2,
X11 ==1.6.1.2, X11 ==1.6.1.2,
x509 ==1.5.0.1, x509 ==1.5.0.1,
x509-store ==1.5.0, x509-store ==1.5.0,
@ -804,7 +827,7 @@ constraints: abstract-deque ==0.3,
xml-conduit ==1.2.3.1, xml-conduit ==1.2.3.1,
xmlgen ==0.6.2.1, xmlgen ==0.6.2.1,
xml-hamlet ==0.4.0.9, xml-hamlet ==0.4.0.9,
xmlhtml ==0.2.3.3, xmlhtml ==0.2.3.4,
xml-types ==0.3.4, xml-types ==0.3.4,
xss-sanitize ==0.3.5.4, xss-sanitize ==0.3.5.4,
yackage ==0.7.0.6, yackage ==0.7.0.6,
@ -812,11 +835,12 @@ constraints: abstract-deque ==0.3,
Yampa ==0.9.6, Yampa ==0.9.6,
YampaSynth ==0.2, YampaSynth ==0.2,
yesod ==1.4.1.3, yesod ==1.4.1.3,
yesod-auth ==1.4.1.1, yesod-auth ==1.4.1.2,
yesod-auth-deskcom ==1.4.0, yesod-auth-deskcom ==1.4.0,
yesod-auth-fb ==1.6.6, yesod-auth-fb ==1.6.6,
yesod-auth-hashdb ==1.4.1.1, yesod-auth-hashdb ==1.4.1.2,
yesod-bin ==1.4.3.1, yesod-auth-oauth2 ==0.0.11,
yesod-bin ==1.4.3.2,
yesod-core ==1.4.7.1, yesod-core ==1.4.7.1,
yesod-eventsource ==1.4.0.1, yesod-eventsource ==1.4.0.1,
yesod-fay ==0.7.0, yesod-fay ==0.7.0,
@ -827,7 +851,7 @@ constraints: abstract-deque ==0.3,
yesod-persistent ==1.4.0.2, yesod-persistent ==1.4.0.2,
yesod-sitemap ==1.4.0.1, yesod-sitemap ==1.4.0.1,
yesod-static ==1.4.0.4, yesod-static ==1.4.0.4,
yesod-test ==1.4.2.1, yesod-test ==1.4.2.2,
yesod-text-markdown ==0.1.7, yesod-text-markdown ==0.1.7,
yesod-websockets ==0.2.1.1, yesod-websockets ==0.2.1.1,
zeromq4-haskell ==0.6.2, zeromq4-haskell ==0.6.2,

View File

@ -5,5 +5,5 @@ stanzas:
- production - production
env: env:
STACKAGE_CABAL_LOADER: "0" STACKAGE_CABAL_LOADER: "0"
STACKAGE_HOOGLE_GEN: "0"
host: www.stackage.org host: www.stackage.org
copy-to: fpuser@www.stackage.org:/var/opt/keter/incoming

View File

@ -134,3 +134,7 @@ Suggested
package PackageName package PackageName
insteadOf PackageName insteadOf PackageName
UniqueSuggested package insteadOf UniqueSuggested package insteadOf
UploadProgress
message Text
dest Text Maybe

View File

@ -1 +1,2 @@
User-agent: * User-agent: *
Disallow: /haddock/

View File

@ -25,12 +25,13 @@
/package/#PackageNameVersion StackageSdistR GET /package/#PackageNameVersion StackageSdistR GET
/packages SnapshotPackagesR GET /packages SnapshotPackagesR GET
/docs DocsR GET /docs DocsR GET
/hoogle HoogleR GET
/hackage-view/#HackageView/00-index.tar.gz HackageViewIndexR GET /hackage-view/#HackageView/00-index.tar.gz HackageViewIndexR GET
/hackage-view/#HackageView/package/#PackageNameVersion HackageViewSdistR GET /hackage-view/#HackageView/package/#PackageNameVersion HackageViewSdistR GET
/aliases AliasesR PUT /aliases AliasesR PUT
/alias/#Slug/#Slug/*Texts AliasR /alias/#Slug/#Slug/*Texts AliasR
/progress/#Int ProgressR GET /progress/#UploadProgressId ProgressR GET
/system SystemR GET /system SystemR GET
/haddock/#SnapSlug/*Texts HaddockR GET /haddock/#SnapSlug/*Texts HaddockR GET
/package/#PackageName PackageR GET /package/#PackageName PackageR GET
@ -54,3 +55,5 @@
/refresh-deprecated RefreshDeprecatedR GET /refresh-deprecated RefreshDeprecatedR GET
/upload2 UploadV2R PUT /upload2 UploadV2R PUT
/build-version BuildVersionR GET
/package-counts PackageCountsR GET

8
fpbuild.config Normal file
View File

@ -0,0 +1,8 @@
docker:
repo-suffix: "_ghc-7.8.4.20141229_stackage-lts-1.0"
image-tag: "20150101"
# For fpbuild <= 0.1.0
registry-username: "dummy"
registry-password: "no-auth-required"
packages:
- "."

View File

@ -27,6 +27,7 @@ library
Data.Hackage.DeprecationInfo Data.Hackage.DeprecationInfo
Data.Hackage.Views Data.Hackage.Views
Data.WebsiteContent Data.WebsiteContent
Data.Unpacking
Types Types
Handler.Home Handler.Home
Handler.Snapshots Handler.Snapshots
@ -44,6 +45,7 @@ library
Handler.Progress Handler.Progress
Handler.System Handler.System
Handler.Haddock Handler.Haddock
Handler.Hoogle
Handler.Package Handler.Package
Handler.PackageList Handler.PackageList
Handler.CompressorStatus Handler.CompressorStatus
@ -51,6 +53,8 @@ library
Handler.BannedTags Handler.BannedTags
Handler.RefreshDeprecated Handler.RefreshDeprecated
Handler.UploadV2 Handler.UploadV2
Handler.BuildVersion
Handler.PackageCounts
if flag(dev) || flag(library-only) if flag(dev) || flag(library-only)
cpp-options: -DDEVELOPMENT cpp-options: -DDEVELOPMENT
@ -83,6 +87,7 @@ library
ScopedTypeVariables ScopedTypeVariables
BangPatterns BangPatterns
TupleSections TupleSections
DeriveGeneric
build-depends: build-depends:
base >= 4 base >= 4
@ -150,9 +155,14 @@ library
, formatting , formatting
, blaze-html , blaze-html
, haddock-library , haddock-library
, yesod-gitrepo
, async , async
, stackage >= 0.4 , stackage >= 0.4
, yesod-gitrepo >= 0.1.1
, hoogle
, spoon
, deepseq
, deepseq-generics
, auto-update
executable stackage-server executable stackage-server
if flag(library-only) if flag(library-only)

View File

@ -1,8 +1,9 @@
<h1>Module listing for #{toPathPiece slug} <div .container>
<p> <h1>Module listing for #{toPathPiece slug}
<a href=@{SnapshotR slug StackageHomeR}>Return to snapshot <p>
<ul> <a href=@{SnapshotR slug StackageHomeR}>Return to snapshot
$forall (name, url, package, version) <- modules <ul>
<li> $forall (name, url, package, version) <- modules
<a href=#{url}>#{name} <li>
(#{package}-#{version}) <a href=#{url}>#{name}
(#{package}-#{version})

View File

@ -0,0 +1,6 @@
<form .hoogle action=@{SnapshotR slug HoogleR}>
<input type=search autofocus name=q value=#{queryText} placeholder="Hoogle Search Phrase" .search>
<input .btn type="submit" value="Search">
<label .checkbox .exact-lookup for=exact title="Only find identifiers matching your search term precisely">
<input type=checkbox name=exact :exact:checked #exact>
Exact lookup

View File

@ -0,0 +1,14 @@
form.hoogle {
margin-bottom: 20px;
.search {
width: 25em;
}
input {
margin-bottom: 0;
}
}
.exact-lookup {
display: inline-block;
margin-left: 1em;
}

39
templates/hoogle.hamlet Normal file
View File

@ -0,0 +1,39 @@
<div .container>
<div .content>
<h1>Hoogle Search (experimental)
<p>Within <a href=@{snapshotLink}>#{stackageTitle stackage}</a>
^{hoogleForm}
$case mresults
$of HoogleQueryBad err
<p>#{err}
<p>For information on what queries should look like, see the <a href="http://www.haskell.org/haskellwiki/Hoogle">hoogle user manual</a>.
$of HoogleQueryOutput _query results mtotalCount
$if null results
<p>Your search produced no results.
$else
<ol .search-results>
$forall HoogleResult url sources self docs <- results
<li>
<p .self>
<a href=#{url}>#{preEscapedToHtml self}
<table .sources>
$forall (pkg, modus) <- sources
<tr>
<th>
<a href=#{plURL pkg}>#{plName pkg}
<td>
$forall ModuleLink name url' <- modus
<a href=#{url'}>#{name}
$if null docs
<p .nodocs>No documentation available.
$else
<p .docs>#{docs}
<p .pagination>
$with mpageCount <- fmap getPageCount mtotalCount
Page #{page} of #{maybe "many" show mpageCount} #
$if page > 1
|
<a href=@?{pageLink $ page - 1}>Previous
$if maybe True ((<) page) mpageCount
|
<a href=@?{pageLink $ page + 1}>Next

5
templates/hoogle.julius Normal file
View File

@ -0,0 +1,5 @@
$(function() {
var input = $(".hoogle .search").get(0);
var len = input.value.length;
input.setSelectionRange(len, len);
})

63
templates/hoogle.lucius Normal file
View File

@ -0,0 +1,63 @@
ol.search-results {
margin: 0;
padding: 0;
list-style: none;
}
.self {
margin-bottom: 0;
/* Use bold instead of italics to indicate matching part of search */
a {
b {
font-weight: normal;
}
i {
font-weight: bold;
font-style: normal;
}
}
}
table.sources {
margin: 0;
padding: 0;
font-size: 0.8em;
th {
padding-right: 0.5em;
}
a, a:visited {
color: #060;
}
}
.docs {
white-space: pre-wrap;
background: #e8e8e8;
}
.docs, .nodocs {
margin-left: 1em;
padding: 0.5em;
}
.nodocs {
font-style: italic;
}
.haddocks {
font-weight: bold;
margin-bottom: 1em;
ul {
display: inline;
padding: 0;
margin: 0;
}
li {
display: inline-block;
font-weight: normal;
margin-left: 1em;
}
}

View File

@ -0,0 +1,20 @@
<div .container>
<h1>Package counts
<p>
This page provides historical information on the number of packages included
in Stackage Nightly and LTS Haskell snapshots, purely for the sake of
curiosity.
<table>
<thead>
<tr>
<th .name>Title
<th .count>Count
<th .date>Date
<tbody>
$forall c <- counts
<tr>
<td .name>#{name c}
<td .count>#{packages c}
<td .date>#{show $ date c}

View File

@ -0,0 +1,12 @@
th {
font-size: 1.2em;
}
td, th {
padding: 0.5em;
}
.name {
text-align: right;
font-weight: bold;
}

View File

@ -57,8 +57,6 @@ h3 {
border-top: 1px solid #ddd; border-top: 1px solid #ddd;
padding-top: 0.5em; padding-top: 0.5em;
ul { ul {
max-height: 20ex;
overflow: auto;
list-style-type: none; list-style-type: none;
margin-left: 0; margin-left: 0;
padding-left: 0; padding-left: 0;

View File

@ -37,8 +37,13 @@ $newline never
<a href=@{SnapshotR slug StackageCabalConfigR}?global=true> <a href=@{SnapshotR slug StackageCabalConfigR}?global=true>
the global configuration instructions the global configuration instructions
<h3> <h3>Hoogle (experimental)
Packages ^{hoogleForm}
<h3>Packages
<p>
<a href=@{SnapshotR slug DocsR}>View documentation by modules
<div .container .content> <div .container .content>
<div .packages> <div .packages>