{-# LANGUAGE OverloadedStrings #-} -- **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 [Pygments](http://pygments.org/) 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 -- -- ...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 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.List (sort, groupBy, genericIndex) import Data.Maybe (fromJust) import Control.Monad (filterM, (>=>), forM) import Text.Pandoc.Templates import Text.Regex import Text.Regex.PCRE ((=~)) import System.Directory ( getDirectoryContents , doesDirectoryExist , doesFileExist , createDirectoryIfMissing ) import System.Environment (getArgs) import System.FilePath ( takeBaseName , takeExtension , takeFileName , (>) ) import System.Process (readProcess) import Paths_hyakko (getDataFileName) -- ### Main Documentation Generation Functions (><) :: ByteString -> ByteString -> ByteString (><) = L.append {-# INLINE (><) #-} -- 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 <- L.readFile x let sections = parse (getLanguage x) code if null sections then putStrLn $ "hyakko doesn't support the language extension " ++ takeExtension x else do (output, language) <- highlight x sections let y = mapSections sections output language 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 :: [ByteString] -> ByteString -> [Map String ByteString] inSections xs r = [M.fromList l | l <- clump sections] where -- Bring the lists together into groups of comment and groups of code -- pattern. sections :: [[ByteString]] sections = ensurePair . map concat -- Group code into a list . groupBy' head not -- Group comments into a list $ groupBy' id id xs -- Clump sectioned off lines into doc and code text. clump :: [[ByteString]] -> [[(String, ByteString)]] clump [x] = clump $ ensurePair [x] clump (x:y:ys) = [ ("docsText", replace x) , ("codeText", L.unlines y) ] : clump ys clump _ = [] -- Generalized function used to section off code and comments groupBy' t t1 = groupBy $ \x y -> and $ map (t1 . (=~ r)) [t x, t y] -- Replace the beggining comment symbol with nothing replace :: [ByteString] -> ByteString replace = L.unlines . map (\x -> let y = L.unpack x mkReg = mkRegex . (=~ r) in L.pack $ subRegex (mkReg y) y "") -- Make sure the result is in the right pairing order ensurePair :: [[ByteString]] -> [[ByteString]] ensurePair ys | even (length ys) = ys | otherwise = appendList [[""]] where appendList | (head . head) ys =~ r = (ys ++) | otherwise = (++ ys) parse :: Maybe (Map String ByteString) -> ByteString -> [Map String ByteString] parse Nothing _ = [] parse (Just src) code = inSections line $ src M.! "comment" where line = filter ((/=) "#!" . L.take 2) $ L.lines code -- Highlights a single chunk of Haskell code, using **Pygments** over stdio, -- and runs the text of its corresponding comment through **Markdown**, -- using the Markdown translator in -- **[Pandoc](http://johnmacfarlane.net/pandoc/)**. -- -- We process the entire file in a single call to Pygments by inserting -- little marker comments between each section and then splitting the result -- string wherever our markers occur. highlight :: FilePath -> [Map String ByteString] -> IO (String, Map String ByteString) highlight src section = do let language = fromJust $ getLanguage src options = ["-l", L.unpack $ language M.! "name", "-f", "html", "-O", "encoding=utf-8"] input = concatMap (\x -> let codeText = L.unpack $ x M.! "codeText" divider = L.unpack $ language M.! "dividerText" in codeText ++ divider) section output <- readProcess "pygmentize" options input return (output, language) -- After `highlight` is called, there are divider inside to show when the -- hightlighed stop and code begins. `mapSections` is used to take out the -- dividers and put them into `docsHtml` and `codeHtml` sections. mapSections :: [Map String ByteString] -> String -> Map String ByteString -> [Map String ByteString] mapSections section highlighted language = let output = subRegex (mkRegex highlightReplace) highlighted "" divider = mkRegex . L.unpack $ language M.! "dividerHtml" fragments = splitRegex divider output packFrag = L.pack . genericIndex fragments docText s = toHTML . L.unpack $ s M.! "docsText" codeText i = highlightStart >< packFrag i >< highlightEnd 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 x , "" ]) -- Produces a list of table rows that split up code and documentation -- --
"
highlightEnd = "