packages feed

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 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'-             , "\">&#955;</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'+>              , "\">&#955;</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