BlogLiterately 0.8.6.3 → 0.8.7
raw patch · 8 files changed
+132/−90 lines, 8 filesdep ~basedep ~lensdep ~pandocPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: base, lens, pandoc, pandoc-citeproc, pandoc-types
API changes (from Hackage documentation)
- Text.BlogLiterately.Block: onTag :: String -> (Attr -> String -> a) -> (Block -> a) -> Block -> a
+ Text.BlogLiterately.Block: onTag :: Text -> (Attr -> Text -> a) -> (Block -> a) -> Block -> a
- Text.BlogLiterately.Block: unTag :: String -> (Maybe String, String)
+ Text.BlogLiterately.Block: unTag :: Text -> (Maybe Text, Text)
- Text.BlogLiterately.Highlight: colourIt :: Bool -> String -> String
+ Text.BlogLiterately.Highlight: colourIt :: Bool -> Text -> String
- Text.BlogLiterately.Highlight: litify :: String -> String
+ Text.BlogLiterately.Highlight: litify :: Text -> Text
Files
- BlogLiterately.cabal +7/−7
- CHANGES.md +6/−0
- src/Text/BlogLiterately/Block.hs +14/−12
- src/Text/BlogLiterately/Ghci.hs +6/−3
- src/Text/BlogLiterately/Highlight.hs +28/−20
- src/Text/BlogLiterately/Image.hs +8/−3
- src/Text/BlogLiterately/LaTeX.hs +13/−7
- src/Text/BlogLiterately/Transform.hs +50/−38
BlogLiterately.cabal view
@@ -1,5 +1,5 @@ Name: BlogLiterately-Version: 0.8.6.3+Version: 0.8.7 Synopsis: A tool for posting Haskelly articles to blogs Description: Write blog posts in Markdown format, then use BlogLiterately to do syntax highlighting, format ghci sessions, and upload@@ -26,7 +26,7 @@ Maintainer: Brent Yorgey <byorgey@gmail.com> Stability: experimental Build-Type: Simple-Tested-With: GHC ==8.0.2, GHC ==8.2.2, GHC ==8.4.4, GHC ==8.6.3+Tested-With: GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.3 || ==8.10.1 Extra-Source-Files: CHANGES.md README.markdown doc/BlogLiteratelyDoc.lhs@@ -39,7 +39,7 @@ location: git://github.com/byorgey/BlogLiterately.git Library- Build-Depends: base >= 4.0 && < 4.13,+ Build-Depends: base >= 4.0 && < 4.15, process, filepath, directory,@@ -58,12 +58,12 @@ blaze-html >= 0.5 && < 0.10, cmdargs >= 0.9.5 && < 0.11, haxr >= 3000.11 && < 3000.12,- pandoc >= 2.0 && < 2.8,- pandoc-types >= 1.16 && < 1.20,- pandoc-citeproc >= 0.1.2 && < 0.17,+ pandoc >= 2.0 && < 2.11,+ pandoc-types >= 1.20 && < 1.22,+ pandoc-citeproc >= 0.1.2 && < 0.18, highlighting-kate >= 0.5 && < 0.7, data-default >= 0.5 && < 0.8,- lens >= 3.8 && < 4.18,+ lens >= 3.8 && < 4.20, tagsoup >= 0.13.4 && < 0.15, HTTP >= 4000.3 && < 4000.4 Exposed-modules: Text.BlogLiterately
CHANGES.md view
@@ -1,3 +1,9 @@+0.8.7 (21 July 2020)+--------------------++ - Add support for GHC 8.8, 8.10+ - Switch to `pandoc-2.9` and `text`+ 0.8.6.3 (4 March 2019) ----------------------
src/Text/BlogLiterately/Block.hs view
@@ -16,21 +16,26 @@ , onTag ) where -import Data.Char (toLower)-import Text.Pandoc (Attr, Block (CodeBlock))-import Text.ParserCombinators.Parsec+import Data.Text (Text)+import qualified Data.Text as T+import Text.Pandoc (Attr, Block (CodeBlock))+import Text.Parsec+import Text.Parsec.Text --- | Given a block, if begins with a tag in square brackets, strip off+-- switch to megaparsec, use e.g. https://hackage.haskell.org/package/megaparsec-8.0.0/docs/Text-Megaparsec.html#v:takeWhileP ?++-- | Given a block, if it begins with a tag in square brackets, strip off -- the tag and return a pair consisting of the tag and de-tagged -- block. Otherwise, return @Nothing@ and the unchanged block.-unTag :: String -> (Maybe String, String)+unTag :: Text -> (Maybe Text, Text) unTag s = either (const (Nothing, s)) id $ parse tag "" s where+ tag :: Parser (Maybe Text, Text) tag = do- tg <- between (char '[') (char ']') $ many $ noneOf "[]"+ tg <- T.pack <$> (between (char '[') (char ']') $ many $ noneOf "[]") skipMany $ oneOf " \t" _ <- (string "\r\n" <|> string "\n")- txt <- many $ anyToken+ txt <- T.pack <$> many anyToken eof return (Just tg, txt) @@ -38,13 +43,10 @@ -- blocks with a tag matching the given tag (case insensitive). On -- any other blocks (which don't have a matching tag, or are not code -- blocks), run the other function.-onTag :: String -> (Attr -> String -> a) -> (Block -> a) -> Block -> a+onTag :: Text -> (Attr -> Text -> a) -> (Block -> a) -> Block -> a onTag t f def b@(CodeBlock attr@(_, as, _) s)- | lowercase t `elem` map lowercase (maybe id (:) tag $ as)+ | T.toLower t `elem` map T.toLower (maybe id (:) tag $ as) = f attr src | otherwise = def b where (tag, src) = unTag s onTag _ _ def b = def b--lowercase :: String -> String-lowercase = map toLower
src/Text/BlogLiterately/Ghci.hs view
@@ -41,6 +41,8 @@ import Data.Char (isSpace) import Data.Functor ((<$>)) import Data.List (intercalate, isPrefixOf)+import Data.Text (Text)+import qualified Data.Text as T import System.FilePath (takeFileName) import System.IO import qualified System.IO.Strict as Strict@@ -194,14 +196,14 @@ formatInlineGhci f = withGhciProcess f . bottomUpM formatInlineGhci' where formatInlineGhci' :: Block -> ReaderT ProcessInfo IO Block- formatInlineGhci' = onTag "ghci" formatGhciBlock return+ formatInlineGhci' = onTag (T.pack "ghci") formatGhciBlock return formatGhciBlock attr src = do let inputs = parseGhciInputs src results <- zipWith GhciLine inputs <$> mapM ghciEval inputs- return $ CodeBlock attr (intercalate "\n" $ map formatGhciResult results)+ return $ CodeBlock attr (T.intercalate (T.pack "\n") $ map (T.pack . formatGhciResult) results) -parseGhciInputs :: String -> [GhciInput]+parseGhciInputs :: Text -> [GhciInput] parseGhciInputs = map mkGhciInput . split ( dropInitBlank@@ -210,6 +212,7 @@ $ whenElt (not . (" " `isPrefixOf`)) ) . lines+ . T.unpack mkGhciInput :: [String] -> GhciInput mkGhciInput [] = GhciInput "" Nothing
src/Text/BlogLiterately/Highlight.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} -----------------------------------------------------------------------------@@ -26,6 +27,12 @@ , colourisePandoc ) where +-- xmlParse (from HaXmL) uses String+-- hscolour uses String++import Data.Text (Text)+import qualified Data.Text as T+ import Control.Lens (makePrisms) import Control.Monad (liftM) import Data.Char (toLower)@@ -39,7 +46,7 @@ import Text.Highlighting.Kate import Text.Pandoc.Definition import Text.Pandoc.Shared (safeRead)-import Text.XML.HaXml hiding (find, attr, html)+import Text.XML.HaXml hiding (attr, find, html) import Text.XML.HaXml.Posn (noPos) import Text.BlogLiterately.Block (unTag)@@ -67,9 +74,9 @@ The `Attr` component has metadata about what's in the code block: [haskell]- type Attr = ( String, -- code block identifier- , [String] -- list of code classes- , [(String, String)] -- name/value pairs+ type Attr = ( Text, -- code block identifier+ , [Text] -- list of code classes+ , [(Text, Text)] -- name/value pairs ) Thanks to some feedback from the Pandoc author, John MacFarlane, I@@ -147,9 +154,9 @@ -- | Use hscolour to syntax highlight some Haskell code. The first -- argument indicates whether the code is literate Haskell.-colourIt :: Bool -> String -> String+colourIt :: Bool -> Text -> String colourIt literate srcTxt =- wrapCode $ hscolour CSS defaultColourPrefs False True "" literate srcTxt'+ wrapCode $ hscolour CSS defaultColourPrefs False True "" literate (T.unpack srcTxt') where srcTxt' | literate = litify srcTxt | otherwise = srcTxt -- wrap the result in a <pre><code> tag, similar to@@ -164,8 +171,8 @@ `when` tag "pre" -- | Prepend literate Haskell markers to some source code.-litify :: String -> String-litify = unlines . map ("> " ++) . lines+litify :: Text -> Text+litify = T.unlines . map (T.append "> ") . T.lines {- Hscolour uses HTML `span` elements and CSS classes like 'hs-keyword'@@ -259,8 +266,8 @@ | ctag == Just "haskell" || haskell = case hsHighlight of HsColourInline style ->- rawHtml $ bakeStyles style $ colourIt lit src- HsColourCSS -> rawHtml $ colourIt lit src+ rawHtmlT . bakeStyles style $ colourIt lit src+ HsColourCSS -> rawHtmlT $ colourIt lit src HsNoHighlight -> rawHtml $ simpleHTML hsrc HsKate -> case ctag of Nothing -> myHighlightK attr hsrc@@ -283,11 +290,12 @@ | otherwise = src lit = "sourceCode" `elem` classes haskell = "haskell" `elem` classes- simpleHTML h = "<pre><code>" ++ h ++ "</code></pre>"+ simpleHTML h = T.append "<pre><code>" (T.append h "</code></pre>") myHighlightK attrs h = case highlight formatHtmlBlock attrs h of- Nothing -> rawHtml $ simpleHTML s- Just html -> rawHtml $ replaceBreaks $ renderHtml html- rawHtml = RawBlock (Format "html")+ Nothing -> rawHtml $ simpleHTML s+ Just html -> rawHtmlT $ replaceBreaks $ renderHtml html+ rawHtmlT = rawHtml . T.pack+ rawHtml = RawBlock (Format "html") colouriseCodeBlock _ _ b = b @@ -311,21 +319,21 @@ lcLanguages = map (map toLower) languages highlight :: (FormatOptions -> [SourceLine] -> a) -- ^ Formatter- -> Attr -- ^ Attributes of the CodeBlock- -> String -- ^ Raw contents of the CodeBlock+ -> Attr -- ^ Attributes of the CodeBlock+ -> Text -- ^ Raw contents of the CodeBlock -> Maybe a -- ^ Maybe the formatted result highlight formatter (_, classes, keyvals) rawCode = let firstNum = case safeRead (fromMaybe "1" $ lookup "startFrom" keyvals) of- Just n -> n+ Just n -> n Nothing -> 1 fmtOpts = defaultFormatOpts{ startNumber = firstNum, numberLines = any (`elem` ["number","numberLines", "number-lines"]) classes }- lcclasses = map (map toLower) classes+ lcclasses = map (T.unpack . T.toLower) classes in case find (`elem` lcLanguages) lcclasses of Nothing -> Nothing Just language -> Just $ formatter fmtOpts{ codeClasses = [language],- containerClasses = classes }- $ highlightAs language rawCode+ containerClasses = map T.unpack classes }+ $ highlightAs language (T.unpack rawCode)
src/Text/BlogLiterately/Image.hs view
@@ -19,6 +19,8 @@ , mkMediaObject ) where +import qualified Data.Text as T+ import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.State (StateT, get, modify, runStateT)@@ -55,21 +57,24 @@ _ -> return p where uploadOneImage :: String -> Inline -> StateT (M.Map FilePath URL) IO Inline- uploadOneImage xmlrpc i@(Image attr altText (imgUrl, imgTitle))+ uploadOneImage xmlrpc i@(Image attr altText (imgUrlT, imgTitle)) | isLocal imgUrl = do uploaded <- get case M.lookup imgUrl uploaded of- Just url -> return $ Image attr altText (url, imgTitle)+ Just url -> return $ Image attr altText (T.pack url, imgTitle) Nothing -> do res <- lift $ uploadIt xmlrpc imgUrl bl case res of Just (ValueStruct (lookup "url" -> Just (ValueString newUrl))) -> do modify (M.insert imgUrl newUrl)- return $ Image attr altText (newUrl, imgTitle)+ return $ Image attr altText (T.pack newUrl, imgTitle) _ -> do liftIO . putStrLn $ "Warning: upload of " ++ imgUrl ++ " failed." return i | otherwise = return i+ where+ imgUrl = T.unpack imgUrlT+ uploadOneImage _ i = return i isLocal imgUrl = none (`isPrefixOf` imgUrl) ["http", "/"]
src/Text/BlogLiterately/LaTeX.hs view
@@ -10,26 +10,32 @@ -- ----------------------------------------------------------------------------- +{-# LANGUAGE OverloadedStrings #-}+ module Text.BlogLiterately.LaTeX ( rawTeXify , wpTeXify ) where -import Data.List (isPrefixOf)+import Data.Text (Text)+import qualified Data.Text as T import Text.Pandoc +bracket :: Text -> Text -> Text -> Text+bracket l r t = T.append l (T.append t r)+ -- | Pass LaTeX through unchanged. rawTeXify :: Pandoc -> Pandoc rawTeXify = bottomUp formatDisplayTex . bottomUp formatInlineTex where formatInlineTex :: [Inline] -> [Inline] formatInlineTex (Math InlineMath tex : is)- = (RawInline (Format "html") ("$" ++ tex ++ "$")) : is+ = (RawInline (Format "html") (bracket "$" "$" tex)) : is formatInlineTex is = is formatDisplayTex :: [Block] -> [Block] formatDisplayTex (Para [Math DisplayMath tex] : bs)- = RawBlock (Format "html") ("\n\\[" ++ tex ++ "\\]\n")+ = RawBlock (Format "html") (bracket "\n\\[" "\\]\n" tex) : bs formatDisplayTex bs = bs @@ -40,17 +46,17 @@ wpTeXify = bottomUp formatDisplayTex . bottomUp formatInlineTex where formatInlineTex :: [Inline] -> [Inline] formatInlineTex (Math InlineMath tex : is)- = (Str $ "$latex " ++ unPrefix "latex" tex ++ "$") : is+ = (Str $ bracket "$latex " "$" (unPrefix "latex" tex)) : is formatInlineTex is = is formatDisplayTex :: [Block] -> [Block] formatDisplayTex (Para [Math DisplayMath tex] : bs) = RawBlock (Format "html") "<p><div style=\"text-align: center\">"- : Plain [Str $ "$latex " ++ "\\displaystyle " ++ unPrefix "latex" tex ++ "$"]+ : Plain [Str $ bracket "$latex \\displaystyle " "$" (unPrefix "latex" tex)] : RawBlock (Format "html") "</div></p>" : bs formatDisplayTex bs = bs unPrefix pre s- | pre `isPrefixOf` s = drop (length pre) s- | otherwise = s+ | pre `T.isPrefixOf` s = T.drop (T.length pre) s+ | otherwise = s
src/Text/BlogLiterately/Transform.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeOperators #-}@@ -276,7 +277,7 @@ -- -- is a simple 'SpecialLink' which causes links of the form -- @twitter::user@ to be replaced by @https://twitter.com/user@.-type SpecialLink = (String, String -> BlogLiterately -> IO String)+type SpecialLink = (Text, Text -> BlogLiterately -> IO Text) -- | Create a transformation which looks for the given special links -- and replaces them appropriately. You can use this function with@@ -292,7 +293,7 @@ specialLink :: Inline -> IO Inline specialLink i@(Link attrs alt (url, title)) | Just (typ, target) <- getSpecial url- = mkLink <$> case lookup (map toLower typ) links of+ = mkLink <$> case lookup (T.toLower typ) links of Just mkURL -> mkURL target bl Nothing -> return target where@@ -301,9 +302,9 @@ specialLink i = return i getSpecial url- | "::" `isInfixOf` url =- let (typ:rest) = splitOn "::" url- in Just (typ, intercalate "::" rest)+ | "::" `T.isInfixOf` url =+ let (typ:rest) = T.splitOn "::" url+ in Just (typ, T.intercalate "::" rest) | otherwise = Nothing -- | Turn @lucky::<search>@ into a link to the first Google result for@@ -311,12 +312,16 @@ luckyLink :: SpecialLink luckyLink = ("lucky", getLucky) where+ getLucky :: Text -> BlogLiterately -> IO Text getLucky searchTerm _ = do- results <- openURL $ "http://www.google.com/search?q=" ++ searchTerm+ results <- openURL $ "http://www.google.com/search?q=" ++ (T.unpack searchTerm) let tags = parseTags results- anchor = take 1 . dropWhile (~/= "<a>") . dropWhile (~/= "<h3 class='r'>") $ tags+ anchor = take 1+ . dropWhile (~/= ("<a>" :: String))+ . dropWhile (~/= ("<h3 class='r'>" :: String))+ $ tags url = case anchor of- [t@(TagOpen{})] -> takeWhile (/='&') . dropWhile (/='h') . fromAttrib "href" $ t+ [t@(TagOpen{})] -> T.pack . takeWhile (/='&') . dropWhile (/='h') . fromAttrib "href" $ t _ -> searchTerm return url @@ -327,7 +332,7 @@ -- | Given @wiki::<title>@, generate a link to the Wikipedia page for -- @<title>@. Note that the page is not checked for existence. wikiLink :: SpecialLink-wikiLink = ("wiki", \target _ -> return $ "https://en.wikipedia.org/wiki/" ++ target)+wikiLink = ("wiki", \target _ -> return $ T.append "https://en.wikipedia.org/wiki/" target) -- | @postLink@ handles two types of special links. --@@ -341,12 +346,13 @@ postLink :: SpecialLink postLink = ("post", getPostLink) where+ getPostLink :: Text -> BlogLiterately -> IO Text getPostLink target bl =- fromMaybe target <$>- case (all isDigit target, bl ^. blog) of+ (fromMaybe target . fmap T.pack) <$>+ case (T.all isDigit target, bl ^. blog) of (_ , Nothing ) -> return Nothing- (True , Just url) -> getPostURL url target (user' bl) (password' bl)- (False, Just url) -> findTitle 20 url target (user' bl) (password' bl)+ (True , Just url) -> getPostURL url (T.unpack target) (user' bl) (password' bl)+ (False, Just url) -> findTitle 20 url (T.unpack target) (user' bl) (password' bl) -- If all digits, replace with permalink for that postid -- Otherwise, search titles of 20 most recent posts.@@ -363,10 +369,11 @@ githubLink :: SpecialLink githubLink = ("github", getGithubLink) where- getGithubLink target bl =- case splitOn "/" target of- (user : repo : ghTarget) -> return $ github </> user </> repo </> mkTarget ghTarget- _ -> return $ github </> target+ getGithubLink :: Text -> BlogLiterately -> IO Text+ getGithubLink target bl = return . T.pack $+ case splitOn "/" (T.unpack target) of+ (user : repo : ghTarget) -> github </> user </> repo </> mkTarget ghTarget+ _ -> github </> (T.unpack target) github = "https://github.com/" mkTarget [] = "" mkTarget (('@': hash) : _) = "commit" </> hash@@ -377,7 +384,8 @@ hackageLink :: SpecialLink hackageLink = ("hackage", getHackageLink) where- getHackageLink pkg bl = return $ hackagePrefix ++ pkg+ getHackageLink :: Text -> BlogLiterately -> IO Text+ getHackageLink pkg bl = return $ T.append hackagePrefix pkg hackagePrefix = "http://hackage.haskell.org/package/" -- | Potentially extract a title from the metadata block, and set it@@ -389,9 +397,9 @@ (Pandoc (Meta m) _) <- gets snd case M.lookup "title" m of Just (MetaString s) ->- setTitle s+ setTitle (T.unpack s) Just (MetaInlines is) ->- setTitle (intercalate " " [s | Str s <- is])+ setTitle (intercalate " " [T.unpack s | Str s <- is]) _ -> return () -- title set explicitly with --title takes precedence.@@ -412,7 +420,7 @@ -- options record. If the blog is not tagged with @[BLOpts]@ these -- will just be empty. extractOptions :: Block -> ([ParseError], BlogLiterately)-extractOptions = onTag "blopts" (const readBLOptions) (const mempty)+extractOptions = onTag "blopts" (const (readBLOptions . T.unpack)) (const mempty) -- | Delete any blocks tagged with @[BLOpts]@. killOptionBlocks :: Block -> Block@@ -533,14 +541,17 @@ -- | Transform a complete input document string to an HTML output -- string, given a list of transformation passes. xformDoc :: BlogLiterately -> [Transform] -> String -> IO (Either PandocError (BlogLiterately, String))-xformDoc bl xforms = runIO .+xformDoc bl xforms s = do+ Right tpl <- compileTemplate "" blHtmlTemplate+ runIO . ( fixLineEndings >>> T.pack >>> parseFile parseOpts >=> (liftIO . runTransforms xforms bl)- >=> (\(bl', p) -> (bl',) <$> writeHtml5String (writeOpts bl') p)+ >=> (\(bl', p) -> (bl',) <$> writeHtml5String (writeOpts bl' tpl) p) >=> _2 (return . T.unpack) )+ $ s where parseFile :: ReaderOptions -> Text -> PandocIO Pandoc parseFile opts =@@ -572,44 +583,45 @@ -- Ext_pandoc_title_block -- Ext_citations }- writeOpts bl = def- { writerReferenceLinks = True+ writeOpts bl tpl = def+ { writerReferenceLinks = True , writerTableOfContents = toc' bl- , writerHTMLMathMethod =+ , writerHTMLMathMethod = case math' bl of "" -> PlainMath- opt -> mathOption opt- , writerTemplate = Just blHtmlTemplate+ opt -> mathOption (T.pack opt)+ , writerTemplate = Just tpl } mathOption opt- | opt `isPrefixOf` "mathml" = MathML- | opt `isPrefixOf` "mimetex" =+ | opt `T.isPrefixOf` "mathml" = MathML+ | opt `T.isPrefixOf` "mimetex" = WebTeX (mathUrl "/cgi-bin/mimetex.cgi?" opt)- | opt `isPrefixOf` "webtex" = WebTeX (mathUrl webTeXURL opt)- | opt `isPrefixOf` "mathjax" = MathJax (mathUrl mathJaxURL opt)+ | opt `T.isPrefixOf` "webtex" = WebTeX (mathUrl webTeXURL opt)+ | opt `T.isPrefixOf` "mathjax" = MathJax (mathUrl mathJaxURL opt) | otherwise = PlainMath webTeXURL = "http://chart.apis.google.com/chart?cht=tx&chl="- mathJaxURL = "http://cdn.mathjax.org/mathjax/latest/MathJax.js"- ++ "?config=TeX-AMS-MML_HTMLorMML"+ mathJaxURL = T.append "http://cdn.mathjax.org/mathjax/latest/MathJax.js"+ "?config=TeX-AMS-MML_HTMLorMML" - urlPart = drop 1 . dropWhile (/='=')+ urlPart :: Text -> Text+ urlPart = T.drop 1 . T.dropWhile (/='=') mathUrl dflt opt = case urlPart opt of "" -> dflt; x -> x -- | Turn @CRLF@ pairs into a single @LF@. This is necessary since -- 'readMarkdown' is picky about line endings. fixLineEndings :: String -> String-fixLineEndings [] = []+fixLineEndings [] = [] fixLineEndings ('\r':'\n':cs) = '\n':fixLineEndings cs-fixLineEndings (c:cs) = c:fixLineEndings cs+fixLineEndings (c:cs) = c:fixLineEndings cs -- We use a special template with the "standalone" pandoc writer. We -- don't actually want truly "standalone" HTML documents because they -- have to sit inside another web page. But we do want things like -- math typesetting and a table of contents.-blHtmlTemplate = unlines+blHtmlTemplate = T.unlines [ "$if(highlighting-css)$" , " <style type=\"text/css\">" , "$highlighting-css$"