From 66fb2f5f08a2dab465784c55ff694c08736b7d3e Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Wed, 24 May 2023 23:43:33 +0200 Subject: [PATCH] render, install files --- FormatOpts.hs | 23 ++++++++ Types.hs | 10 ++-- Utils.hs | 18 ++++++ external/mypage/img/awesome.png | Bin 0 -> 36215 bytes reploy.cabal | 7 ++- site.hs | 94 +++++++++++++++++--------------- templates/default.html | 2 +- templates/redirect.html | 13 +++++ 8 files changed, 115 insertions(+), 52 deletions(-) create mode 100644 FormatOpts.hs create mode 100644 Utils.hs create mode 100644 external/mypage/img/awesome.png create mode 100644 templates/redirect.html diff --git a/FormatOpts.hs b/FormatOpts.hs new file mode 100644 index 0000000..363f6cd --- /dev/null +++ b/FormatOpts.hs @@ -0,0 +1,23 @@ +module FormatOpts where + +import Text.Pandoc.Extensions +import Text.Pandoc.Highlighting (pygments) +import Text.Pandoc.Options + +-- | Default markdown reading options for Pandoc. +markdownReadOpts = + def + { readerExtensions = + Text.Pandoc.Extensions.enableExtension + Text.Pandoc.Extensions.Ext_smart + Text.Pandoc.Extensions.pandocExtensions + } + +-- | Default HTML writing options for Pandoc. +htmlWriteOpts :: WriterOptions +htmlWriteOpts = + def + { writerExtensions = enableExtension Ext_smart pandocExtensions + , writerHighlightStyle = Just pygments + , writerWrapText = WrapPreserve + } diff --git a/Types.hs b/Types.hs index b46011e..9dba9a4 100644 --- a/Types.hs +++ b/Types.hs @@ -16,7 +16,7 @@ data PageInfo = PageInfo { _pagePath :: FilePath -- ^ original path to the markdown file , _pageMeta :: Y.Value -- ^ YAML metadata extracted from the file - , _pagePandoc :: Text.Pandoc.Definition.Pandoc -- ^ Page data + , _pageDoc :: Text.Pandoc.Definition.Pandoc -- ^ Page data } deriving (Show) @@ -32,10 +32,10 @@ data SiteState = -- | Map of tags, assigning to each tag sequence a list of -- tagged page mounts , _htags :: M.Map [String] [FilePath] - -- | List of installed files (prevents overwriting) + -- | List of installed files (enables sharing) , _installs :: S.Set FilePath - -- | List of installed pages (basically directories with index; prevents overwriting) - , _renders :: S.Set FilePath + -- | List of installed files (prevents overwriting) + , _targets :: S.Set FilePath -- | Map of Mustache templates organized by template search path (within -- the template directory) , _templates :: M.Map FilePath Mu.Template @@ -54,7 +54,7 @@ emptySiteState = , _redirects = M.empty , _htags = M.empty , _installs = S.empty - , _renders = S.empty + , _targets = S.empty , _templates = M.empty , _outputDir = "_site" , _defaultTemplate = "default.html" diff --git a/Utils.hs b/Utils.hs new file mode 100644 index 0000000..d058c1c --- /dev/null +++ b/Utils.hs @@ -0,0 +1,18 @@ + +module Utils where +import Data.Maybe (isJust) +import Data.List.Extra (stripSuffix) +import Control.Monad.IO.Class + +-- | A shortcut for `liftIO`. +io :: MonadIO m => IO a -> m a +io = liftIO + +-- | A helper for throwing an error if something is `Nothing` +just :: String -> Maybe a -> a +just _ (Just val) = val +just err Nothing = error ("Missing: " ++ err) + +-- | Test for whether something listy has a suffix +hasSuffix :: Eq a => [a] -> [a] -> Bool +hasSuffix s = isJust . stripSuffix s diff --git a/external/mypage/img/awesome.png b/external/mypage/img/awesome.png new file mode 100644 index 0000000000000000000000000000000000000000..769a333b0a6fc5fd746de8ade078b8219dfe024e GIT binary patch literal 36215 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%0A%~!oImc~f#$ux@n@6>c zY9M?oV;BbIj4|H3hR7=jejS@Dfdd=DNa~X3FXInm8h$`%b9%R#7s9cSI8^97fzc~{ z5MWb0>o!od-d=wT(t>kCgx}uzI5vf0W}?T<%(6G7g#o6w6`U(UBg5EJh8t95pv6Ga z`TaW_Pr$^>m#Xi-RL||BqLp&3u;y++ZT&jU9CG*HR@)CiNxM0WHf3}tcY7-T_TIhI zCs27yM@7R})>#`9=XL`V&q*_(vE_)=Nx)FeZ0>%ylrgXXOww2dX$i{A|?4^{MkP~WN zdF^;DZh5)#=K1zmntPW|ACnj;ciA!Lyu6a;EUH;k)E|Lhabb2q?pVYwZgw*X)rTgp zi!;Nd3fI&r`y@$!=#i(f6Rc z)i`**s=wTPzIW@KhpxBPwl_%X}I%+1JR7mt`$p+`J7IJpc-LTquPUsLu;%76hLsb@BL7 z&#tNppFRO9HjuH5T3%~(QQY#q79AUD#ZNbCx%^jS;K_0tOP?wbGHIsYnv{Jmi!WuF zYuR+vGqU`JjO2itP&}*GOnBZM|0O+MpI?U4TgNcSTuaa(`NFZuDWPARCvs=pI*knz z|Ngl6_(>a*E6s(C)2`#IO=-)LWvEO7*5!D&f00j_z= zZPh`Ur$WnOy;N+Y+^q-yGW#8%d)AI%T-LVlsgM^BO1qAHh|}bLi3*_&YNk1Ra=;Gz zfKnMKtgoO=t?iUb65>ZfG3et#x_Bp4#!J&~L$hQC4L?-K%!U6xZ4G9gz05A){$eqn zw(jv3qoN@wEoAf?pgWoF7~FR8c&XQIxXNZ=XvIvj1>;~NWL#-EkVQtmA0D%eWC`kY z=nNoE&E>zyrmVq%Oxpon_j$Xj?2br&&2g&X=o<>gRJJgKIUWSetVhyEr91K;>$3ZnLRCfu=$C*b8;1v)D;4^A4jRw{x{&8=SwxH1K7m>8wMMBI0fn1 zGPlqDGG1wB<3IlmH+?7g^ian`=~trE+)?UddybNSb+gS)sesJ;7mOsvPUcZpv8C51 z=_+oeT~dWvSfa@24ah|5gEiHI7O+1?xj#REg?ypi?f5tMu5|3GKl{)YZudWB^J>*b zRX@92QcV%{OF_}5e7OOU)jNL-WUP)L#s!Rrk{YMv3Wxmx1ZJALqY2=Lz>wruyNlNs z!cS-6$g6@-ApvUtZC$Z1Fa=qb%bHEW!& zH8%APxo0jY(AkjNZ3~1Op3MoTq~N(4^~w z9oHXxT^>=CG8yx{)ThW~2e@2yd&FshO5A$S%lZr=kwSnIZcbH0w1OF;QE8 z^fATg_Si;sYp1^ zT&(YtD1q=+9VV|#4Zgyk35WW>H-X%cth49`_9p^X%m2FXw}=nV;v^H3x^9uSfK;H6 zwke^WTZYLOTAcGO+&lAp1sufH`%|OKmv&1SW&TC@;YYe(L`BbA+4l+r`8m;{NTqz+ zd2Q*{LCas|e(_u@;Xd5tklFj+83SZ31vq@bZhWE$Dsci99k{#tPt^}*dQcb{g_ zkX}KlywP4gJ=_jKT4ZA`z&@3EP9HY2Au|zi&6TH+8BYCo@^^u|>X^+l&ff%|Th}Rx zMnxZ4Dk%wzpn>_)@>IiFJWSLV1fM`{E{sBHrw>i7N(nOAmiHYpV=ED0GN?KU6UtK~ zx=%oD<@Gl#H%NxVStxB^2E5AxFxH2YtEW)~Ff#lR`BT)T+RH8$6TjUMEw%zHL2Z_c z8+nc{DO0mHv@zp)ihA~LKhj5KU^ae$JZ4-5(Yx zMgVAM#L#C`I8Lzw!NJb_i8J_bs4P?H(LwgoR_S8j#6_1LV>)oRtCg}B1Jwt+o`Un6 z2TIkDg*3Y0KRtK-(7vqw`aOChFcD_3>whmEp@11=aJ`32R;1B)V6y-_I(V+CF*QF( z-&QIZMbj++*&GPY*W_2lx`3#w(?J;_>KD%M*ySNP{kx8(^rQeHQi?0FaruYm$p^DW zKANFCW`&XaD+mN=d{N+xQ$c@hWOHzR`d&c69~F{i8L`Fv0cN06Bib^@QlK~)C3dSv zIm>;B?*5r5$0qmW$PN*D7<%h}OKmW1^I=~=p>Kv1oi(nwls+PQ>7n=?P2=UkC&hH8 zWmLm}4Ybtsga3UZptN`fuk{UQb^Zk%pAX->UXrw5)!jf?Y;9z6!~{x6!OLpYtl&fM zqNVw`++=S3$pnyqx065uLXqg`XT;&;TVlOM(%%6odRU6ThhyzW(@IK)OPlaKK3r78 zFW#SVwY|~x4_$L7fc{jz1o^yKP12(Uvwm`VP^`DDBf8InAy8uu>x!isgQ*83W;}-- z7AMl6zpXG{TR%9sM6R@VfIJL-ANgh6>4pK^yQr$f1vYGz)NpRq(YLr_%A*6d4KD|7 z&^;x$tGVh|!aew?_>CxcFc~#6u@oNbZ>jBz+Q{0bCU(gYDp$qRBpgrMc`+{_xyLU8 zQJKfk84K;R&B0_2rX>%q7L7ZV#;hre0N_id`FT_djCh5$@3}#-jSQJahEBm=QgF5u zH)~-RQ$(p4!Dp{V_VIUQ5?NtO->m@8I(lZPyoNzqKb(nlhDXpE%7gdqV8|zm9acq_ z>GAF-QOQN8U#xeo<}s2we|3&_T?}YyaBnup+Sag`)0A?t@Z(9g0k-AP<2*=1Mgxlr{=vT-Ck7vn1_%10_CDw~f!eud`+v$Ae9&+~ZjR+-71@!VR& zmxPgD2$D+OOnKFr+5~q!aXwU;=H(kt^N209V42KUIf?iZqV9 zJ&=Y7){~71V2bCE?a<2f@i`VO)CUcpXl5tj5IS+SpCsVsM^G!U*CreIMBt z!P`8F&9c@tCb#|cfZWdXUeCo^F^Jl{w6A2ADY;#RiPI|0het4@ z{*|P3EU?GE*}UCy7}<(0*T`i4noGt9MKB?6IE|&B+6xTJl1iB>kG$A>sH)mmYkKb( z39h~N>AME5Af1~Sa!Xa_YR7xG`Js~T)Q%(!1s4g2UH#myqy>&WD(qN#BC z3!~!FdCdI6O{4>DUqlbYHtt9Gt^F<6nKt8NDw5xByAOZXNHu)L&R{%XB@a|1x`SdX zMs)Pq$#Eykvc2d9Gao5#AF|3PZ@TGi;mLjUni|;O&M{F7Kf40dB6saoh3&gyf;8Mx z1FJ;?!7-?JbQL6$dG*v|U{R9Lsk(|iB$e_*(G-5kdVMc<`W2a8VQ>jhI(>bv!NYf_$e zoN*)*Q$SIU3iH^?RzDPzpdl9!epvLZhtG#h=u!KZE5=WXS-2I7u-jqu3G4_Q>Z@+D z`n{4iyx5ic@$rBKn`|vfA4^!2X7kk)wZ!QVvW|vn?Whl~HmF=+B1U71#wxzx)Wx~> zhfO(9N^n`dlq0xfje^QjDFC<}s$1>GM zEYa9?GP_%v*LPS2`dV}U} z7=3XhDhqT+g_6I0vyN!X oQ&_f$J)rok@?IhHxk&Why= 1.10 executable site main-is: site.hs - other-modules: Types + other-modules: Types, Utils, FormatOpts build-depends: base == 4.* + , aeson + , bytestring , containers , data-default , extra @@ -20,8 +22,9 @@ executable site , pandoc , pandoc-types , parsec + , SHA , text , transformers , yaml - ghc-options: -threaded -rtsopts -with-rtsopts=-N + ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wunused-imports default-language: Haskell2010 diff --git a/site.hs b/site.hs index a1e03b5..8ab060f 100644 --- a/site.hs +++ b/site.hs @@ -4,14 +4,13 @@ module Main where import Control.Monad (unless, when) -import Control.Monad.IO.Class import Control.Monad.Trans.State.Lazy -import Data.Default (def) +import qualified Data.Aeson.KeyMap as KM +import qualified Data.ByteString.Lazy as B +import Data.Digest.Pure.SHA (sha256, showDigest) import Data.Foldable (traverse_) import Data.List (nub) -import Data.List.Extra (stripSuffix) import qualified Data.Map as M -import Data.Maybe (fromMaybe, isJust) import qualified Data.Set as S import Data.String (fromString) import qualified Data.Text as T @@ -22,29 +21,16 @@ import Hakyll.Core.Util.File (getRecursiveContents, makeDirectories) import Lens.Micro import Lens.Micro.Aeson import Lens.Micro.Mtl -import System.Environment (getArgs) -import System.FilePath ((), splitPath) +import System.FilePath ((), splitPath, takeFileName) import qualified Text.Mustache as Mu import Text.Pandoc.Class (runIOorExplode) -import qualified Text.Pandoc.Extensions -import Text.Pandoc.Options (ReaderOptions(..)) import Text.Pandoc.Readers.Markdown (readMarkdown) +import Text.Pandoc.Writers.HTML (writeHtml5String) import qualified Text.Parsec.Error +import FormatOpts import Types - --- | A shortcut for `liftIO`. -io :: MonadIO m => IO a -> m a -io = liftIO - --- | A helper for throwing an error if something is `Nothing` -just :: String -> Maybe a -> a -just _ (Just val) = val -just err Nothing = error ("Missing: " ++ err) - --- | Test for whether something listy has a suffix -hasSuffix :: Eq a => [a] -> [a] -> Bool -hasSuffix s = isJust . stripSuffix s +import Utils -- | Load the pages from a directory and add them to `pages`. sourcePages :: FilePath -> Site () @@ -54,20 +40,11 @@ sourcePages fp = do getRecursiveContents (pure . const False) fp traverse_ loadPage (map (fp ) links) --- | Default markdown reading options for Pandoc. -markdownReadOpts = - def - { readerExtensions = - Text.Pandoc.Extensions.enableExtension - Text.Pandoc.Extensions.Ext_smart - Text.Pandoc.Extensions.pandocExtensions - } - {- | Extract `PageInfo` about a single page and save it into `pages` in - `SiteState`. -} loadPage :: FilePath -> Site () loadPage fp = do - io $ putStrLn $ "<<< " ++ fp + io $ putStrLn $ "P <- " ++ fp txt <- io $ TIO.readFile fp {- tear out the metadata manually -} (T.take 4 txt == "---\n") `unless` @@ -82,7 +59,7 @@ loadPage fp = do T.unpack . just ("mount point of " ++ fp) $ yml ^? key "title" . _String {- save to the state -} pages %= - M.insert mount PageInfo {_pagePath = fp, _pageMeta = yml, _pagePandoc = md} + M.insert mount PageInfo {_pagePath = fp, _pageMeta = yml, _pageDoc = md} -- | Find which template to use for rendering a page. pageTemplate :: PageInfo -> Site FilePath @@ -102,7 +79,7 @@ compileTemplate :: -> Site (Either Text.Parsec.Error.ParseError Mu.Template) compileTemplate templdir templ = io $ do - putStrLn $ "TTT " ++ (templdir templ) + putStrLn $ "T <- " ++ (templdir templ) Mu.automaticCompile [templdir] templ -- | Use a template set from a given directory. @@ -121,12 +98,12 @@ indexFilename mount = do pure (od mount "index.html") -- | Check that the page was not rendered before, and add it to the rendered set -checkRender :: FilePath -> Site () -checkRender fp = do - found <- S.member fp <$> use renders +checkTarget :: FilePath -> Site () +checkTarget fp = do + found <- S.member fp <$> use targets if found then error $ "colliding renders for page: " ++ fp - else renders %= S.insert fp + else targets %= S.insert fp -- | Render a page using the current template. installPage :: FilePath -> PageInfo -> Site () @@ -136,11 +113,14 @@ installPage mount pi tname <- pageTemplate pi templ <- use $ templates . to (M.! fromString tname) file <- indexFilename mount - checkRender file + checkTarget file io $ do - putStrLn $ ">>> " ++ file + putStrLn $ "P -> " ++ file makeDirectories file - TIO.writeFile file . Mu.substitute templ $ pi ^. pageMeta + body <- runIOorExplode $ writeHtml5String htmlWriteOpts (pi ^. pageDoc) + let Y.Object meta' = pi ^. pageMeta + meta = Y.Object $ KM.insert "body" (Y.String body) meta' + TIO.writeFile file $ Mu.substitute templ meta {- | Install a simple redirect handler page. -} installRedirect :: FilePath -> FilePath -> Site () @@ -148,9 +128,9 @@ installRedirect target from = do tname <- use redirectTemplate templ <- use $ templates . to (M.! fromString tname) file <- indexFilename from - checkRender file + checkTarget file io $ do - putStrLn $ "@@@ " ++ file ++ " -> " ++ target + putStrLn $ "@ -> " ++ file ++ " -> " ++ target makeDirectories file TIO.writeFile file . Mu.substitute templ $ Y.object [("target", Y.String $ T.pack target)] @@ -167,11 +147,35 @@ installRedirects :: Site () installRedirects = use pages >>= traverse_ (uncurry installPageRedirects) . M.assocs +-- | Find the path to the file of a given hash +dataFilename :: String -> FilePath -> Site (FilePath, FilePath) +dataFilename hash basename = do + od <- use outputDir + let (h1, h2) = splitAt 3 hash + loc = "data" h1 h2 basename + pure (od loc, loc) + {- | Install a file. Files are installed into a single shared location. That - prevents file duplication and also gives a bit of control for where the - files reside and what are their names. -} installFile :: FilePath -> Site FilePath -installFile = undefined +installFile fp = do + let basename = takeFileName fp + hash <- showDigest . sha256 <$> io (B.readFile fp) + alreadyExists <- S.member hash <$> use installs + (file, loc) <- dataFilename hash basename + unless alreadyExists $ do + installs %= S.insert hash + checkTarget file + io $ do + putStrLn $ "F -> " ++ fp ++ " -> " ++ file + makeDirectories file + B.readFile fp >>= B.writeFile file + pure loc + +-- | Simply copy a strictly named asset. +installAsset :: FilePath -> Site () +installAsset fp = undefined -- | Render a site for a given tag string. renderTag :: [String] -> Site () @@ -186,6 +190,8 @@ main = flip runStateT emptySiteState $ do traverse sourcePages ["external"] sourceTemplates "templates" - installRedirects use pages >>= traverse (uncurry installPage) . M.assocs + installRedirects + installFile "external/mypage/img/awesome.png" + io $ putStrLn "OK" get >>= io . print diff --git a/templates/default.html b/templates/default.html index ca9aee0..214229e 100644 --- a/templates/default.html +++ b/templates/default.html @@ -1,6 +1,6 @@ {{> head.html}} -aaaaaa +{{{body}}} diff --git a/templates/redirect.html b/templates/redirect.html new file mode 100644 index 0000000..33ee528 --- /dev/null +++ b/templates/redirect.html @@ -0,0 +1,13 @@ + + + + + + + + Permanent Redirect + + +

The page has moved to here.

+ +