-- Body of the HTML page for a package
{-# LANGUAGE PatternGuards, RecordWildCards #-}
module Distribution.Server.Pages.Package (
packagePage,
renderDependencies,
renderVersion,
renderFields,
renderDownloads
) where
import Distribution.Server.Features.PreferredVersions
import Distribution.Server.Pages.Template (hackagePageWith)
import Distribution.Server.Pages.Package.HaddockParse (parseHaddockParagraphs)
import Distribution.Server.Pages.Package.HaddockLex (tokenise)
import Distribution.Server.Pages.Package.HaddockHtml
import Distribution.Server.Packages.ModuleForest
import Distribution.Server.Packages.Render
import Distribution.Server.Users.Types (userStatus, userName, isActiveAccount)
import Distribution.Package
import Distribution.PackageDescription as P
import Distribution.Simple.Utils ( cabalVersion )
import Distribution.Version
import Distribution.Text (display)
import Text.XHtml.Strict hiding (p, name, title, content)
import Data.Maybe (maybeToList)
import Data.List (intersperse, intercalate)
import System.FilePath.Posix ((>), (<.>))
import System.Locale (defaultTimeLocale)
import Data.Time.Format (formatTime)
packagePage :: PackageRender -> [Html] -> [Html] -> [(String, Html)] -> [(String, Html)] -> Maybe URL -> Html
packagePage render headLinks top sections bottom docURL =
hackagePageWith [] docTitle docSubtitle docBody [docFooter]
where
pkgid = rendPkgId render
docTitle = display (packageName pkgid)
++ case synopsis (rendOther render) of
"" -> ""
short -> ": " ++ short
docSubtitle = toHtml docTitle
docBody =
h1 <<
bodyTitle :
concat [
renderHeads,
top,
pkgBody render sections,
moduleSection render docURL,
downloadSection render,
maintainerSection pkgid,
map pair bottom
]
bodyTitle = "The " ++ display (pkgName pkgid) ++ " package"
renderHeads = case headLinks of
[] -> []
items -> [thediv ! [thestyle "font-size: small"] <<
(map (\item -> "[" +++ item +++ "] ") items)]
docFooter = thediv ! [identifier "footer"]
<< paragraph
<< [ toHtml "Produced by "
, anchor ! [href "/"] << "hackage"
, toHtml " and "
, anchor ! [href cabalHomeURL] << "Cabal"
, toHtml (" " ++ display cabalVersion) ]
pair (title, content) =
toHtml [ h2 << title, content ]
-- | Body of the package page
pkgBody :: PackageRender -> [(String, Html)] -> [Html]
pkgBody render sections =
prologue (description $ rendOther render) ++
propertySection sections
prologue :: String -> [Html]
prologue [] = []
prologue desc = case tokenise desc >>= parseHaddockParagraphs of
Left _ -> [paragraph << p | p <- paragraphs desc]
Right doc -> [markup htmlMarkup doc]
-- Break text into paragraphs (separated by blank lines)
paragraphs :: String -> [String]
paragraphs = map unlines . paras . lines
where paras xs = case dropWhile null xs of
[] -> []
xs' -> case break null xs' of
(para, xs'') -> para : paras xs''
downloadSection :: PackageRender -> [Html]
downloadSection PackageRender{..} =
[ h2 << "Downloads"
, ulist << map (li <<) downloadItems
]
where
downloadItems =
[ if rendHasTarball
then [ anchor ! [href downloadURL] << tarGzFileName
, toHtml << " ["
, anchor ! [href srcURL] << "browse"
, toHtml << "]"
, toHtml << " (Cabal source package)"
]
else [ toHtml << "Package tarball not uploaded" ]
, [ anchor ! [href cabalURL] << "Package description"
, toHtml $ if rendHasTarball then " (included in the package)" else ""
]
, case (rendHasTarball, rendHasChangeLog) of
(True, True) -> [ anchor ! [href changeLogURL] << "Changelog"
, toHtml << " (included in the package)"
]
(True, False) -> [ toHtml << "No changelog available" ]
_ -> [ toHtml << "Package tarball not uploaded" ]
]
downloadURL = rendPkgUri > display rendPkgId <.> "tar.gz"
cabalURL = rendPkgUri > display (packageName rendPkgId) <.> "cabal"
changeLogURL = rendPkgUri > "changelog"
srcURL = rendPkgUri > "src/"
tarGzFileName = display rendPkgId ++ ".tar.gz"
maintainerSection :: PackageId -> [Html]
maintainerSection pkgid =
[ h4 << "Maintainers' corner"
, paragraph << "For package maintainers and hackage trustees"
, ulist << li << anchor ! [href maintainURL]
<< "edit package information"
]
where
maintainURL = display (packageName pkgid) > "maintain"
moduleSection :: PackageRender -> Maybe URL -> [Html]
moduleSection render docURL = maybeToList $ fmap msect (rendModules render)
where msect lib = toHtml
[ h2 << "Modules"
, renderModuleForest docURL lib
]
propertySection :: [(String, Html)] -> [Html]
propertySection sections =
[ h2 << "Properties"
, tabulate $ filter (not . isNoHtml . snd) sections
]
tabulate :: [(String, Html)] -> Html
tabulate items = table <<
[tr << [th ! [align "left", valign "top"] << t, td << d] | (t, d) <- items]
renderDependencies :: PackageRender -> (String, Html)
renderDependencies render = ("Dependencies", case htmlDepsList of
[] -> toHtml "None"
_ -> foldr (+++) noHtml htmlDepsList)
where htmlDepsList =
intersperse (toHtml " " +++ bold (toHtml "or") +++ br) $
map showDependencies (rendDepends render)
showDependencies :: [Dependency] -> Html
showDependencies deps = commaList (map showDependency deps)
showDependency :: Dependency -> Html
showDependency (Dependency (PackageName pname) vs) = showPkg +++ vsHtml
where vsHtml = if vs == anyVersion then noHtml
else toHtml (" (" ++ display vs ++ ")")
-- mb_vers links to latest version in range. This is a bit computationally
-- expensive, not cache-friendly, and perhaps unexpected in some cases
{-mb_vers = maybeLast $ filter (`withinRange` vs) $ map packageVersion $
PackageIndex.lookupPackageName vmap (PackageName pname)-}
-- nonetheless, we should ensure that the package exists /before/
-- passing along the PackageRender, which is not the case here
showPkg = anchor ! [href . packageURL $ PackageIdentifier (PackageName pname) (Version [] [])] << pname
renderVersion :: PackageId -> [(Version, VersionStatus)] -> Maybe String -> (String, Html)
renderVersion (PackageIdentifier pname pversion) allVersions info =
(if null earlierVersions && null laterVersions then "Version" else "Versions", versionList +++ infoHtml)
where (earlierVersions, laterVersionsInc) = span (( (Just v, later)
later -> (Nothing, later)
versionList = commaList $ map versionedLink earlierVersions
++ (case pversion of
Version [] [] -> []
_ -> [strong ! (maybe [] (status . snd) mThisVersion) << display pversion]
)
++ map versionedLink laterVersions
versionedLink (v, s) = anchor ! (status s ++ [href $ packageURL $ PackageIdentifier pname v]) << display v
status st = case st of
NormalVersion -> []
DeprecatedVersion -> [theclass "deprecated"]
UnpreferredVersion -> [theclass "unpreferred"]
infoHtml = case info of Nothing -> noHtml; Just str -> " (" +++ (anchor ! [href str] << "info") +++ ")"
-- We don't keep currently per-version downloads in memory; if we decide that
-- it is important to show this all the time, we can reenable
renderDownloads :: Int -> {- Int -> Version -> -} (String, Html)
renderDownloads totalDown {- versionDown version -} =
("Downloads", toHtml $ {- show versionDown ++ " for " ++ display version ++
" and " ++ -} show totalDown ++ " total")
renderFields :: PackageRender -> [(String, Html)]
renderFields render = [
-- Cabal-Version
("License", toHtml $ rendLicenseName render),
("Copyright", toHtml $ P.copyright desc),
("Author", toHtml $ author desc),
("Maintainer", maintainField $ rendMaintainer render),
("Stability", toHtml $ stability desc),
("Category", commaList . map categoryField $ rendCategory render),
("Home page", linkField $ homepage desc),
("Bug tracker", linkField $ bugReports desc),
("Source repository", vList $ map sourceRepositoryField $ sourceRepos desc),
("Executables", commaList . map toHtml $ rendExecNames render),
("Upload date", toHtml $ showTime utime),
("Uploaded by", userField)
]
where desc = rendOther render
(utime, uinfo) = rendUploadInfo render
uname = maybe "Unknown" (display . userName) uinfo
uactive = maybe False (isActiveAccount . userStatus) uinfo
userField | uactive = anchor ! [href $ "/user/" ++ uname] << uname
| otherwise = toHtml uname
linkField url = case url of
[] -> noHtml
_ -> anchor ! [href url] << url
categoryField cat = anchor ! [href $ "/packages/#cat:" ++ cat] << cat
maintainField mnt = case mnt of
Nothing -> strong ! [theclass "warning"] << toHtml "none"
Just n -> toHtml n
showTime = formatTime defaultTimeLocale "%c"
sourceRepositoryField sr = sourceRepositoryToHtml sr
sourceRepositoryToHtml :: SourceRepo -> Html
sourceRepositoryToHtml sr
= toHtml (display (repoKind sr) ++ ": ")
+++ case repoType sr of
Just Darcs
| (Just url, Nothing, Nothing) <-
(repoLocation sr, repoModule sr, repoBranch sr) ->
concatHtml [toHtml "darcs get ",
anchor ! [href url] << toHtml url,
case repoTag sr of
Just tag' -> toHtml (" --tag " ++ tag')
Nothing -> noHtml,
case repoSubdir sr of
Just sd -> toHtml " ("
+++ (anchor ! [href (url > sd)]
<< toHtml sd)
+++ toHtml ")"
Nothing -> noHtml]
Just Git
| (Just url, Nothing) <-
(repoLocation sr, repoModule sr) ->
concatHtml [toHtml "git clone ",
anchor ! [href url] << toHtml url,
case repoBranch sr of
Just branch -> toHtml (" -b " ++ branch)
Nothing -> noHtml,
case repoTag sr of
Just tag' -> toHtml ("(tag " ++ tag' ++ ")")
Nothing -> noHtml,
case repoSubdir sr of
Just sd -> toHtml ("(" ++ sd ++ ")")
Nothing -> noHtml]
Just SVN
| (Just url, Nothing, Nothing, Nothing) <-
(repoLocation sr, repoModule sr, repoBranch sr, repoTag sr) ->
concatHtml [toHtml "svn checkout ",
anchor ! [href url] << toHtml url,
case repoSubdir sr of
Just sd -> toHtml ("(" ++ sd ++ ")")
Nothing -> noHtml]
Just CVS
| (Just url, Just m, Nothing, Nothing) <-
(repoLocation sr, repoModule sr, repoBranch sr, repoTag sr) ->
concatHtml [toHtml "cvs -d ",
anchor ! [href url] << toHtml url,
toHtml (" " ++ m),
case repoSubdir sr of
Just sd -> toHtml ("(" ++ sd ++ ")")
Nothing -> noHtml]
Just Mercurial
| (Just url, Nothing, Nothing, Nothing) <-
(repoLocation sr, repoModule sr, repoBranch sr, repoTag sr) ->
concatHtml [toHtml "hg clone ",
anchor ! [href url] << toHtml url,
case repoSubdir sr of
Just sd -> toHtml ("(" ++ sd ++ ")")
Nothing -> noHtml]
Just Bazaar
| (Just url, Nothing, Nothing) <-
(repoLocation sr, repoModule sr, repoBranch sr) ->
concatHtml [toHtml "bzr branch ",
anchor ! [href url] << toHtml url,
case repoTag sr of
Just tag' -> toHtml (" -r " ++ tag')
Nothing -> noHtml,
case repoSubdir sr of
Just sd -> toHtml ("(" ++ sd ++ ")")
Nothing -> noHtml]
_ ->
-- We don't know how to show this SourceRepo.
-- This is a kludge so that we at least show all the info.
toHtml (show sr)
commaList :: [Html] -> Html
commaList = concatHtml . intersperse (toHtml ", ")
vList :: [Html] -> Html
vList = concatHtml . intersperse br
-----------------------------------------------------------------------------
renderModuleForest :: Maybe URL -> ModuleForest -> Html
renderModuleForest mb_url forest =
thediv ! [identifier "module-list"] << renderForest [] forest
where
renderForest _ [] = noHtml
renderForest pathRev ts = myUnordList $ map renderTree ts
where
renderTree (Node s isModule subs) =
( if isModule then moduleEntry newPath else italics << s )
+++ renderForest newPathRev subs
where
newPathRev = s:pathRev
newPath = reverse newPathRev
moduleEntry path =
thespan ! [theclass "module"] << maybe modName linkedName mb_url path
modName path = toHtml (intercalate "." path)
linkedName url path = anchor ! [href modUrl] << modName path
where
modUrl = url ++ "/" ++ intercalate "-" path ++ ".html"
myUnordList :: HTML a => [a] -> Html
myUnordList = unordList ! [theclass "modules"]
------------------------------------------------------------------------------
-- TODO: most of these should be available from the CoreFeature
-- so pass it in to this module
-- | URL describing a package.
packageURL :: PackageIdentifier -> URL
packageURL pkgId = "/package" > display pkgId
--cabalLogoURL :: URL
--cabalLogoURL = "/built-with-cabal.png"
-- global URLs
cabalHomeURL :: URL
cabalHomeURL = "http://haskell.org/cabal/"