-- | HTML output for documentation package index. module Distribution.DocIdx.Html ( htmlPage ) where import Control.Monad import Data.Char (isAlpha, toUpper) import Data.List import Data.Ord import Data.Time import Data.Version import qualified Data.Map as M import System.FilePath import System.Locale import Text.Html import Distribution.DocIdx.Common import Distribution.DocIdx.Config import Distribution.GhcPkgList -- | Project homepage, for footer. homePage :: String homePage = "http://hackage.haskell.org/package/docidx" -- | Create and render entire page. htmlPage :: DocIdxCfg -> PackageMap HaddockInfo -> UTCTime -> String htmlPage config pkgs now = renderHtml [htmlHeader, htmlBody] where htmlHeader = header << ((thetitle << pageTitle config) : fav : css) fav = thelink ![rel "shortcut icon", href $ favIcon config] << noHtml css = map oneCss (pageCss config) oneCss cp = thelink ![rel "stylesheet", thetype "text/css", href cp] << noHtml htmlBody = body << (title' ++ toc ++ secs ++ nowFoot) where title' = [h2 << "Local packages with docs"] toc = [htmlToc config am] secs = concatMap (uncurry htmlPkgsAlpha) $ M.assocs am am = alphabetize pkgs now' = formatTime defaultTimeLocale rfc822DateFormat now nowFoot = [p ![theclass "toc"] $ stringToHtml ("Page rendered " ++ now' ++ " by ") +++ (anchor ![href homePage] << stringToHtml appName)] -- | An AlphaMap groups packages together by their name's first character. type AlphaMap = M.Map Char (PackageMap HaddockInfo) -- | Group packages together by their name's first character. alphabetize :: PackageMap HaddockInfo -> AlphaMap alphabetize = foldr addAlpha M.empty where addAlpha (n, vs) = M.insertWith (++) c [(n, vs)] where c = if isAlpha c' then c' else '\0' c' = toUpper $ head n -- | Generate the table of contents. htmlToc :: DocIdxCfg -> AlphaMap -> Html htmlToc config am = p ![theclass "toc"] << tocHtml (alphaItems ++ tocExtras config) where tocHtml = intersperse bull . concatMap tocItemHtml alphaItems = map (\k -> TocItem [k] ('#':[k])) $ sort $ M.keys am -- | Render toc elements to HTML. tocItemHtml :: TocItem -> [Html] tocItemHtml (TocItem nm path) = [anchor ![href path] << nm] tocItemHtml TocSeparator = [mdash] tocItemHtml TocNewline = [br] -- Hmmm... you still get the bullets? -- | Render a collection of packages with the same first character. htmlPkgsAlpha :: Char -> PackageMap HaddockInfo -> [Html] htmlPkgsAlpha c pm = [heading, packages] where heading = h3 ![theclass "category"] << anchor ![name [c]] << [c] packages = ulist ![theclass "packages"] << map (uncurry htmlPkg) pm' pm' = sortBy (comparing (map toUpper . fst)) pm -- | Render a particularly-named package (all versions of it). htmlPkg :: String -> VersionMap HaddockInfo -> Html htmlPkg nm vs = li << pvsHtml (flattenPkgVersions nm vs) -- | Everything we want to know about a particular version of a -- package, nicely flattened and ready to use. (Actually, we'd also -- like to use the synopsis, but this isn't exposed through the Cabal -- library, sadly. We could conceivably grab it from the haddock docs -- (and hackage for packages with no local docs) but this -- seems excessive so for now we forget about it. data PkgVersion = PkgVersion { pvName ::String , pvSynopsis :: Maybe String , pvVersion :: Version , pvExposed :: Bool , pvHaddocks :: Maybe FilePath } deriving (Eq, Ord, Show) -- | Flatten a given package's various versions into a list of -- PkgVersion values, which is much nicer to iterate over when -- building the HTML for this package. flattenPkgVersions :: String -> VersionMap HaddockInfo -> [PkgVersion] flattenPkgVersions nm vs = concatMap (uncurry flatten') $ reverse vs where flatten' :: Version -> [VersionInfo HaddockInfo] -> [PkgVersion] -- We reverse here to put user versions of pkgs before -- identically versioned global versions. flatten' v = concatMap (uncurry flatten3) . reverse where flatten3 :: Bool -> [HaddockInfo] -> [PkgVersion] flatten3 ex [] = [PkgVersion nm Nothing v ex Nothing] flatten3 ex ps = map (mkPv nm v ex) ps -- | Construct a PkgVersion from information about a single version of -- a package. mkPv :: String -> Version -> Bool -> HaddockInfo -> PkgVersion mkPv nm v ex Nothing = PkgVersion nm Nothing v ex Nothing mkPv nm v ex (Just (hp, syn)) = PkgVersion nm (Just syn) v ex (Just hp) -- | Render the HTML for a list of versions of (we presume) the same -- package. pvsHtml :: [PkgVersion] -> Html pvsHtml pvs = pvHeader (head pvs) +++ spaceHtml +++ pvVersions pvs +++ pvSyn pvs -- | Render the "header" part of some package's HTML: name (with link -- to default version of local docs if available) and hackage link. pvHeader :: PkgVersion -> [Html] pvHeader pv = [maybeURL nme (pvHaddocks pv) ,spaceHtml ,anchor ![href $ hackagePath pv] << extLinkArrow ] where nme = if not (pvExposed pv) then "(" ++ nm ++ ")" else nm nm = pvName pv -- | Render HTML linking to the various versions of a package -- installed, listed by version number only (name is implicit). pvVersions :: [PkgVersion] -> Html pvVersions [_] = noHtml -- Don't bother if there's only one version. pvVersions pvs = stringToHtml "[" +++ intersperse comma (map pvOneVer pvs) +++ stringToHtml "]" where pvOneVer pv = maybeURL (showVersion $ pvVersion pv) (pvHaddocks pv) -- | Render the synopsis of a package, if present in any of its versions. pvSyn :: [PkgVersion] -> Html pvSyn = maybe noHtml (\x -> mdash +++ stringToHtml x) . msum . map pvSynopsis -- | Render a URL if there's a path; otherwise, just render some text. -- (Useful in cases where a package is installed but no documentation -- was found: you'll still get the hackage link.) maybeURL :: String -> Maybe String -> Html maybeURL nm Nothing = stringToHtml nm maybeURL nm (Just path) = anchor ![href $ joinPath [path, "index.html"]] << nm -- | Compute the URL to a package's page on hackage. hackagePath :: PkgVersion -> String hackagePath pv = "http://hackage.haskell.org/package/" ++ pvTag where pvTag = pvName pv ++ "-" ++ showVersion (pvVersion pv) -- Some primitives. bull, comma, extLinkArrow, mdash :: Html bull = primHtml " • " comma = stringToHtml ", " extLinkArrow = primHtml "⬈" mdash = primHtml " — "