----------------------------------------------------------------------------- -- | -- Module : Haddock.Backends.Hoogle -- Copyright : (c) Neil Mitchell 2006-2008 -- License : BSD-like -- -- Maintainer : haddock@projects.haskell.org -- Stability : experimental -- Portability : portable -- -- Write out Hoogle compatible documentation -- http://www.haskell.org/hoogle/ ----------------------------------------------------------------------------- module Haddock.Backends.Hoogle ( ppHoogle ) where import Haddock.GhcUtils import Haddock.Types import Haddock.Utils hiding (out) import GHC import Outputable import Data.Char import Data.List import Data.Maybe import System.FilePath import System.IO prefix :: [String] prefix = ["-- Hoogle documentation, generated by Haddock" ,"-- See Hoogle, http://www.haskell.org/hoogle/" ,""] ppHoogle :: String -> String -> String -> Maybe (Doc RdrName) -> [Interface] -> FilePath -> IO () ppHoogle package version synopsis prologue ifaces odir = do let filename = package ++ ".txt" contents = prefix ++ docWith (drop 2 $ dropWhile (/= ':') synopsis) prologue ++ ["@package " ++ package] ++ ["@version " ++ version | version /= ""] ++ concat [ppModule i | i <- ifaces, OptHide `notElem` ifaceOptions i] h <- openFile (odir filename) WriteMode hSetEncoding h utf8 hPutStr h (unlines contents) hClose h ppModule :: Interface -> [String] ppModule iface = "" : doc (ifaceDoc iface) ++ ["module " ++ moduleString (ifaceMod iface)] ++ concatMap ppExport (ifaceExportItems iface) ++ concatMap ppInstance (ifaceInstances iface) --------------------------------------------------------------------- -- Utility functions dropHsDocTy :: HsType a -> HsType a dropHsDocTy = f where g (L src x) = L src (f x) f (HsForAllTy a b c d) = HsForAllTy a b c (g d) f (HsBangTy a b) = HsBangTy a (g b) f (HsAppTy a b) = HsAppTy (g a) (g b) f (HsFunTy a b) = HsFunTy (g a) (g b) f (HsListTy a) = HsListTy (g a) f (HsPArrTy a) = HsPArrTy (g a) f (HsTupleTy a b) = HsTupleTy a (map g b) f (HsOpTy a b c) = HsOpTy (g a) b (g c) f (HsParTy a) = HsParTy (g a) f (HsKindSig a b) = HsKindSig (g a) b f (HsDocTy a _) = f $ unL a f x = x outHsType :: OutputableBndr a => HsType a -> String outHsType = out . dropHsDocTy makeExplicit :: HsType a -> HsType a makeExplicit (HsForAllTy _ a b c) = HsForAllTy Explicit a b c makeExplicit x = x makeExplicitL :: LHsType a -> LHsType a makeExplicitL (L src x) = L src (makeExplicit x) dropComment :: String -> String dropComment (' ':'-':'-':' ':_) = [] dropComment (x:xs) = x : dropComment xs dropComment [] = [] out :: Outputable a => a -> String out = f . unwords . map (dropWhile isSpace) . lines . showSDocUnqual . ppr where f xs | " " `isPrefixOf` xs = f $ drop 19 xs f (x:xs) = x : f xs f [] = [] operator :: String -> String operator (x:xs) | not (isAlphaNum x) && x `notElem` "_' ([{" = "(" ++ x:xs ++ ")" operator x = x --------------------------------------------------------------------- -- How to print each export ppExport :: ExportItem Name -> [String] ppExport (ExportDecl decl dc subdocs _) = doc (fst dc) ++ f (unL decl) where f (TyClD d@TyDecl{}) | isDataDecl d = ppData d subdocs | otherwise = ppSynonym d f (TyClD d@ClassDecl{}) = ppClass d f (ForD (ForeignImport name typ _ _)) = ppSig $ TypeSig [name] typ f (ForD (ForeignExport name typ _ _)) = ppSig $ TypeSig [name] typ f (SigD sig) = ppSig sig f _ = [] ppExport _ = [] ppSig :: Sig Name -> [String] ppSig (TypeSig names sig) = [operator prettyNames ++ " :: " ++ outHsType typ] where prettyNames = concat . intersperse ", " $ map out names typ = case unL sig of HsForAllTy Explicit a b c -> HsForAllTy Implicit a b c x -> x ppSig _ = [] -- note: does not yet output documentation for class methods ppClass :: TyClDecl Name -> [String] ppClass x = out x{tcdSigs=[]} : concatMap (ppSig . addContext . unL) (tcdSigs x) where addContext (TypeSig name (L l sig)) = TypeSig name (L l $ f sig) addContext _ = error "expected TypeSig" f (HsForAllTy a b con d) = HsForAllTy a b (reL (context : unLoc con)) d f t = HsForAllTy Implicit emptyHsQTvs (reL [context]) (reL t) context = nlHsTyConApp (unL $ tcdLName x) (map (reL . HsTyVar . hsTyVarName . unL) (hsQTvBndrs (tcdTyVars x))) ppInstance :: ClsInst -> [String] ppInstance x = [dropComment $ out x] ppSynonym :: TyClDecl Name -> [String] ppSynonym x = [out x] ppData :: TyClDecl Name -> [(Name, DocForDecl Name)] -> [String] ppData decl@(TyDecl { tcdTyDefn = defn }) subdocs = showData decl{ tcdTyDefn = defn { td_cons=[],td_derivs=Nothing }} : concatMap (ppCtor decl subdocs . unL) (td_cons defn) where -- GHC gives out "data Bar =", we want to delete the equals -- also writes data : a b, when we want data (:) a b showData d = unwords $ map f $ if last xs == "=" then init xs else xs where xs = words $ out d nam = out $ tcdLName d f w = if w == nam then operator nam else w ppData _ _ = panic "ppData" -- | for constructors, and named-fields... lookupCon :: [(Name, DocForDecl Name)] -> Located Name -> Maybe (Doc Name) lookupCon subdocs (L _ name) = case lookup name subdocs of Just (d, _) -> d _ -> Nothing ppCtor :: TyClDecl Name -> [(Name, DocForDecl Name)] -> ConDecl Name -> [String] ppCtor dat subdocs con = doc (lookupCon subdocs (con_name con)) ++ f (con_details con) where f (PrefixCon args) = [typeSig name $ args ++ [resType]] f (InfixCon a1 a2) = f $ PrefixCon [a1,a2] f (RecCon recs) = f (PrefixCon $ map cd_fld_type recs) ++ concat [doc (lookupCon subdocs (cd_fld_name r)) ++ [out (unL $ cd_fld_name r) `typeSig` [resType, cd_fld_type r]] | r <- recs] funs = foldr1 (\x y -> reL $ HsFunTy (makeExplicitL x) (makeExplicitL y)) apps = foldl1 (\x y -> reL $ HsAppTy x y) typeSig nm flds = operator nm ++ " :: " ++ outHsType (makeExplicit $ unL $ funs flds) name = out $ unL $ con_name con resType = case con_res con of ResTyH98 -> apps $ map (reL . HsTyVar) $ unL (tcdLName dat) : [hsTyVarName v | L _ (v@UserTyVar {}) <- hsQTvBndrs $ tcdTyVars dat] ResTyGADT x -> x --------------------------------------------------------------------- -- DOCUMENTATION doc :: Outputable o => Maybe (Doc o) -> [String] doc = docWith "" docWith :: Outputable o => String -> Maybe (Doc o) -> [String] docWith [] Nothing = [] docWith header d = ("":) $ zipWith (++) ("-- | " : repeat "-- ") $ [header | header /= ""] ++ ["" | header /= "" && isJust d] ++ maybe [] (showTags . markup markupTag) d data Tag = TagL Char [Tags] | TagP Tags | TagPre Tags | TagInline String Tags | Str String deriving Show type Tags = [Tag] box :: (a -> b) -> a -> [b] box f x = [f x] str :: String -> [Tag] str a = [Str a] -- want things like paragraph, pre etc to be handled by blank lines in the source document -- and things like \n and \t converted away -- much like blogger in HTML mode -- everything else wants to be included as tags, neatly nested for some (ul,li,ol) -- or inlne for others (a,i,tt) -- entities (&,>,<) should always be appropriately escaped markupTag :: Outputable o => DocMarkup o [Tag] markupTag = Markup { markupParagraph = box TagP, markupEmpty = str "", markupString = str, markupAppend = (++), markupIdentifier = box (TagInline "a") . str . out, markupIdentifierUnchecked = box (TagInline "a") . str . out . snd, markupModule = box (TagInline "a") . str, markupEmphasis = box (TagInline "i"), markupMonospaced = box (TagInline "tt"), markupPic = const $ str " ", markupUnorderedList = box (TagL 'u'), markupOrderedList = box (TagL 'o'), markupDefList = box (TagL 'u') . map (\(a,b) -> TagInline "i" a : Str " " : b), markupCodeBlock = box TagPre, markupURL = box (TagInline "a") . str, markupAName = const $ str "", markupExample = box TagPre . str . unlines . map exampleToString } showTags :: [Tag] -> [String] showTags = intercalate [""] . map showBlock showBlock :: Tag -> [String] showBlock (TagP xs) = showInline xs showBlock (TagL t xs) = ['<':t:"l>"] ++ mid ++ ['<':'/':t:"l>"] where mid = concatMap (showInline . box (TagInline "li")) xs showBlock (TagPre xs) = ["
"] ++ showPre xs ++ ["
"] showBlock x = showInline [x] asInline :: Tag -> Tags asInline (TagP xs) = xs asInline (TagPre xs) = [TagInline "pre" xs] asInline (TagL t xs) = [TagInline (t:"l") $ map (TagInline "li") xs] asInline x = [x] showInline :: [Tag] -> [String] showInline = unwordsWrap 70 . words . concatMap f where fs = concatMap f f (Str x) = escape x f (TagInline s xs) = "<"++s++">" ++ (if s == "li" then trim else id) (fs xs) ++ "" f x = fs $ asInline x trim = dropWhile isSpace . reverse . dropWhile isSpace . reverse showPre :: [Tag] -> [String] showPre = trimFront . trimLines . lines . concatMap f where trimLines = dropWhile null . reverse . dropWhile null . reverse trimFront xs = map (drop i) xs where ns = [length a | x <- xs, let (a,b) = span isSpace x, b /= ""] i = if null ns then 0 else minimum ns fs = concatMap f f (Str x) = escape x f (TagInline s xs) = "<"++s++">" ++ fs xs ++ "" f x = fs $ asInline x unwordsWrap :: Int -> [String] -> [String] unwordsWrap n = f n [] where f _ s [] = [g s | s /= []] f i s (x:xs) | nx > i = g s : f (n - nx - 1) [x] xs | otherwise = f (i - nx - 1) (x:s) xs where nx = length x g = unwords . reverse escape :: String -> String escape = concatMap f where f '<' = "<" f '>' = ">" f '&' = "&" f x = [x]