@@ -4,11 +4,14 @@
4-- | Generation of HTML documentation for Idris code
4-- | Generation of HTML documentation for Idris code
5module Idris.IdrisDoc (generateDocs) where
5module Idris.IdrisDoc (generateDocs) where
7−import Idris.Core.TT (Name (..), SpecialName (..), txt, str, nsroot)
7+import Idris.Core.TT (Name (..), sUN, SpecialName (..), OutputAnnotation (..),
8import Idris.Core.Evaluate (ctxtAlist, Def (..), lookupDefAcc,
9import Idris.Core.Evaluate (ctxtAlist, Def (..), lookupDefAcc,
9 Accessibility (..))
10 Accessibility (..))
11+import Idris.ParseHelpers (opChars)
10import Idris.AbsSyntax
12import Idris.AbsSyntax
11import Idris.Docs
13import Idris.Docs
14+import Idris.Docstrings (nullDocstring)
13import Paths_idris (getDataFileName)
16import Paths_idris (getDataFileName)
@@ -16,12 +19,15 @@ import Control.Monad (forM_)
16import Control.Monad.Trans.Error
19import Control.Monad.Trans.Error
17import Control.Monad.Trans.State.Strict
20import Control.Monad.Trans.State.Strict
19import qualified Data.ByteString as BS
24import qualified Data.ByteString as BS
20import qualified Data.ByteString.Lazy as BS2
25import qualified Data.ByteString.Lazy as BS2
21import qualified Data.Text.Encoding as E
26import qualified Data.Text.Encoding as E
22import qualified Data.List as L
27import qualified Data.List as L
23import qualified Data.List.Split as LS
28import qualified Data.List.Split as LS
24import qualified Data.Map as M hiding ((!))
29import qualified Data.Map as M hiding ((!))
30+import qualified Data.Ord (compare)
25import qualified Data.Set as S
31import qualified Data.Set as S
26import qualified Data.Text as T
32import qualified Data.Text as T
@@ -30,15 +36,22 @@ import System.IO.Error
30import System.FilePath
36import System.FilePath
31import System.Directory
37import System.Directory
39+import Cheapskate.Html (renderDoc)
40+import Text.PrettyPrint.Annotated.Leijen (displayDecorated, renderCompact)
33−import Text.Blaze (toValue)
42+import Text.Blaze (toValue, contents)
43+import Text.Blaze.Internal (MarkupM (Empty))
34−import Text.Blaze.Html5 ((!), toHtml)
44+import Text.Blaze.Html5 ((!), toHtml, preEscapedToHtml)
35import qualified Text.Blaze.Html5 as H
45import qualified Text.Blaze.Html5 as H
36−import Text.Blaze.Html5.Attributes
46+import Text.Blaze.Html5.Attributes as A
37import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
47import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
48+import qualified Text.Blaze.Html.Renderer.String as R
49+import Text.Blaze.Renderer.String (renderMarkup)
39-- ---------------------------------------------------------------- [ Public ]
51-- ---------------------------------------------------------------- [ Public ]
41−-- | Generates HTML documentation for a series of loaded namespaces and their dependencies.
53+-- | Generates HTML documentation for a series of loaded namespaces
54+-- and their dependencies.
42generateDocs :: IState -- ^ IState where all necessary information is
55generateDocs :: IState -- ^ IState where all necessary information is
43 -- extracted from.
56 -- extracted from.
44 -> [Name] -- ^ List of namespaces to generate
57 -> [Name] -- ^ List of namespaces to generate
@@ -130,7 +143,7 @@ fetchInfo ist nss =
132-- | Removes loose class methods and data constructors,
145-- | Removes loose class methods and data constructors,
133−-- leaving them documented only under their parent.
146+-- leaving them documented only under their parent.
134removeOrphans :: [NsItem] -- ^ List to remove orphans from
147removeOrphans :: [NsItem] -- ^ List to remove orphans from
135 -> [NsItem] -- ^ Orphan-free list
148 -> [NsItem] -- ^ Orphan-free list
136removeOrphans list =
149removeOrphans list =
@@ -214,7 +227,7 @@ hasDoc _ = False
216-- | Predicate saying whether a Name possibly may have docs defined
229-- | Predicate saying whether a Name possibly may have docs defined
217−-- | Without this, getDocs from Idris.Docs may fail a pattern match.
230+-- Without this, getDocs from Idris.Docs may fail a pattern match.
218mayHaveDocs :: Name -- ^ The Name to test
231mayHaveDocs :: Name -- ^ The Name to test
219 -> Bool -- ^ The result
232 -> Bool -- ^ The result
220mayHaveDocs (UN _) = True
233mayHaveDocs (UN _) = True
@@ -234,8 +247,8 @@ loadDocs ist n
236-- | Extracts names referred from a type.
249-- | Extracts names referred from a type.
237−-- The covering of all PTerms ensures that we avoid unanticipated cases.
250+-- The covering of all PTerms ensures that we avoid unanticipated cases, though
238−-- All of them are probably not needed.
251+-- all of them are not needed. The author just did not know which!
239-- TODO: Remove unnecessary cases
252-- TODO: Remove unnecessary cases
240extractPTermNames :: PTerm -- ^ Where to extract names from
253extractPTermNames :: PTerm -- ^ Where to extract names from
241 -> [Name] -- ^ Extracted names
254 -> [Name] -- ^ Extracted names
@@ -319,8 +332,9 @@ extractPTactic _ = []
319-- A merge of the new docs and any existing docs located in the output dir
332-- A merge of the new docs and any existing docs located in the output dir
320-- is attempted.
333-- is attempted.
321-- TODO: Ensure the merge always succeeds.
334-- TODO: Ensure the merge always succeeds.
322−-- Currently the content of '<builtins>.ns.html' may change between runs,
335+-- Currently the content of '<builtins>.ns.html' may change between
323−-- thus not always containing all items referred from other .ns.html files.
336+-- runs, thus not always containing all items referred from other
324createDocs :: NsDict -- ^ All info from which to generate docs
338createDocs :: NsDict -- ^ All info from which to generate docs
325 -> FilePath -- ^ The base directory to which
339 -> FilePath -- ^ The base directory to which
326 -- documentation will be written.
340 -- documentation will be written.
@@ -335,18 +349,13 @@ createDocs nsd out =
335 " already in use for other than IdrisDoc."
349 " already in use for other than IdrisDoc."
337 createDirectoryIfMissing True out
351 createDirectoryIfMissing True out
338− foldl (docGen out) (return ()) (M.toList nsd)
352+ foldl docGen (return ()) (M.toList nsd)
339−-- if new then putStrLn "Creating IdrisDoc index"
340−-- else putStrLn "Updating IdrisDoc index"
341 createIndex nss out
353 createIndex nss out
342 writeDocInfo out (docInfo' { namespaces = nss })
354 writeDocInfo out (docInfo' { namespaces = nss })
343 copyDependencies out
355 copyDependencies out
344 return $ Right ()
356 return $ Right ()
346− where docGen out io (n, c) =
348−-- putStrLn $ "Creating IdrisDoc for '" ++ (nsName2Str n) ++ "'"
349− createNsDoc n c out
358+ where docGen io (n, c) = do io; createNsDoc nsd n c out
352-- | (Over)writes the 'index.html' file in the given directory with
361-- | (Over)writes the 'index.html' file in the given directory with
@@ -361,40 +370,42 @@ createIndex nss out =
361 BS2.hPut h $ renderHtml $ wrapper Nothing $ do
370 BS2.hPut h $ renderHtml $ wrapper Nothing $ do
362 H.h1 $ "Namespaces"
371 H.h1 $ "Namespaces"
363 H.ul ! class_ "names" $ do
372 H.ul ! class_ "names" $ do
364− let path ns = genRelNsPath ns "ns.html"
373+ let path ns = genRelNsPath ns "ns.html"
365− item ns = do let n = toHtml $ nsName2Str ns
374+ item ns = do let n = toHtml $ nsName2Str ns
366− link = toValue $ path ns
375+ link = toValue $ path ns
367− H.li $ H.a ! href link ! class_ "code" $ n
376+ H.li $ H.a ! href link ! class_ "code" $ n
377+ sort = L.sortBy (\n1 n2 -> reverse n1 `compare` reverse n2)
368− forM_ (S.toList nss) item
378+ forM_ (sort $ S.toList nss) item
370 renameFile path (out </> "index.html")
380 renameFile path (out </> "index.html")
373-- | Generates a HTML file for a namespace and its contents.
383-- | Generates a HTML file for a namespace and its contents.
374-- The location for e.g. Prelude.Algebra is <base>/Prelude/Algebra.html
384-- The location for e.g. Prelude.Algebra is <base>/Prelude/Algebra.html
385+createNsDoc :: NsDict -- ^ Information about other namespaces
375−createNsDoc :: NsName -- ^ The name of the namespace to
386+ -> NsName -- ^ The name of the namespace to
376 -- create documentation for
387 -- create documentation for
377 -> NsInfo -- ^ The contents of the namespace
388 -> NsInfo -- ^ The contents of the namespace
378 -> FilePath -- ^ The base directory to which
389 -> FilePath -- ^ The base directory to which
379 -- documentation will be written.
390 -- documentation will be written.
381−createNsDoc ns content out =
392+createNsDoc nsd ns content out =
382 do let tpath = out </> (genRelNsPath ns "ns.html")
393 do let tpath = out </> (genRelNsPath ns "ns.html")
383 dir = takeDirectory tpath
394 dir = takeDirectory tpath
384 file = takeFileName tpath
395 file = takeFileName tpath
385 nonHidden (_, _, Hidden) = False
396 nonHidden (_, _, Hidden) = False
386 nonHidden _ = True
397 nonHidden _ = True
387− haveDocs (_, Nothing, _) = False
398+ haveDocs (_, Nothing, _) = []
388− haveDocs _ = True
399+ haveDocs (_, Just d, _) = [d]
389 -- Do not document private members
400 -- Do not document private members
390 content' = filter nonHidden content
401 content' = filter nonHidden content
391 -- We cannot do anything without a Doc
402 -- We cannot do anything without a Doc
392− content'' = filter haveDocs content'
403+ content'' = concatMap haveDocs content'
393 createDirectoryIfMissing True dir
404 createDirectoryIfMissing True dir
394 (path, h) <- openTempFile dir file
405 (path, h) <- openTempFile dir file
395 BS2.hPut h $ renderHtml $ wrapper (Just ns) $ do
406 BS2.hPut h $ renderHtml $ wrapper (Just ns) $ do
396 H.h1 $ toHtml (nsName2Str ns)
407 H.h1 $ toHtml (nsName2Str ns)
397− forM_ content'' (\(n,_,_) -> H.p $ toHtml $ show n)
408+ H.dl ! class_ "decls" $ forM_ content'' (createOtherDoc nsd)
399 renameFile path tpath
410 renameFile path tpath
@@ -408,6 +419,123 @@ genRelNsPath ns suffix = subpath ns <.> suffix
408 where subpath = L.foldl1' (</>) . LS.splitOn "." . nsName2Str
419 where subpath = L.foldl1' (</>) . LS.splitOn "." . nsName2Str
422+-- | Generates a HTML type signature with proper tags
423+-- TODO: Turn docstrings into title attributes more robustly
424+genTypeHeader :: NsDict -- ^ Information about namespaces
425+ -> FunDoc -- ^ Type to generate type declaration for
426+ -> H.Html -- ^ Resulting HTML
427+genTypeHeader nsd (FD n _ args ftype _) = do
428+ H.span ! class_ "name" ! title (toValue $ show n) $ toHtml $ name $ nsroot n
430+ preEscapedToHtml htmlSignature
433+ htmlSignature = displayDecorated decorator $ renderCompact signature
434+ signature = pprintPTerm False [] names ftype
435+ names = [ n | (n@(UN n'), _, _, _) <- args,
436+ not (T.isPrefixOf (txt "__") n') ]
438+ decorator (AnnName n _ _ _) str | filterName n, isFun n = do
439+ R.renderHtml $ H.a ! class_ "name"
440+ ! title (toValue $ show n) ! href (toValue $ link n)
441+ $ toHtml $ show $ nsroot n
442+ decorator (AnnName n _ _ _) str | filterName n = do
443+ R.renderHtml $ H.a ! class_ "type"
444+ ! title (toValue $ show n) ! href (toValue $ link n)
445+ $ toHtml $ show $ nsroot n
446+ decorator (AnnBoundName n _) str | Just t <- M.lookup n docs = do
447+ R.renderHtml $ H.span ! class_ "arg" ! title (toValue t)
449+ decorator _ str = str
451+ docs = M.fromList $ mapMaybe docExtractor args
452+ docExtractor (_, _, _, Nothing) = Nothing
453+ docExtractor (n, _, _, Just d) = Just (n, doc2Txt d)
454+ -- TODO: Remove <p> tags more robustly
455+ doc2Txt d = let dirty = renderMarkup $ contents $ renderDoc $ d
456+ in take (length dirty - 8) $ drop 3 dirty
458+ name (NS n ns) = show (NS (sUN $ name n) ns)
459+ name n = let n' = show n
460+ in if (head n') `elem` opChars
461+ then '(':(n' ++ ")")
464+ link n = let path = genRelNsPath (getNs n) "ns.html"
465+ in path ++ "#" ++ (show n)
469+ in case M.lookup ns nsd of
470+ Nothing -> False -- Should never happen
471+ Just nsi -> case L.find (\(n', d, a) -> n == n') nsi of
472+ Just (_, Just (FunDoc _), _) -> True
475+-- | Generates HTML documentation for a function.
476+createFunDoc :: NsDict -- ^ Information about other namespaces
477+ -> FunDoc -- ^ Function to generate block for
478+ -> H.Html -- ^ Resulting HTML
479+createFunDoc nsd fd@(FD name docstring args ftype fixity) = do
480+ H.dt ! (A.id $ toValue $ show name) $ genTypeHeader nsd fd
482+ (if nullDocstring docstring then Empty else renderDoc docstring)
483+ let args' = filter (\(_, _, _, d) -> isJust d) args
484+ if (not $ null args') || (isJust fixity)
486+ if (isJust fixity) then do
487+ H.dt ! class_ "fixity" $ "Fixity"
488+ let f = fromJust fixity
489+ H.dd ! class_ "fixity" ! title (toValue $ show f) $ genFix f
494+ where genFix (Infixl {prec=p}) =
495+ toHtml $ "Left associative, precedence " ++ show p
496+ genFix (Infixr {prec=p}) =
497+ toHtml $ "Left associative, precedence " ++ show p
498+ genFix (InfixN {prec=p}) =
499+ toHtml $ "Non-associative, precedence " ++ show p
500+ genFix (PrefixN {prec=p}) =
501+ toHtml $ "Prefix, precedence " ++ show p
502+ genArg (_, _, _, Nothing) = Empty
503+ genArg (name, _, _, Just docstring) = do
504+ H.dt $ toHtml $ show name
505+ H.dd $ renderDoc docstring
508+-- | Generates HTML documentation for any Docs type
509+-- TODO: Generate actual signatures for typeclasses
510+createOtherDoc :: NsDict -- ^ Information about other namespaces
511+ -> Docs -- ^ Namespace item to generate HTML block for
512+ -> H.Html -- ^ Resulting HTML
513+createOtherDoc nsd (FunDoc fd) = createFunDoc nsd fd
514+createOtherDoc nsd (ClassDoc n docstring fds) = do
515+ H.dt ! (A.id $ toValue $ show n) $ do
519+ (if nullDocstring docstring then Empty else renderDoc docstring)
520+ H.dl ! class_ "decls" $ forM_ fds (createFunDoc nsd)
521+createOtherDoc nsd (DataDoc fd@(FD n docstring args _ _) fds) = do
522+ H.dt ! (A.id $ toValue $ show n) $ do
524+ genTypeHeader nsd fd
526+ (if nullDocstring docstring then Empty else renderDoc docstring)
527+ let args' = filter (\(_, _, _, d) -> isJust d) args
529+ then H.dl $ forM_ args' genArg
531+ H.dl ! class_ "decls" $ forM_ fds (createFunDoc nsd)
533+ where genArg (_, _, _, Nothing) = Empty
534+ genArg (name, _, _, Just docstring) = do
535+ H.dt $ toHtml $ show name
536+ H.dd $ renderDoc docstring
411-- | Generates everything but the actual content of the page
539-- | Generates everything but the actual content of the page
412wrapper :: Maybe NsName -- ^ Namespace name, unless it is the index
540wrapper :: Maybe NsName -- ^ Namespace name, unless it is the index
413 -> H.Html -- ^ Inner HTML
541 -> H.Html -- ^ Inner HTML
@@ -430,7 +558,7 @@ wrapper ns inner =
430 H.div ! class_ "wrapper" $ do
558 H.div ! class_ "wrapper" $ do
431 H.header $ do
559 H.header $ do
432 H.strong "IdrisDoc"
560 H.strong "IdrisDoc"
433− if index then "" else do
561+ if index then Empty else do
435 toHtml str
563 toHtml str
436 H.nav $ H.a ! href "index.html" $ "Index"
564 H.nav $ H.a ! href "index.html" $ "Index"