hyakko 0.4.0 → 0.5.0
raw patch · 4 files changed
+385/−376 lines, 4 filesdep +blaze-htmldep +highlighting-katedep −processdep −regex-compat
Dependencies added: blaze-html, highlighting-kate
Dependencies removed: process, regex-compat
Files
- hyakko.cabal +7/−7
- resources/hyakko.css +10/−11
- src/Hyakko.hs +0/−358
- src/Hyakko.lhs +368/−0
hyakko.cabal view
@@ -1,5 +1,5 @@ name: hyakko-version: 0.4.0+version: 0.5.0 cabal-version: >= 1.6 build-type: Simple license: MIT@@ -8,8 +8,8 @@ author: Jeremy Hull <sourdrums@gmail.com> maintainer: Jeremy Hull <sourdrums@gmail.com> bug-reports: https://github.com/sourrust/hyakko/issues-stability: alpha-homepage: http://sourrust.github.com/hyakko/+stability: beta+homepage: http://sourrust.github.io/hyakko/ category: Documentation tested-with: GHC == 7.4.2 synopsis: Literate-style Documentation Generator@@ -33,15 +33,15 @@ Executable hyakko build-depends: base >= 4 && < 5, ghc >= 7,- process >= 1, filepath >= 1.2,- regex-compat >= 0.9, regex-pcre >= 0.9, containers >= 0.4, directory >= 1, pandoc >= 1.10, bytestring >= 0.9,- text >= 0.11+ text >= 0.11,+ highlighting-kate >= 0.5,+ blaze-html >= 0.5 hs-source-dirs: src ghc-options: -O2 -Wall- main-is: Hyakko.hs+ main-is: Hyakko.lhs
resources/hyakko.css view
@@ -124,13 +124,12 @@ td.linenos { background-color: #f0f0f0; padding-right: 10px; } span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } body .hll { background-color: #ffffcc }-body .c { color: #408080; font-style: italic } /* Comment */-body .err { border: 1px solid #FF0000 } /* Error */+body .co { color: #408080; font-style: italic } /* Comment */+body .er { border: 1px solid #FF0000 } /* Error */ body .k { color: #954121 } /* Keyword */ body .o { color: #666666 } /* Operator */-body .cm { color: #408080; font-style: italic } /* Comment.Multiline */+body .ot { color: #408080; font-style: italic } /* Comment.Multiline */ body .cp { color: #BC7A00 } /* Comment.Preproc */-body .c1 { color: #408080; font-style: italic } /* Comment.Single */ body .cs { color: #408080; font-style: italic } /* Comment.Special */ body .gd { color: #A00000 } /* Generic.Deleted */ body .ge { font-style: italic } /* Generic.Emph */@@ -146,10 +145,10 @@ body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ body .kp { color: #954121 } /* Keyword.Pseudo */-body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */+body .kw { color: #954121; font-weight: bold } /* Keyword.Reserved */ body .kt { color: #B00040 } /* Keyword.Type */ body .m { color: #666666 } /* Literal.Number */-body .s { color: #219161 } /* Literal.String */+body .st { color: #219161 } /* Literal. */ body .na { color: #7D9029 } /* Name.Attribute */ body .nb { color: #954121 } /* Name.Builtin */ body .nc { color: #0000FF; font-weight: bold } /* Name.Class */@@ -157,19 +156,19 @@ body .nd { color: #AA22FF } /* Name.Decorator */ body .ni { color: #999999; font-weight: bold } /* Name.Entity */ body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */-body .nf { color: #0000FF } /* Name.Function */+body .fu { color: #0000FF } /* Name.Function */ body .nl { color: #A0A000 } /* Name.Label */-body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */+body .dt { color: #0000FF; font-weight: bold } /* Name.Namespace */ body .nt { color: #954121; font-weight: bold } /* Name.Tag */ body .nv { color: #19469D } /* Name.Variable */ body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ body .w { color: #bbbbbb } /* Text.Whitespace */-body .mf { color: #666666 } /* Literal.Number.Float */+body .fl { color: #666666 } /* Literal.Number.Float */ body .mh { color: #666666 } /* Literal.Number.Hex */-body .mi { color: #666666 } /* Literal.Number.Integer */+body .dv { color: #666666 } /* Literal.Number.Integer */ body .mo { color: #666666 } /* Literal.Number.Oct */ body .sb { color: #219161 } /* Literal.String.Backtick */-body .sc { color: #219161 } /* Literal.String.Char */+body .ch { color: #219161 } /* Literal.String.Char */ body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ body .s2 { color: #219161 } /* Literal.String.Double */ body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
− src/Hyakko.hs
@@ -1,358 +0,0 @@-{-# 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.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.IO as T-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--(++.) :: Text -> Text -> Text-(++.) = T.append-{-# INLINE (++.) #-}--(++*) :: 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 <- 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- (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 :: [Text] -> ByteString -> [Map String Text]-inSections xs r = [M.fromList l | l <- clump sections]- where- -- Bring the lists together into groups of comment and groups of code- -- pattern.- sections :: [[Text]]- 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 :: [[Text]] -> [[(String, Text)]]- clump [x] = clump $ ensurePair [x]- clump (x:y:ys) = [ ("docsText", replace x)- , ("codeText", T.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.unpack) [t x, t y]-- -- Replace the beggining comment symbol with nothing- replace :: [Text] -> Text- replace = T.unlines . map (\x ->- let y = T.unpack x- mkReg = mkRegex . (=~ r)- in T.pack $ subRegex (mkReg y) y "")-- -- Make sure the result is in the right pairing order- ensurePair :: [[Text]] -> [[Text]]- ensurePair ys | even (length ys) = ys- | otherwise = appendList [[""]]- where appendList | toBytes ys =~ r = (ys ++)- | otherwise = (++ ys)-- toBytes :: [[Text]] -> ByteString- toBytes = L.pack . T.unpack . head . head--parse :: Maybe (Map String ByteString) -> Text -> [Map String Text]-parse Nothing _ = []-parse (Just src) code = inSections line $ src M.! "comment"- where line = filter ((/=) "#!" . T.take 2) $ T.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 Text]- -> 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 = T.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 Text]- -> String- -> Map String ByteString- -> [Map String Text]-mapSections section highlighted language =- let output = subRegex (mkRegex highlightReplace) highlighted ""- divider = mkRegex . L.unpack $ language M.! "dividerHtml"- fragments = splitRegex divider output- packFrag = T.pack . genericIndex fragments- docText s = toHTML . T.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------ <a class="source" href="$href-link$">$file-name$</a>-sourceTemplate :: [FilePath] -> [(String, String)]-sourceTemplate = map source- where source x = ("source", concat- [ "<a class=\"source\" href=\""- , takeFileName $ destination x- , "\">"- , takeFileName x- , "</a>"- ])---- Produces a list of table rows that split up code and documentation------ <tr id="section-$number$">--- <td class="docs">--- <div class="pilwrap">--- <a class="pilcrow" href="#section-$number$">λ</a>--- </div>--- $doc-html$--- </td>--- <td class="code">--- $code-html$--- </td>--- </tr>-sectionTemplate :: [Map String Text]- -> [Int]- -> [(String, String)]-sectionTemplate section = map sections- where sections x =- let x' = x + 1- sect = section !! x- in ("section", concat- [ "<tr id=\"section-"- , show x'- , "\"><td class=\"docs\">"- , "<div class=\"pilwrap\">"- , "<a class=\"pilcrow\" href=\"#section-"- , show x'- , "\">λ</a></div>"- , T.unpack $ sect M.! "docsHtml"- , "</td><td class=\"code\">"- , T.unpack $ sect M.! "codeHtml"- , "</td></tr>"- ])---- Once all of the code is finished highlighting, we can generate the HTML--- file and write out the documentation. Pass the completed sections into--- the template found in `resources/hyakko.html`-generateHTML :: FilePath -> [Map String Text] -> IO ()-generateHTML src section = do- let title = takeFileName src- dest = destination src- source <- sources- html <- hyakkoTemplate $ concat- [ [("title", title)]- , multiTemplate $ length source- , sourceTemplate source- , sectionTemplate section [0 .. (length section) - 1]- ]- putStrLn $ "hyakko: " ++ src ++ " -> " ++ dest- T.writeFile dest html---- ### Helpers & Setup---- A list of the languages that Hyakko supports, mapping the file extension--- to the name of the Pygments lexer and the symbol that indicates a--- comment. To add another language to Hyakko's repertoire, add it here.-languages :: Map String (Map String ByteString)-languages =- let hashSymbol = ("symbol", "#")- language = M.fromList [- (".hs", M.fromList [- ("name", "haskell"), ("symbol", "--")]),- (".coffee", M.fromList [- ("name", "coffee-script"), hashSymbol]),- (".js", M.fromList [- ("name", "javascript"), ("symbol", "//")]),- (".py", M.fromList [- ("name", "python"), hashSymbol]),- (".rb", M.fromList [- ("name", "ruby"), hashSymbol])- ]- -- Does the line begin with a comment?- hasComments symbol = "^\\s*" ++* symbol ++* "\\s?"- -- The dividing token we feed into Pygments, to delimit the boundaries- -- between sections.- tokenDivider symbol = "\n" ++* symbol ++* "DIVIDER\n"- -- The mirror of `divider_text` that we expect Pygments to return. We- -- can split on this to recover the original sections. **Note**: the- -- class is "c" for Python and "c1" for the other languages- htmlDivider symbol = L.concat [ "\n*<span class=\"c1?\">"- , symbol- , "DIVIDER</span>"- ]- intoMap lang = let symbol = lang M.! "symbol"- in M.insert "comment" (hasComments symbol)- . M.insert "dividerText" (tokenDivider symbol)- $ M.insert "dividerHtml" (htmlDivider symbol) lang-- -- Build out the appropriate matchers and delimiters for each language.- in M.map intoMap language---- Get the current language we're documenting, based on the extension.-getLanguage :: FilePath -> Maybe (Map String ByteString)-getLanguage src = M.lookup (takeExtension src) languages---- Compute the destination HTML path for an input source file path. If the--- source is `lib/example.hs`, the HTML will be at docs/example.html-destination :: FilePath -> FilePath-destination fp = "docs" </> (takeBaseName fp) ++ ".html"---- Create the template that we will use to generate the Hyakko HTML page.-hyakkoTemplate :: [(String, String)] -> IO Text-hyakkoTemplate var = readDataFile "resources/hyakko.html" >>=- return . T.pack . renderTemplate var . T.unpack---- The CSS styles we'd like to apply to the documentation.-hyakkoStyles :: IO Text-hyakkoStyles = readDataFile "resources/hyakko.css"---- The start and end of each Pygments highlight block.-highlightStart, highlightEnd :: Text-highlightStart = "<div class=\"highlight\"><pre>"-highlightEnd = "</pre></div>"--highlightReplace :: String-highlightReplace = T.unpack highlightStart ++ "|" ++ T.unpack highlightEnd---- Reads from resource path given in cabal package-readDataFile :: FilePath -> IO Text-readDataFile = getDataFileName >=> T.readFile---- For each source file passed in as an argument, generate the--- documentation.-sources :: IO [FilePath]-sources = do- args <- getArgs- files <- forM args $ \x -> do- isDir <- doesDirectoryExist x- if isDir then- unpackDirectories x- else- return [x]- return . sort $ concat files---- Turns the directory give into a list of files including all of the files--- in sub-directories.-unpackDirectories :: FilePath -> IO [FilePath]-unpackDirectories d = do- let reg = "[^(^\\.{1,2}$)]" :: ByteString- content <- getDirectoryContents d >>= return . filter (=~ reg)- let content' = map (d </>) content- files <- filterM doesFileExist content'- subdir <- filterM doesDirectoryExist content'- subcontent <- mapM unpackDirectories subdir >>= return . concat- return (files ++ subcontent)---- Run the script.-main :: IO ()-main = do- style <- hyakkoStyles- source <- sources- createDirectoryIfMissing False "docs"- T.writeFile "docs/hyakko.css" style- generateDocumentation source
+ src/Hyakko.lhs view
@@ -0,0 +1,368 @@+**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++ <a class="source" href="$href-link$">$file-name$</a>++> sourceTemplate :: [FilePath] -> [(String, String)]+> sourceTemplate = map source+> where source x = ("source", concat+> [ "<a class=\"source\" href=\""+> , takeFileName $ destination x+> , "\">"+> , takeFileName x+> , "</a>"+> ])++Produces a list of table rows that split up code and documentation++ <tr id="section-$number$">+ <td class="docs">+ <div class="pilwrap">+ <a class="pilcrow" href="#section-$number$">λ</a>+ </div>+ $doc-html$+ </td>+ <td class="code">+ $code-html$+ </td>+ </tr>++> sectionTemplate :: [Map String Text]+> -> [Int]+> -> [(String, String)]+> sectionTemplate section = map sections+> where sections x =+> let x' = x + 1+> sect = section !! x+> in ("section", concat+> [ "<tr id=\"section-"+> , show x'+> , "\"><td class=\"docs\">"+> , "<div class=\"pilwrap\">"+> , "<a class=\"pilcrow\" href=\"#section-"+> , show x'+> , "\">λ</a></div>"+> , T.unpack $ sect M.! "docsHtml"+> , "</td><td class=\"code\">"+> , T.unpack $ sect M.! "codeHtml"+> , "</td></tr>"+> ])++Once all of the code is finished highlighting, we can generate the HTML file+and write out the documentation. Pass the completed sections into the+template found in `resources/hyakko.html`++> generateHTML :: FilePath -> [Map String Text] -> IO ()+> generateHTML src section = do+> let title = takeFileName src+> dest = destination src+> source <- sources+> html <- hyakkoTemplate $ concat+> [ [("title", title)]+> , multiTemplate $ length source+> , sourceTemplate source+> , sectionTemplate section [0 .. (length section) - 1]+> ]+> putStrLn $ "hyakko: " ++ src ++ " -> " ++ dest+> T.writeFile dest html++Helpers & Setup+---------------++A list of the languages that Hyakko supports, mapping the file extension to+the name of the Pygments lexer and the symbol that indicates a comment. To+add another language to Hyakko's repertoire, add it here.++> languages :: Map String (Map String ByteString)+> languages =+> let hashSymbol = ("symbol", "#")+> language = M.fromList [+> (".hs", M.fromList [+> ("name", "haskell"), ("symbol", "--")]),+> (".lhs", M.fromList [+> ("name", "haskell"), ("symbol", "--"),+> ("literate", "True"), ("symbol2", ">")]),+> (".coffee", M.fromList [+> ("name", "coffee-script"), hashSymbol]),+> (".js", M.fromList [+> ("name", "javascript"), ("symbol", "//")]),+> (".py", M.fromList [+> ("name", "python"), hashSymbol]),+> (".rb", M.fromList [+> ("name", "ruby"), hashSymbol])+> ]++Does the line begin with a comment?++> hasComments symbol = "^\\s*" ++* symbol ++* "\\s?"+> intoMap lang = M.insert "comment"+> (hasComments $ lang M.! "symbol")+> lang++Build out the appropriate matchers and delimiters for each language.++> in M.map intoMap language++Get the current language we're documenting, based on the extension.++> getLanguage :: FilePath -> Maybe (Map String ByteString)+> getLanguage src = M.lookup (takeExtension src) languages++Compute the destination HTML path for an input source file path. If the+source is `lib/example.hs`, the HTML will be at docs/example.html++> destination :: FilePath -> FilePath+> destination fp = "docs" </> (takeBaseName fp) ++ ".html"++Create the template that we will use to generate the Hyakko HTML page.++> hyakkoTemplate :: [(String, String)] -> IO Text+> hyakkoTemplate var = readDataFile "resources/hyakko.html" >>=+> return . T.pack . renderTemplate var . T.unpack++The CSS styles we'd like to apply to the documentation.++> hyakkoStyles :: IO Text+> hyakkoStyles = readDataFile "resources/hyakko.css"++Reads from resource path given in cabal package++> readDataFile :: FilePath -> IO Text+> readDataFile = getDataFileName >=> T.readFile++For each source file passed in as an argument, generate the documentation.++> sources :: IO [FilePath]+> sources = do+> args <- getArgs+> files <- forM args $ \x -> do+> isDir <- doesDirectoryExist x+> if isDir then+> unpackDirectories x+> else+> return [x]+> return . sort $ concat files++Turns the directory give into a list of files including all of the files in+sub-directories.++> unpackDirectories :: FilePath -> IO [FilePath]+> unpackDirectories d = do+> let reg = "[^(^\\.{1,2}$)]" :: ByteString+> content <- getDirectoryContents d >>= return . filter (=~ reg)+> let content' = map (d </>) content+> files <- filterM doesFileExist content'+> subdir <- filterM doesDirectoryExist content'+> subcontent <- mapM unpackDirectories subdir >>= return . concat+> return (files ++ subcontent)++Run the script.++> main :: IO ()+> main = do+> style <- hyakkoStyles+> source <- sources+> createDirectoryIfMissing False "docs"+> T.writeFile "docs/hyakko.css" style+> generateDocumentation source