**Hyakko** is a Haskell port of [docco](http://jashkenas.github.com/docco/): the original quick-and-dirty, hundred-line-line, literate-programming-style documentation generator. It produces HTML that displays your comments alongside your code. Comments are passed through [Markdown](http://daringfireball.net/projects/markdown/syntax) and code is passed through [Kate](http://johnmacfarlane.net/highlighting-kate/) syntax highlighting. This page is the result of running Hyakko against its own source file. If you install Hyakko, you can run it from the command-line: hyakko src/*.hs or just specify a directory and Hyakko will search for supported files inside the directory recursively. Then it will generate linked HTML documentation for the named source files, saving it into a `docs` folder. The [source for Hyakko](https://github.com/sourrust/hyakko) available on GitHub. To install Hyakko git clone git://github.com/sourrust/hyakko.git cd hyakko cabal install or cabal update cabal install hyakko > {-# LANGUAGE OverloadedStrings #-} > module Main where > import Text.Markdown > import Data.Map (Map) > import qualified Data.Map as M > import Data.ByteString.Lazy.Char8 (ByteString) > import qualified Data.ByteString.Lazy.Char8 as L > import Data.Text (Text) > import qualified Data.Text as T > import qualified Data.Text.IO as T > import Data.List (sort) > import Data.Maybe (fromJust) > import Control.Monad (filterM, (>=>), forM) > import qualified Text.Blaze.Html as B > import Text.Blaze.Html.Renderer.Utf8 (renderHtml) > import qualified Text.Highlighting.Kate as K > import Text.Pandoc.Templates > import Text.Regex.PCRE ((=~)) > import System.Directory ( getDirectoryContents > , doesDirectoryExist > , doesFileExist > , createDirectoryIfMissing > ) > import System.Environment (getArgs) > import System.FilePath ( takeBaseName > , takeExtension > , takeFileName > , (>) > ) > import Paths_hyakko (getDataFileName) Main Documentation Generation Functions --------------------------------------- Infix functions for easier concatenation with Text and ByteString. > (++.) :: Text -> Text -> Text > (++.) = T.append > {-# INLINE (++.) #-} > (++*) :: ByteString -> ByteString -> ByteString > (++*) = L.append > {-# INLINE (++*) #-} Simpler type signatuted regex replace function. > replace :: ByteString -> Text -> Text -> Text > replace reg x y = > let str = T.unpack x > (_, _, rp) = str =~ reg :: (String, String, String) > in y ++. (T.pack rp) Generate the documentation for a source file by reading it in, splitting it up into comment/code sections, highlighting them for the appropriate language, and merging them into an HTML template. > generateDocumentation :: [FilePath] -> IO () > generateDocumentation [] = return () > generateDocumentation (x:xs) = do > code <- T.readFile x > let sections = parse (getLanguage x) code > if null sections then > putStrLn $ "hyakko doesn't support the language extension " > ++ takeExtension x > else do > let output = highlight x sections > y = mapSections sections output > generateHTML x y > generateDocumentation xs Given a string of source code, parse out each comment and the code that follows it, and create an individual **section** for it. Sections take the form: [ ("docsText", ...), ("docsHtml", ...), ("codeText", ...), ("codeHtml", ...) ] > inSections :: [Text] > -> ByteString > -> [Map String Text] > inSections xs r = > let sections = sectionOff "" "" xs > in map M.fromList sections > where sectionOff :: Text -> Text -> [Text] -> [[(String, Text)]] > sectionOff code docs [] = [ ("codeText", code) > , ("docsText", docs) > ] : [] > sectionOff code docs (y:ys) = > if T.unpack y =~ r then > handleDocs > else > sectionOff (code ++. y ++. "\n") docs ys > where handleDocs = > if T.null code then > sectionOff code (newdocs docs) ys > else > [ ("codeText", code) > , ("docsText", docs) > ] : sectionOff "" (newdocs "") ys > newdocs d = d ++. (replace r y "") ++. "\n" > parse :: Maybe (Map String ByteString) -> Text -> [Map String Text] > parse Nothing _ = [] > parse (Just src) code = > inSections (newlines line (M.lookup "literate" src) True) > (src M.! "comment") > where line :: [Text] > line = filter ((/=) "#!" . T.take 2) $ T.lines code > newlines :: [Text] -> Maybe ByteString -> Bool -> [Text] > newlines [] _ _ = [] > newlines xs Nothing _ = xs > newlines (x:xs) lit isText = > let s = src M.! "symbol" > r = "^" ++* (src M.! "symbol2") ++* "\\s?" > r1 = L.pack "^\\s*$" > (x', y) = if T.unpack x =~ r then > (replace r x "", False) > else > insert (T.unpack x =~ r1) isText > ((T.pack $ L.unpack s) ++. " " ++. x) > in x': newlines xs lit y > where insert :: Bool -> Bool -> Text -> (Text, Bool) > insert True True _ = (T.pack . L.unpack > $ src M.! "symbol", True) > insert True False _ = ("", False) > insert False _ y = (y, True) Highlights a single chunk of Haskell code, using **Kate**, and runs the text of its corresponding comment through **Markdown**, using the Markdown translator in **[Pandoc](http://johnmacfarlane.net/pandoc/)**. > highlight :: FilePath -> [Map String Text] -> [Text] > highlight src section = > let language = fromJust $ getLanguage src > langName = L.unpack $ language M.! "name" > input = map (\x -> T.unpack $ x M.! "codeText") section > html = B.toHtml . K.formatHtmlBlock K.defaultFormatOpts > . K.highlightAs langName > htmlText = T.pack . L.unpack . renderHtml . html > in map htmlText input `mapSections` is used to insert the html parts of the mapped sections of text into the corresponding keys of `docsHtml` and `codeHtml`. > mapSections :: [Map String Text] -> [Text] -> [Map String Text] > mapSections section highlighted = > let docText s = toHTML . T.unpack $ s M.! "docsText" > codeText i = highlighted !! i > sectLength = (length section) - 1 > intoMap x = let sect = section !! x > in M.insert "docsHtml" (docText sect) $ > M.insert "codeHtml" (codeText x) sect > in map intoMap [0 .. sectLength] Determine whether or not there is a `Jump to` section > multiTemplate :: Int -> [(String, String)] > multiTemplate 1 = [] > multiTemplate _ = [("multi", "1")] Produces a list of anchor tags to different files in docs $file-name$ > sourceTemplate :: [FilePath] -> [(String, String)] > sourceTemplate = map source > where source x = ("source", concat > [ " , takeFileName $ destination x > , "\">" > , takeFileName x > , "" > ]) Produces a list of table rows that split up code and documentation