hask-home-2007.12.6: hask-home.hs
#!/usr/bin/env runghc
-- Generate a homepage for a darcsized cabalized Haskell package.
-- NOTE: this is very hack, making lots of assumptions and
-- with crazy path stuff everywhere. I should clean this up.
import Control.Exception
import Control.Monad
import Data.Char
import Data.List
import Data.Maybe
import Distribution.PackageDescription
import Distribution.Simple
import Distribution.Simple.Utils
import Distribution.Verbosity
import Network.URI
import Prelude hiding (catch)
import System.Directory
import System.Environment
import System.Exit
import System.IO
import System.Cmd
import Text.Regex
import Text.XHtml
import Text.HMarkup
-- These paths are all relative to the root of the darcs repo.
docDir = "doc"
downloadDir = "download"
haddockDir = docDir ++ "/" ++ "api"
indexFile = docDir ++ "/" ++ "index.html"
htaccessFile = downloadDir ++ "/" ++ ".htaccess"
-- packages that we don't need to list as requirements
standardPackages = ["base","stm","mtl","fgl","QuickCheck",
"Cabal","network","readline","unix","parsec",
"haskell98","posix","html"]
-- Packages whose homepages we know
knownPackages = [("fps",("FastPackedString","http://www.cse.unsw.edu.au/~dons/fps.html")),
("Crypto",("The Haskell Cryptographic Library","http://haskell.org/crypto/")),
("HTTP",("The Haskell HTTP package","http://haskell.org/http/")),
("XmlRpc",("HaXR - the Haskell XML-RPC library","http://haskell.org/haxr/")),
("xhtml",("Text.XHtml","http://www.cs.chalmers.se/~bringert/darcs/haskell-xhtml/doc/")),
("cgi-compat",("cgi-compat","http://www.cs.chalmers.se/~bringert/darcs/cgi-compat/doc/")),
("haskelldb",("HaskellDB","http://haskelldb.sourceforge.net/")),
("parsedate",("parsedate","http://www.cs.chalmers.se/~bringert/darcs/parsedate/doc/")),
("hmarkup",("hmarkup","http://www.cs.chalmers.se/~bringert/darcs/hmarkup/doc/"))
]
stylesheet = unlines $
[
"body { background-color: white; color: black; margin: 0; padding: 0; }",
"h1, .footer { background-color:silver; color: black; margin: 0; border: 0 solid black; }",
"h1 { border-bottom-width: thin; padding: 1em; }",
".footer { font-size: smaller; text-align:center; border-top-width: thin; padding: 0.25em 1em; }",
".footer span { padding: 0 0.25em; } ",
"hr { display: none; }",
".section { padding: 0; margin: 0 5em; }"
]
txt2html :: String -> IO String
txt2html s = do r <- markupToHtml defaultMarkupXHtmlPrefs s
case r of
Left err -> fail err
Right h -> return $ renderHtml h
buildHaddock :: PackageDescription -> IO ()
buildHaddock desc =
do
showExceptions $ withArgs ["haddock","-v"] $ defaultMainNoRead desc
rawSystem "rm" ["-rf", haddockDir]
rawSystem "cp" ["-r", "dist/doc/html", haddockDir]
return ()
systemOrFail :: String -> IO ()
systemOrFail cmd =
do
e <- system cmd
case e of
ExitSuccess -> return ()
ExitFailure i -> do hPutStrLn stderr $ "Command failed with status " ++ show i
++ ": " ++ cmd
exitWith e
readFileOrNull :: FilePath -> IO String
readFileOrNull f =
do e <- doesFileExist f
if e then readFile f
else do hPutStrLn stderr $ f ++ " not found, skipping"
return ""
match :: String -> String -> Bool
match p s = isJust $ matchRegex (mkRegex p) s
distDir :: PackageDescription -> String
distDir desc = showPackageId (package desc)
distFile :: PackageDescription -> String
distFile desc = distDir desc ++ ".tar.gz"
latestDistFile :: PackageDescription -> String
latestDistFile desc = pkgName (package desc) ++ "-latest.tar.gz"
fileURI :: PackageDescription -> String -> URI
fileURI desc f = fromJust $ (nullURI { uriPath = f }) `relativeTo` darcsURI desc
linkFile :: HTML a => PackageDescription -> String -> a -> Html
linkFile desc f x = hlink (show $ fileURI desc f `relativeFrom` homepageURI desc) << x
-- FIXME: gigantic hack
darcsURI :: PackageDescription -> URI
darcsURI desc = home { uriPath = reverse $ drop (length docDir) $ dropWhile (=='/') $ reverse $ uriPath home }
where home = homepageURI desc
homepageURI :: PackageDescription -> URI
homepageURI desc = fromMaybe (error $ "Package homepage is not a valid URI: " ++ homepage desc) $ parseURI $ homepage desc
mkTarball :: PackageDescription -> IO ()
mkTarball desc =
do
system ("darcs dist --dist-name=" ++ distDir desc)
createDirectoryIfMissing True downloadDir
let f = downloadDir ++ "/" ++ distFile desc
renameFile (distFile desc) f
makeIndex :: PackageDescription -> String -> String -> Html
makeIndex desc setupProg readme = (header << hdr) +++ (body << bdy)
where
hdr = [thetitle << t,
meta ! [name "generator", content "hask-home, http://www.cs.chalmers.se/~bringert/darcs/hask-home/doc/"],
style ! [thetype "text/css"] << stylesheet
]
t = pkgName (package desc) ++ " - " ++ synopsis desc
bdy = [h1 << t, des, api, dow, req, ins, mai, lic, foo]
des = section "Description" [primHtml readme]
api | not (isLibrary desc) = noHtml
| otherwise = section "API Documentation"
[p << linkFile desc (haddockDir ++ "/" ++ "index.html")
<< "Haddock-generated API documentation"]
dow = section "Download"
([h3 << "Darcs", pre << ("$ darcs get --partial " ++ show (darcsURI desc))]
++ [h3 << "Tarball",
p << ("Latest release: "
+++ linkFile desc (downloadDir ++ "/" ++ distFile desc) (distFile desc)),
p << ("You can also use "
+++ linkFile desc (downloadDir ++ "/" ++ latestDistFile desc) (latestDistFile desc)
+++ " which should always redirect you to the latest release tarball.")])
req | null reqs = noHtml
| otherwise = section "Requirements" [ulist << reqs]
reqs = catMaybes $ map formatReq (buildDepends desc)
formatReq d@(Dependency p v)
| p `elem` standardPackages = Nothing
| otherwise = Just $ case lookup p knownPackages of
Just (n,u) -> li << hlink u n
Nothing -> li << p
ins = section "Installation"
[olist << [li << ("Unpack the sources and enter the source directory:"
+++ pre << [unlines ["$ tar -zxf " ++ distFile desc,
"$ cd " ++ distDir desc]]),
li << ("Configure:"
+++ pre << [unlines ["$ runghc " ++ setupProg ++ " configure"]]),
li << ("Build:"
+++ pre << [unlines ["$ runghc " ++ setupProg ++ " build"]]),
if isLibrary desc then
li << ("Install (as root):"
+++ pre << [unlines ["# runghc " ++ setupProg ++ " install"]])
else noHtml
]
]
mai = section "Maintainer" [p << maintainer desc]
lic | null (licenseFile desc) = section "License" [p << show (license desc)]
| otherwise = section "License"
[p << ("See "
+++ (linkFile desc (licenseFile desc) << licenseFile desc)
+++ ".")]
validXHtml = thespan << hlink "http://validator.w3.org/check?uri=referer" "Validate XHTML"
validCSS = thespan << hlink "http://jigsaw.w3.org/css-validator/check/referer" "Validate CSS"
generator = thespan << ("Page generated by "
+++ hlink "http://www.cs.chalmers.se/~bringert/darcs/hask-home/doc/" "hask-home")
foo = thediv ! [theclass "footer"]
<< [hr, p << [generator +++ " " +++ validXHtml +++ " " +++ validCSS]]
section h xs = thediv ! [theclass "section"] << ((h2 << [h]):xs)
mkHtaccess :: PackageDescription -> String
mkHtaccess desc =
unlines [
unwords["Redirect" ,
uriPath $ fileURI desc (downloadDir ++ "/" ++ latestDistFile desc),
show $ fileURI desc (downloadDir ++ "/" ++ distFile desc)]
]
isLibrary :: PackageDescription -> Bool
isLibrary = isJust . library
findSetup :: IO String
findSetup = do
b <- doesFileExist "Setup.hs"
if b then return "Setup.hs"
else do
b <- doesFileExist "Setup.lhs"
if b then return "Setup.lhs"
else fail "No setup program found"
hlink :: HTML a => String -> a -> Html
hlink u b = anchor ! [href u] << b
showExceptions a = catch a (\e -> print e >> throw e)
main = do
packageDesc <- defaultPackageDesc normal
gDesc <- readPackageDescription normal packageDesc
let desc = flattenPackageDescription gDesc
hPutStrLn stderr $ "Creating " ++ docDir ++ " ..."
createDirectoryIfMissing True docDir
setupProg <- findSetup
when (isLibrary desc) $ do hPutStrLn stderr $ "Building API documentation..."
buildHaddock desc
hPutStrLn stderr $ "Building tarball " ++ distFile desc ++ " ..."
mkTarball desc
readme <- readFileOrNull "README"
readme' <- txt2html $ if null readme then description desc else readme
hPutStrLn stderr $ "Writing " ++ indexFile ++ " ..."
writeFile indexFile $ renderHtml $ makeIndex desc setupProg readme'
hPutStrLn stderr $ "Writing " ++ htaccessFile ++ " ..."
writeFile htaccessFile $ mkHtaccess desc