heckle 2.0.1.9 → 2.0.2.0
raw patch · 7 files changed
+305/−333 lines, 7 filesdep −optparse-applicativedep −optparse-genericdep ~directoryPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies removed: optparse-applicative, optparse-generic
Dependency ranges changed: directory
API changes (from Hackage documentation)
- Heckle: April :: Month
- Heckle: August :: Month
- Heckle: December :: Month
- Heckle: February :: Month
- Heckle: January :: Month
- Heckle: July :: Month
- Heckle: June :: Month
- Heckle: March :: Month
- Heckle: May :: Month
- Heckle: November :: Month
- Heckle: October :: Month
- Heckle: September :: Month
- Heckle: data Month
- Heckle: instance GHC.Classes.Eq Heckle.Month
- Heckle: instance GHC.Enum.Bounded Heckle.Month
- Heckle: instance GHC.Enum.Enum Heckle.Month
- Heckle: instance GHC.Show.Show Heckle.Month
- Heckle: month :: Int -> Month
Files
- heckle.cabal +7/−7
- heckle/Files.hs +0/−52
- heckle/Main.hs +0/−102
- lib/Heckle.hs +138/−0
- src/Files.hs +52/−0
- src/Heckle.hs +0/−172
- src/Main.hs +108/−0
heckle.cabal view
@@ -1,5 +1,5 @@ name: heckle-version: 2.0.1.9+version: 2.0.2.0 synopsis: Jekyll in Haskell (feat. LaTeX) description: A static site generator that supports LaTeX-PDF and Markdown-HTML posts. Care has been taken to make it configurable, easy to use, and unopinionated. @@ -18,12 +18,12 @@ location: https://github.com/2016rshah/heckle.git library- hs-source-dirs: src+ hs-source-dirs: lib exposed-modules: Heckle build-depends: base >= 4.8 && <5 , filepath >= 1.4.0.0 , blaze-html >= 0.8.1.1- , directory >=1.2 && <1.3+ , directory >=1.2 && <1.4 , process >= 1.2.0.0 , split >=0.2 && <0.3 , tagsoup >= 0.13.3@@ -33,16 +33,16 @@ default-language: Haskell2010 executable heckle- hs-source-dirs: heckle+ hs-source-dirs: src main-is: Main.hs other-modules: Files build-depends: base >=4.8 && <5 , heckle- , directory >=1.2 && <1.3+ , directory >=1.2 && <1.4 , filepath >= 1.4.0.0 , process >= 1.2.0.0 , split >=0.2 && <0.3- , optparse-applicative >= 0.12.0.0 && <0.13.0.0- , optparse-generic ==1.1.*+ --, optparse-applicative >= 0.12.0.0 && <0.14.0.0+ --, optparse-generic ==1.1.* other-extensions: OverloadedStrings default-language: Haskell2010
− heckle/Files.hs
@@ -1,52 +0,0 @@-module Files where---exampleTeXPost :: String-exampleTeXPost = unlines- [ "\\documentclass{article}"- , "\\author{Rushi Shah}"- , "\\date{1 January 2015}"- , "\\title{Example LaTeX Post}"- , "\\begin{document}"- , "\\maketitle"- , "This is an example LaTeX/PDF post."- , "\\end{document}"- ]--exampleMDPost :: String-exampleMDPost = unlines - [ "% Example MD Post"- , "% Rushi Shah"- , "% 1 January 2016"- , ""- , "This is an example MD/HTML post"- ]- -exampleIndexFile :: String-exampleIndexFile = unlines- [ "<ul id='blog-posts'></ul>"]--exampleResFile :: String-exampleResFile = unlines- ["<ul id=\"blog-posts\">"- ," <li class=\"blog-post\">"- ," <a class=\"post-link\" href=\"posts/example-markdown.html\">"- ," Example MD Post"- ," </a>"- ," <div class=\"post-date\">"- ," 1 January 2016"- ," </div>"- ," </li>"- ," <li class=\"blog-post\">"- ," <a class=\"post-link\" href=\"posts/example-latex.pdf\">"- ," Example Post"- ," </a>"- ," <div class=\"post-date\">"- ," 1 January 2015"- ," </div>"- ," </li>"- ,"</ul>"]--exampleTemplateFile :: String-exampleTemplateFile = unlines- [ "<div id='blog-post'></div>"]
− heckle/Main.hs
@@ -1,102 +0,0 @@-module Main where--import Control.Exception-import Data.Either-import Data.List-import Data.Maybe-import Options.Applicative-import System.Directory-import System.FilePath-import System.Environment (getArgs)-import System.Process (readProcess)--import Paths_heckle (version)-import Data.Version (showVersion)---import Heckle-import Files--data Command- = Build- | Init- | Version--withInfo :: InfoMod a -> Parser a -> ParserInfo a-withInfo im p = info (helper <*> p) im--infixr 1 ==>-(==>) = withInfo . progDesc--mainFlags :: ParserInfo Command-mainFlags = withInfo (fullDesc <> progDesc "heckle: a simple, configurable static site generator") parser- where- parser :: Parser Command- parser = subparser $ mconcat- [ command "build" $ "Generate site"- ==> pure Build- , command "init" $ "Make new site template"- ==> pure Init- , command "version" $ "Get the package version"- ==> pure Version- ]--buildSite :: IO ()-buildSite = do- putStrLn "Reading directory and turning into native posts"- postsToBeCreated <- mapM fileToPost =<< getDirectoryContents "posts"- let posts = reverse . sort . rights $ postsToBeCreated- putStrLn $ "Number of posts found: " ++ show (length posts)-- putStrLn "Writing markdown files into template HTML"- template <- readFile "template.html.hkl"- sequence $ mapMaybe (writeHTML template) posts-- putStrLn "Creating HTML <ul> element for index file"- let generatedHtml = postsToHtml posts-- putStrLn "Inserting HTML <ul> element into layout file"- layoutFile <- readFile "index.html.hkl"- let injectedOutput = layoutFile `injectIndex` generatedHtml- case injectedOutput of- Nothing -> putStrLn "Error with templating"- Just s -> do- writeFile "index.html" s- putStrLn "Success building!"--initSite :: IO ()-initSite = do-- -- Create the basic layout file- writeFile "index.html.hkl" exampleIndexFile -- Change to layout when testing, index when deploying"- writeFile "template.html.hkl" exampleTemplateFile- writeFile "index.html" exampleResFile-- -- Create directory for posts and basic post- createDirectoryIfMissing True "posts"- setCurrentDirectory "posts"- writeFile "example-markdown.md" exampleMDPost-- -- Compile the LaTeX file into a PDF- -- Could do this for every .tex file if wanted to- writeFile "example-latex.tex" exampleTeXPost- x <- try (readProcess "pdflatex" ["example-latex.tex"] "")- case x of- Left e -> do- let err = show (e :: IOException)- putStrLn "Warning: LaTeX installation not found, removing LaTeX post"- removeFile "example-latex.tex"- return "LaTeX not found"- Right r -> return "Success"-- putStrLn "Success initializing!"--versionOfSite :: IO ()-versionOfSite = putStrLn ("heckle " ++ showVersion version)--main = do- command <- execParser mainFlags- case command of- Build -> buildSite- Init -> initSite- Version -> versionOfSite
+ lib/Heckle.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeSynonymInstances #-}++module Heckle where++import Control.Applicative+import Control.Monad+import Data.Either+import Data.Function (on)+import Data.List.Split (splitOn)+import Data.String (IsString)+import Data.Monoid+import System.FilePath++import Text.Blaze.Html5 as H hiding (main, map)+import Text.Blaze.Html5.Attributes as A+import Text.Blaze.Html.Renderer.Pretty++import qualified Text.HTML.TagSoup as TagSoup++import Data.Time++import Text.Pandoc.Definition hiding (Format)+import Text.Pandoc.Options (def)+import Text.Pandoc.Readers.LaTeX (readLaTeX)+import Text.Pandoc.Readers.Markdown (readMarkdown)+import Text.Pandoc.Shared (stringify)+import Text.Pandoc.Writers.HTML (writeHtmlString)++instance Show Html where+ show = renderHtml++displayDate :: UTCTime -> String+displayDate = formatTime defaultTimeLocale "%-d %B %Y"++postsToHtml :: [Post] -> Html+postsToHtml xs = do+ ul ! A.id "blog-posts" $+ forM_ xs postToHtml++postToHtml :: Post -> Html+postToHtml Post{..} = li ! class_ "blog-post" $ do+ a ! class_ "post-link" ! href (stringValue ("posts/" ++ fileName ++ ext)) $ toHtml postTitle+ H.div ! class_ "post-date" $ toHtml (displayDate postDate)+ where+ ext = getOutputExtension format++data Format = LaTeX | Markdown+ deriving (Show, Eq)++getOutputExtension :: Format -> String+getOutputExtension LaTeX = ".pdf"+getOutputExtension Markdown = ".html"++newtype Title = Title { getTitle :: String } + deriving (Show, Eq, IsString, ToMarkup)++data Post = Post+ { fileName :: String -- TODO make this more typed+ , postTitle :: Title+ , postDate :: UTCTime+ , format :: Format+ , pd :: Pandoc+ } + deriving (Show, Eq)++instance Ord Post where+ compare = compare `on` postDate++parseAbsoluteDate :: String -> Either String UTCTime+parseAbsoluteDate s = case parseAbsoluteDate' s of+ Just a -> Right a+ Nothing -> Left "Date does not match valid formats"+++-- | Valid formats:+-- | 6 January 2012+-- | January 6, 2012+-- | 9:47AM 6 January 2012+-- | 9:47AM January 6, 2012+parseAbsoluteDate' :: String -> Maybe UTCTime +parseAbsoluteDate' s = foldr (<|>) Nothing results + where+ results = map ($ s) options+ options = map (parseTimeM True defaultTimeLocale) formats+ formats = ["%-d %B %Y", "%B %-d, %Y", "%-l:%M%p %-d %B %Y", "%-l:%M%p %B %-d, %Y"]++getMeta :: (Meta -> [Inline]) -> Pandoc -> Either String String+getMeta f (Pandoc m _) = case f m of+ [] -> Left "Couldn't find it"+ xs -> Right (stringify xs)++-- | Creates a post given a constructor for a post+createPost+ :: Show a+ => Format+ -> String+ -> Either a Pandoc+ -> Either String Post+createPost _ _ (Left e) = Left (show e)+createPost format fileName (Right pd) = do+ postTitle <- Title <$> getMeta docTitle pd+ postDate <- getMeta docDate pd >>= parseAbsoluteDate+ return Post{..}++fileToPost :: String -> IO (Either String Post)+fileToPost fileName =+ case splitExtension fileName of+ (name, ".pdf") ->+ return . createPost LaTeX name . readLaTeX def =<< readFile ("posts/" <> name <> ".tex")+ (name, ".md") ->+ return . createPost Markdown name . readMarkdown def =<< readFile ("posts/" <> fileName)+ _ -> pure (Left "Not a LaTeX or MD file")++injectIndex :: String -> Html -> Maybe String+injectIndex layout ul = injectAt [ TagSoup.TagOpen "ul" [("id","blog-posts")]+ , TagSoup.TagClose "ul"]+ layout (show ul)++injectTemplate :: String -> Post -> Maybe String+injectTemplate layout post+ | format post == Markdown = injectAt tags layout inp+ | otherwise = Nothing+ where+ tags = [TagSoup.TagOpen "div" [("id","blog-post")], TagSoup.TagClose "div"]+ inp = "<div id='blog-post'>" <> writeHtmlString def (pd post) <> "</div>"++injectAt :: [TagSoup.Tag String] -> String -> String -> Maybe String+injectAt p layout insert = case splitOn p (TagSoup.parseTags layout) of+ [beg, end] -> Just $ TagSoup.renderTags (beg <> TagSoup.parseTags insert <> end)+ _ -> Nothing++writeHTML :: String -> Post -> Maybe (IO ())+writeHTML template p@Post{..} =+ writeFile ("posts/" <> fileName <> ".html") <$> injectTemplate template p
+ src/Files.hs view
@@ -0,0 +1,52 @@+module Files where+++exampleTeXPost :: String+exampleTeXPost = unlines+ [ "\\documentclass{article}"+ , "\\author{Rushi Shah}"+ , "\\date{1 January 2015}"+ , "\\title{Example LaTeX Post}"+ , "\\begin{document}"+ , "\\maketitle"+ , "This is an example LaTeX/PDF post."+ , "\\end{document}"+ ]++exampleMDPost :: String+exampleMDPost = unlines + [ "% Example MD Post"+ , "% Rushi Shah"+ , "% 1 January 2016"+ , ""+ , "This is an example MD/HTML post"+ ]+ +exampleIndexFile :: String+exampleIndexFile = unlines+ [ "<ul id='blog-posts'></ul>"]++exampleResFile :: String+exampleResFile = unlines+ ["<ul id=\"blog-posts\">"+ ," <li class=\"blog-post\">"+ ," <a class=\"post-link\" href=\"posts/example-markdown.html\">"+ ," Example MD Post"+ ," </a>"+ ," <div class=\"post-date\">"+ ," 1 January 2016"+ ," </div>"+ ," </li>"+ ," <li class=\"blog-post\">"+ ," <a class=\"post-link\" href=\"posts/example-latex.pdf\">"+ ," Example Post"+ ," </a>"+ ," <div class=\"post-date\">"+ ," 1 January 2015"+ ," </div>"+ ," </li>"+ ,"</ul>"]++exampleTemplateFile :: String+exampleTemplateFile = unlines+ [ "<div id='blog-post'></div>"]
− src/Heckle.hs
@@ -1,172 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeSynonymInstances #-}--module Heckle where--import Control.Applicative-import Control.Monad-import Data.Bifunctor-import Data.Either-import Data.Function (on)-import Data.List-import Data.List.Split (splitOn)-import Data.String (IsString)-import Data.Monoid-import System.FilePath--import Text.Blaze.Html5 as H hiding (main, map)-import Text.Blaze.Html5.Attributes as A-import Text.Blaze.Html.Renderer.Pretty--import qualified Text.HTML.TagSoup as TagSoup--import Data.Time--- parseTimeM True defaultTimeLocale "%d %B %Y" "12 January 2016" :: Maybe UTCTime----https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Maybe.html figure out how to optionally match either with hh:mm:ss or without it---https://hackage.haskell.org/package/time-1.7/docs/Data-Time-Format.html---http://hackage.haskell.org/package/time-1.7/docs/Data-Time-Clock.html#t:UTCTime---https://hackage.haskell.org/package/time-1.7/docs/Data-Time-Format.html---https://lotz84.github.io/haskellbyexample/ex/time-formatting-parsing--- ---import Text.Pandoc.Definition hiding (Format)-import Text.Pandoc.Options (def)-import Text.Pandoc.Readers.LaTeX (readLaTeX)-import Text.Pandoc.Readers.Markdown (readMarkdown)-import Text.Pandoc.Shared (stringify)-import Text.Pandoc.Writers.HTML (writeHtmlString)--instance Show Html where- show = renderHtml--data Month- = January- | February- | March- | April- | May- | June- | July- | August- | September- | October- | November- | December- deriving (Show, Eq, Bounded, Enum)--month :: Int -> Month-month n = toEnum (n-1)--displayDate :: UTCTime -> String-displayDate t = formatTime defaultTimeLocale "%-d %B %Y" t---postsToHtml :: [Post] -> Html-postsToHtml xs = do- ul ! A.id "blog-posts" $- forM_ xs postToHtml--postToHtml :: Post -> Html-postToHtml Post{..} = li ! class_ "blog-post" $ do- a ! class_ "post-link" ! href (stringValue ("posts/" ++ fileName ++ ext)) $ toHtml postTitle- H.div ! class_ "post-date" $ toHtml (displayDate postDate)- where- ext = getOutputExtension format----data InputFormat =-data Format = LaTeX | Markdown- deriving (Show, Eq)--getOutputExtension :: Format -> String-getOutputExtension LaTeX = ".pdf"-getOutputExtension Markdown = ".html"---- data FileName = FileName { getFileName :: String, getExtension :: }-newtype Title = Title { getTitle :: String } - deriving (Show, Eq, IsString, ToMarkup)--data Post = Post- { fileName :: String -- TODO make this more typed- , postTitle :: Title- , postDate :: UTCTime- , format :: Format- , pd :: Pandoc- } - deriving (Show, Eq)--instance Ord Post where- compare = compare `on` postDate--parseAbsoluteDate :: String -> Either String UTCTime-parseAbsoluteDate s = case parseAbsoluteDate' s of- Just a -> Right a- Nothing -> Left "Date does not match valid formats"---{---Valid formats:-6 January 2012-January 6, 2012-9:47AM 6 January 2012-9:47AM January 6, 2012---}-parseAbsoluteDate' :: String -> Maybe UTCTime -parseAbsoluteDate' s = foldr (<|>) Nothing results - where- results = map ($ s) options- options = map (parseTimeM True defaultTimeLocale) formats- formats = ["%-d %B %Y", "%B %-d, %Y", "%-l:%M%p %-d %B %Y", "%-l:%M%p %B %-d, %Y"]--getMeta :: (Meta -> [Inline]) -> Pandoc -> Either String String-getMeta f (Pandoc m _) = case f m of- [] -> Left "Couldn't find it"- xs -> Right (stringify xs)---- | Creates a post given a constructor for a post-createPost- :: Show a- => Format- -> String- -> Either a Pandoc- -> Either String Post-createPost _ _ (Left e) = Left (show e)-createPost format fileName (Right pd) = do- postTitle <- Title <$> getMeta docTitle pd- postDate <- getMeta docDate pd >>= parseAbsoluteDate- return Post{..}--fileToPost :: String -> IO (Either String Post)-fileToPost fileName =- case splitExtension fileName of- (name, ".pdf") ->- return . createPost LaTeX name . readLaTeX def =<< readFile ("posts/" <> name <> ".tex")- (name, ".md") ->- return . createPost Markdown name . readMarkdown def =<< readFile ("posts/" <> fileName)- _ -> pure (Left "Not a LaTeX or MD file")--injectIndex :: String -> Html -> Maybe String-injectIndex layout ul = injectAt [ TagSoup.TagOpen "ul" [("id","blog-posts")]- , TagSoup.TagClose "ul"]- layout (show ul)--injectTemplate :: String -> Post -> Maybe String-injectTemplate layout post- | format post == Markdown = injectAt tags layout inp- | otherwise = Nothing- where- tags = [TagSoup.TagOpen "div" [("id","blog-post")], TagSoup.TagClose "div"]- inp = "<div id='blog-post'>" <> writeHtmlString def (pd post) <> "</div>"--injectAt :: [TagSoup.Tag String] -> String -> String -> Maybe String-injectAt p layout insert = case splitOn p (TagSoup.parseTags layout) of- [beg, end] -> Just $ TagSoup.renderTags (beg <> TagSoup.parseTags insert <> end)- _ -> Nothing--writeHTML :: String -> Post -> Maybe (IO ())-writeHTML template p@Post{..} =- writeFile ("posts/" <> fileName <> ".html") <$> injectTemplate template p
+ src/Main.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE CPP #-}+module Main where++import Control.Exception+import Data.Either+import Data.List+import Data.Maybe+--import Options.Applicative+import System.Directory+import System.FilePath+import System.Environment (getArgs)+import System.Process (readProcess)++import Paths_heckle (version)+import Data.Version (showVersion)+-- #if MIN_VERSION_optparse_applicative(0,13,0)+-- versions before optparse-applicative-0.13 reexported (<>) themselves+import Data.Monoid ((<>))+-- #endif++import Heckle+import Files++-- data Command+-- = Build+-- | Init+-- | Version++-- withInfo :: InfoMod a -> Parser a -> ParserInfo a+-- withInfo im p = info (helper <*> p) im++-- infixr 1 ==>+-- (==>) = withInfo . progDesc++-- mainFlags :: ParserInfo Command+-- mainFlags = withInfo (fullDesc <> progDesc "heckle: a simple, configurable static site generator") parser+-- where+-- parser :: Parser Command+-- parser = subparser $ mconcat+-- [ command "build" $ "Generate site"+-- ==> pure Build+-- , command "init" $ "Make new site template"+-- ==> pure Init+-- , command "version" $ "Get the package version"+-- ==> pure Version+-- ]++buildSite :: IO ()+buildSite = do+ putStrLn "Reading directory and turning into native posts"+ postsToBeCreated <- mapM fileToPost =<< getDirectoryContents "posts"+ let posts = reverse . sort . rights $ postsToBeCreated+ putStrLn $ "Number of posts found: " ++ show (length posts)++ putStrLn "Writing markdown files into template HTML"+ template <- readFile "template.html.hkl"+ sequence $ mapMaybe (writeHTML template) posts++ putStrLn "Creating HTML <ul> element for index file"+ let generatedHtml = postsToHtml posts++ putStrLn "Inserting HTML <ul> element into layout file"+ layoutFile <- readFile "index.html.hkl"+ let injectedOutput = layoutFile `injectIndex` generatedHtml+ case injectedOutput of+ Nothing -> putStrLn "Error with templating"+ Just s -> do+ writeFile "index.html" s+ putStrLn "Success building!"++initSite :: IO ()+initSite = do++ -- Create the basic layout file+ writeFile "index.html.hkl" exampleIndexFile -- Change to layout when testing, index when deploying"+ writeFile "template.html.hkl" exampleTemplateFile+ writeFile "index.html" exampleResFile++ -- Create directory for posts and basic post+ createDirectoryIfMissing True "posts"+ setCurrentDirectory "posts"+ writeFile "example-markdown.md" exampleMDPost++ -- Compile the LaTeX file into a PDF+ -- Could do this for every .tex file if wanted to+ writeFile "example-latex.tex" exampleTeXPost+ x <- try (readProcess "pdflatex" ["example-latex.tex"] "")+ case x of+ Left e -> do+ let err = show (e :: IOException)+ putStrLn "Warning: LaTeX installation not found, removing LaTeX post"+ removeFile "example-latex.tex"+ return "LaTeX not found"+ Right r -> return "Success"++ putStrLn "Success initializing!"++versionOfSite :: IO ()+versionOfSite = putStrLn ("heckle " ++ showVersion version)++main = do+ --command <- execParser mainFlags+ args <- getArgs+ case args of+ "build":[] -> buildSite+ "init":[] -> initSite+ "version":[] -> versionOfSite+ otherwise -> putStrLn "Command not recognized"