diff --git a/Makefile b/Makefile index 4f068c31d..c1c77f1d4 100644 --- a/Makefile +++ b/Makefile @@ -817,19 +817,27 @@ $(call def-help-subsection,DOCUMENTATION:) # \ # ) -site: \ - $(call def-help,site, generate the hledger.org website with hakyll-std (a generic hakyll script) ) #olddocs - -cd doc/site; hakyll build +doc/site/hakyll-std hakyll-std: \ + doc/site/hakyll-std.hs \ + doc/site/TableOfContents.hs \ + doc/site/hakyll-std.cabal \ + doc/site/stack.yaml \ + $(call def-help,hakyll-std, build a generic hakyll site builder script ) + cd doc/site; stack ghc hakyll-std -site-clean: \ +site: doc/site/hakyll-std \ + $(call def-help,site, generate the hledger.org website with hakyll-std ) + -cd doc/site; ./hakyll-std build + +site-clean: doc/site/hakyll-std \ $(call def-help,site-clean, remove hakyll-generated files (& take down the website) ) #cleanolddocs - -cd doc/site; hakyll clean + -cd doc/site; ./hakyll-std clean # rm -rf doc/site/_site/* -# XXX hakyll preview/watch mostly don't live-update any more -site-preview: \ +# XXX hakyll watch & preview mostly don't live-update any more +site-preview: doc/site/hakyll-std \ $(call def-help,site-preview, run a hakyll server to preview the website ) #doc/site/site - cd doc/site; hakyll preview + -cd doc/site; ./hakyll-std watch # -h hledger.org # site-view: site \ # $(call def-help,site-view,\ diff --git a/doc/site/LICENSE b/doc/site/LICENSE new file mode 100644 index 000000000..c304a9483 --- /dev/null +++ b/doc/site/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2015 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Your name here nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/site/Setup.hs b/doc/site/Setup.hs new file mode 100644 index 000000000..9a994af67 --- /dev/null +++ b/doc/site/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/doc/site/TableOfContents.hs b/doc/site/TableOfContents.hs new file mode 100644 index 000000000..deca3734f --- /dev/null +++ b/doc/site/TableOfContents.hs @@ -0,0 +1,82 @@ +{-# LANGUAGE OverloadedStrings #-} +-- from https://github.com/blaenk/blaenk.github.io + +module TableOfContents ( + tableOfContents, + ignoreTOC, + collectHeaders, + removeTOCMarker +) where + +import Text.Pandoc +import Text.Pandoc.Walk (walk, query) + +import Data.List (groupBy) +import Data.Tree (Forest, Tree(Node)) +import Data.Monoid ((<>), mconcat) +import Data.Function (on) +import Data.Maybe (fromMaybe) + +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 + +headerLevel :: Block -> Int +headerLevel (Header level _ _) = level +headerLevel _ = error "not a header" + +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 + +collectHeaders :: Block -> [Block] +collectHeaders header@(Header _ (_, classes, _) _) = + if "notoc" `elem` classes + then [] + else [header] +collectHeaders _ = [] + +groupByHierarchy :: [Block] -> Forest Block +groupByHierarchy = map (\(x:xs) -> Node x (groupByHierarchy xs)) . groupBy ((<) `on` headerLevel) + +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 + +createTable :: Forest Block -> H.Html +createTable headers = + (H.nav ! A.id "toc") $ do + H.p "Contents" + H.ol $ markupHeaders headers + +generateTOC :: [Block] -> String -> Block -> Block +generateTOC [] _ x = x +generateTOC headers alignment x@(BulletList (( (( Plain ((Str "toc"):_)):_)):_)) + | alignment == "right" = render . (! A.class_ "right-toc") . table $ headers + | alignment == "left" = render . table $ headers + | otherwise = x + where render = (RawBlock "html") . renderHtml + table = createTable . groupByHierarchy +generateTOC _ _ x = x + +tableOfContents :: String -> Pandoc -> Pandoc +tableOfContents alignment ast = + if alignment /= "off" + then let headers = query collectHeaders ast + in walk (generateTOC headers alignment) ast + else walk ignoreTOC ast + diff --git a/doc/site/hakyll-std.cabal b/doc/site/hakyll-std.cabal new file mode 100644 index 000000000..2005f4048 --- /dev/null +++ b/doc/site/hakyll-std.cabal @@ -0,0 +1,39 @@ +name: hakyll-std +version: 0.1.0 +synopsis: Generic hakyll site builder script. +description: Generic hakyll site builder script. +homepage: http://github.com/simonmichael/hledger +license: BSD3 +license-file: LICENSE +author: Simon Michael +maintainer: simon@joyful.com +-- copyright: +category: Web +build-type: Simple +-- extra-source-files: +cabal-version: >=1.10 + +-- library +-- hs-source-dirs: src +-- build-depends: base >= 4.7 && < 5 +-- default-language: Haskell2010 + +executable hakyll-std + default-language: Haskell2010 + ghc-options: -threaded -rtsopts -with-rtsopts=-N + hs-source-dirs: . + main-is: hakyll-std.hs + other-modules: TableOfContents + build-depends: base >= 4.7 && < 5 + ,hakyll >=4.7 + ,pandoc >=1.15 + ,pandoc-types + ,process + ,directory + ,data-default + ,blaze-html + ,containers + +-- source-repository head +-- type: git +-- location: https://github.com/simonmichael/hledger diff --git a/doc/site/hakyll-std.hs b/doc/site/hakyll-std.hs new file mode 100755 index 000000000..8895da3ba --- /dev/null +++ b/doc/site/hakyll-std.hs @@ -0,0 +1,146 @@ +#!/usr/bin/env stack +-- stack --resolver nightly-2015-07-01 --install-ghc runghc --package hakyll --package pandoc +{-# LANGUAGE OverloadedStrings #-} +{- | + +A simple hakyll website builder suitable for software project sites, +intended to be used as-is without recompilation. Functionality: + +- copies these static files to _site/ : + *.{html,htm,css,js,gif,jpg,jpeg,png} + {css,img,js,files}/** (** means everything below) + site/{css,img,js,files,etc}/** + doc/**.{html,htm,txt,gif,jpg,jpeg,png} + +- renders these markdown files to _site/*.html : + *.{md,mdwn,markdown} + doc/**.{md,mdwn,markdown} + +- applies this template file to markdown content: + site.tmpl or site/site.tmpl (the first found) + +- a single markdown list item containing the word "toc" is replaced by + a table of contents based on headings + +- syntax highlighting of fenced code blocks in markdown is enabled + (if you provide suitable kate styles, eg a syntax.css) + +Usage: + +$ hakyll-std [--help|clean|build|preview|...] # standard hakyll options + +-} + +import Control.Monad +import Data.Default +import Hakyll +import System.Directory +import System.Environment (getArgs) +import System.Exit (exitSuccess) +import System.Process (system) +-- import Text.Highlighting.Kate (pygments, kate, espresso, tango, haddock, monochrome, zenburn) +import Text.Pandoc.Options + +import TableOfContents (tableOfContents) + +import Debug.Trace +strace :: Show a => a -> a +strace a = trace (show a) a + +filesToCopy = + ["site/css/**" + ,"site/js/**" + ,"site/img/**" + ,"site/images/**" + ,"site/fonts/**" + ,"site/files/**" + ,"site/etc/**" + ,"site/*.html" + ,"site/*.htm" + ,"site/*.gif" + ,"site/*.jpg" + ,"site/*.jpeg" + ,"site/*.png" + ,"site/*.css" + ,"site/*.js" + ,"css/**" + ,"js/**" + ,"img/**" + ,"images/**" + ,"fonts/**" + ,"files/**" + ,"doc/**.html" + ,"doc/**.htm" + ,"doc/**.txt" + ,"doc/**.gif" + ,"doc/**.jpg" + ,"doc/**.jpeg" + ,"doc/**.png" + ,"*.html" + ,"*.htm" + ,"*.css" + ,"*.js" + ,"*.gif" + ,"*.jpg" + ,"*.jpeg" + ,"*.png" + ] + +filesToRender = + ["*.md" + ,"*.mdwn" + ,"*.markdown" + ,"doc/**.md" + ,"doc/**.mdwn" + ,"doc/**.markdown" + ] + +-- http://hackage.haskell.org/package/pandoc-1.13/docs/src/Text-Pandoc-Options.html#ReaderOptions +pandocReaderOptions = def + +-- http://hackage.haskell.org/package/pandoc-1.13/docs/src/Text-Pandoc-Options.html#WriterOptions +pandocWriterOptions = def + {writerHighlight=True + -- this would change the value of pandoc's $highlight-css$ var + -- for now, let the user provide these styles + -- ,writerHighlightStyle=tango + } + +pandocTransform = tableOfContents "right" + +main = do + args <- getArgs + when (any (`elem` args) ["--version"]) $ do + putStrLn "hakyll standard site builder v0.1" + exitSuccess + + hakyll $ do + + match (foldl1 (.||.) filesToCopy) $ route idRoute >> compile copyFileCompiler + + -- there might or might not be a site template in ./ or ./site/ + mtmpl <- preprocess $ do + t1 <- doesFileExist "site.tmpl" + t2 <- doesFileExist "site/site.tmpl" + return $ case (t1, t2) of (False, True) -> Just "site/site.tmpl" + (True, _) -> Just "site.tmpl" + (False, False) -> Nothing + case mtmpl of + Just tmpl -> match tmpl $ compile templateCompiler + Nothing -> return () + + match (foldl1 (.||.) filesToRender) $ do + route $ setExtension "html" + compile $ + pandocCompilerWithTransformM pandocReaderOptions pandocWriterOptions (return . pandocTransform) + >>= (case mtmpl of + Just tmpl -> loadAndApplyTemplate (fromCapture tmpl "") defaultContext + Nothing -> return) + >>= relativizeUrls + + -- this fails the first time after a clean because it runs before README.html generation + -- when ("build" `elem` args) $ preprocess linkReadmeToIndex + + -- can't do anything here, hakyll exits + +linkReadmeToIndex = system "ln -sf README.html _site/index.html" >> return () diff --git a/doc/site/stack.yaml b/doc/site/stack.yaml new file mode 100644 index 000000000..f43fc5fd9 --- /dev/null +++ b/doc/site/stack.yaml @@ -0,0 +1,6 @@ +# stack.yaml for hakyll-std +extra-deps: [] +resolver: nightly-2015-07-01 +flags: {} +packages: +- '.'