diff --git a/Shake.hs b/Shake.hs index 47400cc06..977dd25a7 100755 --- a/Shake.hs +++ b/Shake.hs @@ -19,9 +19,10 @@ Requires: https://www.haskell.org/downloads#stack Shake notes: wishlist: + just one shake import wildcards in phony rules multiple individually accessible wildcards - just one shake import + not having to write :: Action ExitCode after a non-final cmd -} {-# LANGUAGE PackageImports, QuasiQuotes #-} @@ -41,9 +42,10 @@ usage = [i|Usage: ./Shake.hs compile # compile this script (optional) ./Shake --help # show options, eg --color ./Shake # show commands - ./Shake docs # generate all docs + ./Shake site # generate things needed for the website ./Shake manpages # generate nroff files for man ./Shake webmanpages # generate web man pages for hakyll + ./Shake webmanual # generate combined web man page for hakyll ./Shake m4manpages # generate nroff files for man (alternate method) ./Shake m4webmanpages # generate web man pages for hakyll (alternate method) |] @@ -52,15 +54,15 @@ buildDir = ".build" pandoc = -- "stack exec -- pandoc" -- use the pandoc required above "pandoc" -- use pandoc in PATH (faster) -manpages = [ - "hledger_csv.5" - ,"hledger_journal.5" - ,"hledger_timedot.5" - ,"hledger_timelog.5" - ,"hledger.1" - ,"hledger-api.1" +manpages = [ -- in suggested reading order + "hledger.1" ,"hledger-ui.1" ,"hledger-web.1" + ,"hledger-api.1" + ,"hledger_journal.5" + ,"hledger_csv.5" + ,"hledger_timelog.5" + ,"hledger_timedot.5" ] manpageDir p @@ -92,13 +94,17 @@ main = do -- docs - -- method 1: - - phony "docs" $ need [ + phony "site" $ need [ "manpages" ,"webmanpages" + ,"m4manpages" + ,"m4webmanpages" + ,"site/manual2.md" ] + -- man pages + -- method 1: + -- pandoc filters, these adjust master md files for web or man output phony "pandocfilters" $ need pandocFilters pandocFilters |%> \out -> do @@ -127,15 +133,13 @@ main = do let p = dropExtension $ takeFileName out md = manpageDir p > p <.> "md" need $ md : pandocFilters - cmd pandoc md "-o" out "--filter doc/pandoc-drop-man-blocks" + cmd pandoc md "-o" out + "--filter doc/pandoc-demote-headers" + -- "--filter doc/pandoc-add-toc" + -- "--filter doc/pandoc-drop-man-blocks" -- method 2: - phony "m4docs" $ need [ - "m4manpages" - ,"m4webmanpages" - ] - -- man pages assembled from parts and adjusted for man with m4, adjusted more and converted to nroff with pandoc let m4manpageNroffs = [manpageDir p > "m4-"++p | p <- ["hledger.1"]] phony "m4manpages" $ need m4manpageNroffs @@ -146,7 +150,8 @@ main = do m4lib = "doc/lib.m4" tmpl = "doc/manpage.nroff" need $ m4src : m4lib : tmpl : pandocFilters ++ m4includes - cmd Shell "m4 -P" "-DMAN" "-I" dir m4lib m4src "|" pandoc "-s --template" tmpl "-o" out + cmd Shell "m4 -P" "-DMAN" "-I" dir m4lib m4src + "|" pandoc "-s --template" tmpl "-o" out "--filter doc/pandoc-drop-html-blocks" "--filter doc/pandoc-drop-html-inlines" "--filter doc/pandoc-drop-links" @@ -164,17 +169,35 @@ main = do m4includes = map (dir >) ["description.md","examples.md","queries.md","commands.md","options.md"] m4lib = "doc/lib.m4" need $ m4src : m4lib : m4includes - cmd Shell "m4 -P" "-DWEB" "-I" dir m4lib m4src "|" pandoc "-o" out + cmd Shell "m4 -P" "-DMAN -DWEB" "-I" dir m4lib m4src + "|" pandoc "-o" out + "--filter doc/pandoc-demote-headers" + -- "--filter doc/pandoc-add-toc" + + -- web manual combined from man pages + + let webmanual = "site/manual2.md" + phony "webmanual" $ need [ webmanual ] + "site/manual2.md" %> \out -> do + need webManpageMds + cmd Shell "printf '* toc\\n\\n' >" webmanual :: Action ExitCode + forM_ webManpageMds $ \f -> do + let manpage = dropExtension $ takeFileName f + cmd Shell ("printf '\\n## "++ manpage ++"\\n\\n' >>") webmanual :: Action ExitCode + cmd Shell "pandoc" f "-t markdown" + "--filter doc/pandoc-drop-toc" + "--filter doc/pandoc-demote-headers" + ">>" webmanual :: Action ExitCode -- cleanup phony "clean" $ do putNormal "Cleaning generated files" -- removeFilesAfter "." manpageNroffs - removeFilesAfter "." webManpageMds removeFilesAfter "." m4manpageNroffs - removeFilesAfter "." $ map (<.> "md") m4manpageNroffs + removeFilesAfter "." webManpageMds removeFilesAfter "." m4webManpageMds + removeFilesAfter "." [webmanual] phony "Clean" $ do need ["clean"] diff --git a/doc/.gitignore b/doc/.gitignore index e27154fd8..d29e05367 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -1,7 +1,9 @@ +pandoc-add-toc pandoc-capitalize-headers pandoc-drop-html-blocks pandoc-drop-html-inlines pandoc-drop-links pandoc-drop-man-blocks pandoc-drop-notes +pandoc-drop-toc pandoc-drop-web-blocks diff --git a/doc/pandoc-add-toc.hs b/doc/pandoc-add-toc.hs new file mode 100644 index 000000000..5926e50ce --- /dev/null +++ b/doc/pandoc-add-toc.hs @@ -0,0 +1,121 @@ +#!/usr/bin/env stack +{- stack runghc --verbosity info + --package pandoc +-} +-- Replace a table of contents marker +-- (a bullet list item containing "toc[-N[-M]]") +-- with a table of contents based on headings. +-- toc means full contents, toc-N means contents to depth N +-- and toc-N-M means contents from depth N to depth M. +-- Based on code from https://github.com/blaenk/blaenk.github.io + +{-# LANGUAGE OverloadedStrings #-} + +import Data.Char (isDigit) +import Data.List (groupBy) +import Data.List.Split +import Data.Tree (Forest, Tree(Node)) +import Data.Monoid ((<>), mconcat) +import Data.Function (on) +import Data.Maybe (fromMaybe) +import Safe +import Text.Blaze.Html (preEscapedToHtml, (!)) +import Text.Blaze.Html.Renderer.String (renderHtml) +import qualified Text.Blaze.Html5 as H +import qualified Text.Blaze.Html5.Attributes as A +import Text.Pandoc +import Text.Pandoc.JSON +import Text.Pandoc.Walk (walk, query) + +main :: IO () +main = toJSONFilter tableOfContents + +tableOfContents :: Pandoc -> Pandoc +tableOfContents doc = + let headers = query collectHeaders doc + in walk (generateTOC headers) doc + +collectHeaders :: Block -> [Block] +collectHeaders header@(Header _ (_, classes, _) _) + | "notoc" `elem` classes = [] + | otherwise = [header] +collectHeaders _ = [] + +generateTOC :: [Block] -> Block -> Block +generateTOC [] x = x +generateTOC headers x@(BulletList (( (( Plain ((Str txt):_)):_)):_)) = + case tocParams txt of + Just (mstartlevel, mendlevel) -> + render . + forestDrop mstartlevel . + forestPrune mendlevel . + groupByHierarchy $ + headers -- (! A.class_ "right-toc") . + where + render = (RawBlock "html") . renderHtml . createTable + Nothing -> x +generateTOC _ x = x + +tocParams :: String -> Maybe (Maybe Int, Maybe Int) +tocParams s = + case splitOn "-" s of + ["toc"] -> Just (Nothing, Nothing) + ["toc",a] | all isDigit a -> Just (Nothing, readMay a) + ["toc",a,b] | all isDigit a, all isDigit b -> Just (readMay a, readMay b) + _ -> Nothing + +forestDrop :: Maybe Int -> Forest a -> Forest a +forestDrop Nothing f = f +forestDrop (Just n) ts = concatMap (treeDrop n) ts + +treeDrop :: Int -> Tree a -> Forest a +treeDrop n t | n < 1 = [t] +treeDrop n (Node _ ts) = concatMap (treeDrop (n-1)) ts + +forestPrune :: Maybe Int -> Forest a -> Forest a +forestPrune Nothing f = f +forestPrune (Just n) ts = map (treePrune n) ts + +treePrune :: Int -> Tree a -> Tree a +treePrune n t | n < 1 = t +treePrune n (Node v ts) = Node v $ map (treePrune (n-1)) ts + +-- | remove all nodes past a certain depth +-- treeprune :: Int -> Tree a -> Tree a +-- treeprune 0 t = Node (root t) [] +-- treeprune d t = Node (root t) (map (treeprune $ d-1) $ branches t) + +groupByHierarchy :: [Block] -> Forest Block +groupByHierarchy = map (\(x:xs) -> Node x (groupByHierarchy xs)) . groupBy ((<) `on` headerLevel) + +headerLevel :: Block -> Int +headerLevel (Header level _ _) = level +headerLevel _ = error "not a header" + +createTable :: Forest Block -> H.Html +createTable headers = + (H.nav ! A.id "toc") $ do + H.p "Contents" + H.ol $ markupHeaders headers + +markupHeader :: Tree Block -> H.Html +markupHeader (Node (Header _ (ident, _, keyvals) inline) headers) + | headers == [] = H.li $ link + | otherwise = H.li $ link <> (H.ol $ markupHeaders headers) + where render x = writeHtmlString def (Pandoc nullMeta [(Plain x)]) + section = fromMaybe (render inline) (lookup "toc" keyvals) + link = H.a ! A.href (H.toValue $ "#" ++ ident) $ preEscapedToHtml section +markupHeader _ = error "what" + +markupHeaders :: Forest Block -> H.Html +markupHeaders = mconcat . map markupHeader + +-- ignoreTOC :: Block -> Block +-- ignoreTOC (Header level (ident, classes, params) inline) = +-- Header level (ident, "notoc" : classes, params) inline +-- ignoreTOC x = x + +-- removeTOCMarker :: Block -> Block +-- removeTOCMarker (BulletList (( (( Plain ((Str "toc"):_)):_)):_)) = Null +-- removeTOCMarker x = x + diff --git a/doc/pandoc-drop-toc.hs b/doc/pandoc-drop-toc.hs new file mode 100644 index 000000000..690616a54 --- /dev/null +++ b/doc/pandoc-drop-toc.hs @@ -0,0 +1,40 @@ +#!/usr/bin/env stack +{- stack runghc --verbosity info + --package pandoc +-} +-- Remove a table of contents marker +-- (a bullet list item containing "toc[-N[-M]]") + +{-# LANGUAGE OverloadedStrings #-} + +import Data.Char (isDigit) +import Data.List.Split +import Data.Maybe +import Safe +import Text.Pandoc.JSON + +main :: IO () +main = toJSONFilter dropToc + +dropHtmlBlocks :: Block -> Block +dropHtmlBlocks (RawBlock (Format "html") _) = Plain [] +dropHtmlBlocks x = x + + -- BulletList + -- [ [Plain [Str "toc"]] ] +dropToc :: Block -> Block +dropToc (BulletList is) = + BulletList $ filter (not.null) $ map (filter isNotToc) is + where + isNotToc (Plain [Str s]) | isJust $ tocParams s = False + isNotToc _ = True +dropToc x = x + +tocParams :: String -> Maybe (Maybe Int, Maybe Int) +tocParams s = + case splitOn "-" s of + ["toc"] -> Just (Nothing, Nothing) + ["toc",a] | all isDigit a -> Just (Nothing, readMay a) + ["toc",a,b] | all isDigit a, all isDigit b -> Just (readMay a, readMay b) + _ -> Nothing + diff --git a/hledger-api/doc/hledger-api.1.md b/hledger-api/doc/hledger-api.1.md index cc82befd1..eae0f97a2 100644 --- a/hledger-api/doc/hledger-api.1.md +++ b/hledger-api/doc/hledger-api.1.md @@ -2,6 +2,9 @@ % % January 2016 +