Hyakko ====== **Hyakko** is a Haskell port of [docco](http://jashkenas.github.com/docco/): the original quick-and-dirty documentation generate. It produces an HTML document that displays your comments intermingled with you code. All prose is passed through [Markdown](http://daringfireball.net/projects/markdown/syntax), and code is passed through [Kate](http://johnmacfarlane.net/highlighting-kate/) syntax highlighing. This page is the result of running Hyakko against its own [source file](https://github.com/sourrust/hyakko/blob/master/src/Hyakko.lhs). 1. Install Hyakko with **cabal**: `cabal update; cabal install hyakko` 2. Run it agianst your code: `hyakko src/*.hs` or just `hyakko src` and Hyakko will search for supported files inside the directory recursively. There is no "Step 3". This will generate an HTML page for each of the named source files, with a menu linking to the other pages, saving the whole mess into a `docs` folder — and is also configurable. The [Hyakko source](https://github.com/sourrust/hyakko) is available on GitHub, and is released under the [MIT license](http://opensource.org/licenses/MIT). There is a ["literate" style](http://www.haskell.org/haskellwiki/Literate_programming) of Haskell, only one supported at this time, but other literate styles can be added fairly easily. > {-# LANGUAGE OverloadedStrings, DeriveDataTypeable #-} > 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, isNothing) > import Data.Version (showVersion) > import Control.Monad (filterM, (>=>), forM, forM_, unless) > 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.Console.CmdArgs > import System.Directory ( getDirectoryContents > , doesDirectoryExist > , doesFileExist > , createDirectoryIfMissing > , copyFile > ) > import System.FilePath ( takeBaseName > , takeExtension > , takeFileName > , (>) > , addTrailingPathSeparator > ) > import Paths_hyakko (getDataFileName, version, getDataDir) Main Documentation Generation Functions --------------------------------------- Generate the documentation for our configured source file by copyinh over static assets, reading all the source files in, splitting them up into prose+code sections, highlighting each file in the approapiate language, and printing them out in an HTML template. > generateDocumentation :: Hyakko -> [FilePath] -> IO () > generateDocumentation _ [] = > putStrLn "hyakko: no files or options given (try --help)" > generateDocumentation opts xs = mapM_ generate xs > where generate :: FilePath -> IO () > generate x = do > code <- T.readFile x > dataDir <- getDataDir > let sections = parse (getLanguage x) code > opts' = configHyakko opts > unless (isNothing $ layout opts') $ do > let layoutDir = fromJust $ layout opts' > copyDirectory opts'$ dataDir > "resources" > > layoutDir > > "public" > if null sections then > putStrLn $ "hyakko doesn't support the language extension " > ++ takeExtension x > else do > let highlighted = highlight x sections > y = mapSections sections highlighted > generateHTML opts' x y Given a string of source code, parse out eacg block of prose and the code that follows it — by detecting which is which, line by line — then create an individual **section** for it. Each section is Map with `docText` and `codeText` properties, and eventuall `docsHtml` and `codeHtml` as well. > 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 code > else > sectionOff (code ++. y ++. "\n") docs ys > where handleDocs "" = handleHeaders code (newdocs docs) ys > handleDocs _ = [ ("codeText", code) > , ("docsText", docs) > ] : handleHeaders "" (newdocs "") ys > newdocs d = d ++. (replace r y "") ++. "\n" If there is a header markup, only for `---` and `===`, it will get its own line from the other documentation. > handleHeaders c d zs = > if T.unpack d =~ L.pack "^(---|===)+" then > [ ("codeText", c) > , ("docsText", d) > ] : sectionOff "" "" zs > else > sectionOff c d zs The higher level interface for calling `inSections`. `parse` basically sanitates the file — turing literate into regular source and take out shebangs — then feed it to `inSections`, and finally return the results. > 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 Transforms a literate style language file into its normal, non-literate style language. If it is normal, `newlines` for returns the same list of `Text` that was passed in. > 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 Inserts a comment symbol and a single space into the documentation line and check if the last line was code and documentation. If the previous line was code and the line is blank or has just whitespace, it returns a blank `Text` datatype; otherwise it will return just the comment symbol. > 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 the current file of code, using **Kate**, and outputs the the highlighted html to its caller. > 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. This will only show up if the template support it and there are more than one source file generated. > sourceTemplate :: Hyakko -> [FilePath] -> [(String, String)] > sourceTemplate opts = map source > where source x = ("source", concat > [ " , takeFileName $ destination (output opts) x > , "\">" > , takeFileName x > , "" > ]) Depending on the layout type, `sectionTemplate` will produce the HTML that will be hooked into the templates layout theme. > sectionTemplate :: [Map String Text] > -> Maybe String > -> [Int] > -> [(String, String)] > sectionTemplate section layoutType count = > let isLayout = not $ isNothing layoutType > sections = if isLayout then layoutFn $ fromJust layoutType > else undefined > in map sections count > where layoutFn "parallel" = parallel > layoutFn "linear" = linear > layoutFn _ = undefined > parallel x = > let x' = x + 1 > sect = section !! x > docsHtml = T.unpack $ sect M.! "docsHtml" > codeHtml = T.unpack $ sect M.! "codeHtml" > codeText = T.unpack $ sect M.! "codeText" > header = docsHtml =~ L.pack "^\\s*<(h\\d)" > isBlank = T.null $ replace "\\s" (T.pack codeText) "" > in ("section", concat > [ "