diff --git a/.gitignore b/.gitignore index d9947d9..bb5e6a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ _cache/ +_site/ +_optim/ src/archive.org .direnv/ +_shake/ +.shake/ \ No newline at end of file diff --git a/.hlint.yaml b/.hlint.yaml new file mode 100644 index 0000000..d6a5a09 --- /dev/null +++ b/.hlint.yaml @@ -0,0 +1,63 @@ +# HLint configuration file +# https://github.com/ndmitchell/hlint +########################## + +# This file contains a template configuration file, which is typically +# placed as .hlint.yaml in the root of your project + + +# Specify additional command line arguments +# +# - arguments: [--color, --cpp-simple, -XQuasiQuotes] + + +# Control which extensions/flags/modules/functions can be used +# +# - extensions: +# - default: false # all extension are banned by default +# - name: [PatternGuards, ViewPatterns] # only these listed extensions can be used +# - {name: CPP, within: CrossPlatform} # CPP can only be used in a given module +# +# - flags: +# - {name: -w, within: []} # -w is allowed nowhere +# +# - modules: +# - {name: [Data.Set, Data.HashSet], as: Set} # if you import Data.Set qualified, it must be as 'Set' +# - {name: Control.Arrow, within: []} # Certain modules are banned entirely +# +# - functions: +# - {name: unsafePerformIO, within: []} # unsafePerformIO can only appear in no modules + + +# Add custom hints for this project +# +# Will suggest replacing "wibbleMany [myvar]" with "wibbleOne myvar" +# - error: {lhs: "wibbleMany [x]", rhs: wibbleOne x} + + +# Turn on hints that are off by default +# +# Ban "module X(module X) where", to require a real export list +# - warn: {name: Use explicit module export list} +# +# Replace a $ b $ c with a . b $ c +# - group: {name: dollar, enabled: true} +# +# Generalise map to fmap, ++ to <> +# - group: {name: generalise, enabled: true} + + +# Ignore some builtin hints +# - ignore: {name: Use let} +# - ignore: {name: Use const, within: SpecialModule} # Only within certain modules + + +# Define some custom infix operators +# - fixity: infixr 3 ~^#^~ + + +# To generate a suitable file for HLint do: +# $ hlint --default > .hlint.yaml + +# Protolude does not use String and prefer Text so String is undefined and we should use [Char] +- ignore: {name: Use String} diff --git a/Shakefile.hs b/Shakefile.hs new file mode 100644 index 0000000..90ae396 --- /dev/null +++ b/Shakefile.hs @@ -0,0 +1,374 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NoImplicitPrelude #-} + +import Protolude + +import Development.Shake +-- import Development.Shake.Command +import Development.Shake.FilePath + +import Data.Time.Format.ISO8601 (iso8601Show) +import qualified Data.Time.Clock as Clock + +import Control.Monad.Fail +import Data.Aeson +-- import qualified Text.Megaparsec as Megaparsec +import Data.Default ( Default(def) ) +import qualified Data.Text as T +import Text.Mustache +import Text.Pandoc.Class (PandocMonad) +import qualified Text.Pandoc.Class as Pandoc +import Text.Pandoc.Definition ( Pandoc(..) + , Block(..) + , Inline(..) + , MetaValue(..) + , nullMeta + , docTitle + , docDate + , docAuthors + , lookupMeta + ) +import Text.Pandoc.Options ( ReaderOptions(..) + , WriterOptions(..) + , ObfuscationMethod(..) + ) +import qualified Text.Pandoc.Readers as Readers +import Text.Pandoc.Walk (Walkable(..)) +import qualified Text.Pandoc.Writers as Writers + +main :: IO () +main = shakeArgs shOpts buildRules + where + shOpts = + shakeOptions + { shakeVerbosity = Chatty + , shakeLintInside = ["\\"] + } + +-- Configuration +-- Should probably go in a Reader Monad + +srcDir :: FilePath +srcDir = "src" + +siteDir :: FilePath +siteDir = "_site" + +optimDir :: FilePath +optimDir = "_optim" + +-- BlogPost data structure (a bit of duplication because the metas are in Pandoc) + +data BlogPost = + BlogPost { postTitle :: T.Text + , postDate :: T.Text + , postAuthor :: T.Text + , postUrl :: FilePath + , postSrc :: FilePath + , postTags :: [T.Text] + , postDescr :: T.Text + , postToc :: Bool + , postBody :: Pandoc + } + +inlineToText :: PandocMonad m => [Inline] -> m T.Text +inlineToText inline = + Writers.writeAsciiDoc def (Pandoc nullMeta [Plain inline]) + +reformatDate :: Text -> Text +reformatDate = T.takeWhile (/= ' ') . (T.dropAround dateEnvelope) + where + dateEnvelope ' ' = True + dateEnvelope '\n' = True + dateEnvelope '\t' = True + dateEnvelope '[' = True + dateEnvelope ']' = True + dateEnvelope _ = False + +getBlogpostFromMetas + :: (MonadIO m, MonadFail m) => [Char] -> Bool -> Pandoc -> m BlogPost +getBlogpostFromMetas path toc pandoc@(Pandoc meta _) = do + eitherBlogpost <- liftIO $ Pandoc.runIO $ do + title <- fmap (T.dropEnd 1) $ inlineToText $ docTitle meta + date <- fmap reformatDate $ inlineToText $ docDate meta + author <- case head $ docAuthors meta of + Just m -> inlineToText m + Nothing -> return "" + let tags = tagsToList $ lookupMeta "keywords" meta + description = descr $ lookupMeta "description" meta + url = "/" dropDirectory1 path -<.> "org" + return $ BlogPost title date author url path tags description toc pandoc + case eitherBlogpost of + Left _ -> fail "BAD" + Right bp -> return bp + where + tagsToList (Just (MetaList ms)) = map toStr ms + tagsToList _ = [] + descr (Just (MetaString t)) = t + descr _ = "" + toStr (MetaString t) = t + toStr (MetaInlines inlines) = T.intercalate " " $ map inlineToTxt inlines + toStr _ = "" + inlineToTxt (Str t) = t + inlineToTxt _ = "" + + + +sortByPostDate :: [BlogPost] -> [BlogPost] +sortByPostDate = + sortBy (\a b-> compare (postDate b) (postDate a)) + + +build :: FilePath -> FilePath +build = () siteDir + +genAllDeps :: [FilePattern] -> Action [FilePath] +genAllDeps patterns = do + allMatchedFiles <- getDirectoryFiles srcDir patterns + allMatchedFiles & + filter ((/= "html") . takeExtension) & + filter (null . takeExtension) & + map (siteDir ) & + return + +buildRules :: Rules () +buildRules = do + cleanRule + allRule + getPost <- mkGetPost + getPosts <- mkGetPosts getPost + getTemplate <- mkGetTemplate + build "**" %> \out -> do + let asset = dropDirectory1 out + case (takeExtension asset) of + ".html" -> do + if out == siteDir "archive.html" + then buildArchive getPosts getTemplate out + else genHtmlAction getPost getTemplate out + ".txt" -> do + txtExists <- doesFileExist (srcDir asset) + if txtExists + then copyFileChanged (srcDir asset) out + else genAsciiAction getPost out + ".jpg" -> compressImage asset + ".jpeg" -> compressImage asset + ".gif" -> compressImage asset + ".png" -> compressImage asset + _ -> copyFileChanged (srcDir asset) out + +buildArchive + :: (() -> Action [BlogPost]) + -> (FilePath -> Action Template) -> [Char] -> Action () +buildArchive getPosts getTemplate out = do + css <- genAllDeps ["//*.css"] + posts <- fmap sortByPostDate $ getPosts () + need $ css <> map postSrc posts + let + title :: Text + title = "#+title: Posts" + articleList = toS $ T.intercalate "\n" $ map postInfo posts + fileContent = title <> "\n\n" <> articleList + eitherResult <- liftIO $ Pandoc.runIO $ Readers.readOrg (def { readerStandalone = True }) (toS fileContent) + bp <- case eitherResult of + Left _ -> fail "BAD" + Right pandoc -> getBlogpostFromMetas out False pandoc + innerHtml <- genHtml bp + template <- getTemplate ("templates" "main.mustache") + let htmlContent = + renderMustache template + $ object [ "title" .= postTitle bp + , "author" .= postAuthor bp + , "date" .= postDate bp + , "tags" .= postTags bp + , "description" .= postDescr bp + , "body" .= innerHtml + ] + writeFile' out (toS htmlContent) + +postInfo :: BlogPost -> Text +postInfo bp = + "- " <> date <> ": " <> orglink + where + date = T.takeWhile (/= ' ') (postDate bp) + orglink = "[[file:" <> (toS (postUrl bp)) <> "][" <> (postTitle bp) <> "]]" + +replaceLinks :: Pandoc -> Pandoc +replaceLinks = walk replaceOrgLink + where + replaceOrgLink :: Inline -> Inline + replaceOrgLink lnk@(Link attr inl (url,txt)) = + if takeExtension (toS url) == ".org" + then Link attr inl ((toS (toS url -<.> ".html")),txt) + else lnk + replaceOrgLink x = x + +orgContentToText :: (MonadIO m, MonadFail m) => Text -> m Text +orgContentToText org = do + eitherResult <- liftIO $ Pandoc.runIO $ Readers.readOrg (def { readerStandalone = True }) org + pandoc <- case eitherResult of + Left _ -> fail "BAD" + Right p -> return p + eitherHtml <- liftIO $ Pandoc.runIO $ Writers.writeHtml5String (def {writerEmailObfuscation = ReferenceObfuscation}) pandoc + case eitherHtml of + Left _ -> fail "BAD" + Right innerHtml -> return innerHtml + +postamble :: (MonadIO m, MonadFail m) => Text -> BlogPost -> m Text +postamble now bp = + orgContentToText $ unlines $ + [ "@@html:@@" + ] + +genHtml :: (MonadIO m, MonadFail m) => BlogPost -> m Text +genHtml bp = do + let htmlBody = replaceLinks (postBody bp) + eitherHtml <- liftIO $ + Pandoc.runIO $ + Writers.writeHtml5String + (def { writerTableOfContents = postToc bp + , writerEmailObfuscation = ReferenceObfuscation + }) + htmlBody + body <- case eitherHtml of + Left _ -> fail "BAD" + Right innerHtml -> return innerHtml + now <- liftIO Clock.getCurrentTime + footer <- postamble (toS (iso8601Show now)) bp + return (body <> footer) + +origin :: Text +origin = "https://her.esy.fun" + +genHtmlAction + :: (FilePath -> Action BlogPost) + -> (FilePath -> Action Template) -> [Char] -> Action () +genHtmlAction getPost getTemplate out = do + let isPost = takeDirectory1 (dropDirectory1 out) == "posts" + template <- getTemplate ("templates" if isPost then "post.mustache" else "main.mustache") + let srcFile = srcDir (dropDirectory1 (out -<.> "org")) + liftIO $ putText $ "need: " <> (toS srcFile) <> " -> " <> (toS out) + need [srcFile] + bp <- getPost srcFile + innerHtml <- genHtml bp + let htmlContent = + renderMustache template + $ object [ "title" .= postTitle bp + , "author" .= postAuthor bp + , "date" .= postDate bp + , "tags" .= postTags bp + , "description" .= postDescr bp + , "body" .= innerHtml + , "orgsource" .= T.pack (postUrl bp -<.> "org") + , "txtsource" .= T.pack (postUrl bp -<.> "txt") + , "permalink" .= T.pack (toS origin <> postUrl bp) + ] + writeFile' out (toS htmlContent) + +genAscii :: (MonadIO m, MonadFail m) => BlogPost -> m Text +genAscii bp = do + eitherAscii <- liftIO $ Pandoc.runIO $ Writers.writePlain def (postBody bp) + case eitherAscii of + Left _ -> fail "BAD" + Right innerAscii -> return innerAscii + + +genAsciiAction + :: (FilePath -> Action BlogPost) + -> [Char] -> Action () +genAsciiAction getPost out = do + let srcFile = srcDir (dropDirectory1 (out -<.> "org")) + need [srcFile] + bp <- getPost srcFile + innerAscii <- genAscii bp + let preamble = postTitle bp <> "\n" + <> T.replicate (T.length (postTitle bp)) "=" <> "\n\n" + <> postAuthor bp <> "\n" + <> postDate bp <> "\n" + <> toS origin <> toS (postUrl bp) <> "\n\n" + writeFile' out (toS (preamble <> toS innerAscii)) + +allHtmlAction :: Action () +allHtmlAction = do + allOrgFiles <- getDirectoryFiles srcDir ["//*.org"] + let allHtmlFiles = map (-<.> "html") allOrgFiles + need (map build allHtmlFiles) + +allAsciiAction :: Action () +allAsciiAction = do + allOrgFiles <- getDirectoryFiles srcDir ["//*.org"] + let allAsciiFiles = map (-<.> "txt") allOrgFiles + need (map build allAsciiFiles) + +compressImage :: FilePath -> Action () +compressImage img = do + let src = srcDir img + dst = siteDir img + need [src] + let dir = takeDirectory dst + dirExists <- doesDirectoryExist dir + when (not dirExists) $ + command [] "mkdir" ["-p", dir] + command_ [] "convert" [ src + , "-strip" + , "-resize","320x320>" + , "-interlace","Plane" + , "-quality","85" + , "-define","filter:blur=0.75" + , "-filter","Gaussian" + , "-ordered-dither","o4x4,4" + , dst ] + +allRule :: Rules () +allRule = + phony "all" $ do + allAssets <- filter (/= ".DS_Store") <$> getDirectoryFiles srcDir ["**"] + need (map build $ allAssets <> ["archive.html"]) + allHtmlAction + allAsciiAction + +cleanRule :: Rules () +cleanRule = + phony "clean" $ do + putInfo "Cleaning files in _site and _optim" + forM_ [siteDir,optimDir] $ flip removeFilesAfter ["**"] + +mkGetTemplate :: Rules (FilePath -> Action Template) +mkGetTemplate = newCache $ \path -> do + fileContent <- readFile' path + let res = compileMustacheText "page" (toS fileContent) + case res of + Left _ -> fail "BAD" + Right template -> return template + +tocRequested :: Text -> Bool +tocRequested fc = + let toc = fc & T.lines + & map T.toLower + & filter (T.isPrefixOf (T.pack "#+options: ")) + & head + & fmap (filter (T.isPrefixOf (T.pack "toc:")) . T.words) + in toc == Just ["toc:t"] + +mkGetPost :: Rules (FilePath -> Action BlogPost) +mkGetPost = newCache $ \path -> do + fileContent <- readFile' path + let toc = tocRequested (toS fileContent) + eitherResult <- liftIO $ Pandoc.runIO $ Readers.readOrg (def { readerStandalone = True }) (toS fileContent) + case eitherResult of + Left _ -> fail "BAD" + Right pandoc -> getBlogpostFromMetas path toc pandoc + +mkGetPosts :: (FilePath -> Action b) -> Rules (() -> Action [b]) +mkGetPosts getPost = + newCache $ \() -> mapM getPost =<< getDirectoryFiles "" ["src/posts//*.org"] diff --git a/_optim/.gitignore b/_optim/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/_optim/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/_site/.gitignore b/_site/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/_site/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..71b68d1 --- /dev/null +++ b/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh +mkdir -p _shake +ghc --make Shakefile.hs -rtsopts -threaded -with-rtsopts=-I0 -outputdir=_shake -o _shake/build && _shake/build "$@" diff --git a/engine/compresscss.sh b/engine/compresscss.sh index 3c4670f..00094c1 100755 --- a/engine/compresscss.sh +++ b/engine/compresscss.sh @@ -1,9 +1,3 @@ -#!/usr/bin/env nix-shell -#!nix-shell --pure -#!nix-shell -i bash -#!nix-shell -I nixpkgs="https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz" -#!nix-shell -p bash minify - -# nix-shell -p nodePackages.clean-css +#!/usr/bin/env bash minify "$1" > "$2" diff --git a/engine/dup-for-themes.sh b/engine/dup-for-themes.sh index ae10842..f9d8c65 100755 --- a/engine/dup-for-themes.sh +++ b/engine/dup-for-themes.sh @@ -1,6 +1,4 @@ -#!/usr/bin/env nix-shell -#!nix-shell -i zsh -#!nix-shell -I nixpkgs="https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz" +#!/usr/bin/env zsh cd "$(git rev-parse --show-toplevel)" || exit 1 diff --git a/engine/mkrss.sh b/engine/mkrss.sh index 77f804e..f964ee5 100755 --- a/engine/mkrss.sh +++ b/engine/mkrss.sh @@ -1,6 +1,4 @@ -#!/usr/bin/env nix-shell -#!nix-shell -i zsh -#!nix-shell -I nixpkgs="https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz" +#!/usr/bin/env zsh cd "$(git rev-parse --show-toplevel)" || exit 1 # Directory diff --git a/engine/optim-classes.sh b/engine/optim-classes.sh index 9f78d68..2b5d0e7 100755 --- a/engine/optim-classes.sh +++ b/engine/optim-classes.sh @@ -1,6 +1,4 @@ -#!/usr/bin/env nix-shell -#!nix-shell -i zsh -#!nix-shell -I nixpkgs="https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz" +#!/usr/bin/env zsh cd "$(git rev-parse --show-toplevel)" || exit 1 webdir="_optim" diff --git a/engine/optim-html.sh b/engine/optim-html.sh index baf1495..619f5f4 100755 --- a/engine/optim-html.sh +++ b/engine/optim-html.sh @@ -1,6 +1,4 @@ -#!/usr/bin/env nix-shell -#!nix-shell -i zsh -#!nix-shell -I nixpkgs="https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz" +#!/usr/bin/env zsh cd "$(git rev-parse --show-toplevel)" || exit 1 webdir="_optim" diff --git a/engine/shell.nix b/engine/shell.nix deleted file mode 100644 index 0eab84d..0000000 --- a/engine/shell.nix +++ /dev/null @@ -1,11 +0,0 @@ -# { pkgs ? import {} }: -{ pkgs ? import (fetchTarball https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz) {} }: - pkgs.mkShell { - buildInputs = [ pkgs.coreutils - pkgs.html-xml-utils - pkgs.zsh - pkgs.perl - pkgs.perlPackages.URI - pkgs.minify - ]; - } diff --git a/engine/update-file-size.sh b/engine/update-file-size.sh index 341ea88..9e70862 100755 --- a/engine/update-file-size.sh +++ b/engine/update-file-size.sh @@ -1,6 +1,4 @@ -#!/usr/bin/env nix-shell -#!nix-shell -i zsh -#!nix-shell -I nixpkgs="https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz" +#!/usr/bin/env zsh cd "$(git rev-parse --show-toplevel)" || exit 1 webdir="_optim" diff --git a/nix/sources.json b/nix/sources.json new file mode 100644 index 0000000..58f2808 --- /dev/null +++ b/nix/sources.json @@ -0,0 +1,38 @@ +{ + "niv": { + "branch": "master", + "description": "Easy dependency management for Nix projects", + "homepage": "https://github.com/nmattia/niv", + "owner": "nmattia", + "repo": "niv", + "rev": "f73bf8d584148677b01859677a63191c31911eae", + "sha256": "0jlmrx633jvqrqlyhlzpvdrnim128gc81q5psz2lpp2af8p8q9qs", + "type": "tarball", + "url": "https://github.com/nmattia/niv/archive/f73bf8d584148677b01859677a63191c31911eae.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "nixpkgs": { + "branch": "nixpkgs-unstable", + "description": "A read-only mirror of NixOS/nixpkgs tracking the released channels. Send issues and PRs to", + "homepage": "https://github.com/NixOS/nixpkgs", + "owner": "NixOS", + "repo": "nixpkgs-channels", + "rev": "a84cbb60f0296210be03c08d243670dd18a3f6eb", + "sha256": "04j07c98iy66hpzha7brz867dcl9lkflck43xvz09dfmlvqyzmiz", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs-channels/archive/a84cbb60f0296210be03c08d243670dd18a3f6eb.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "shake": { + "branch": "master", + "description": "Shake build system", + "homepage": "http://shakebuild.com", + "owner": "ndmitchell", + "repo": "shake", + "rev": "4536d9ce5cef0e56395fd61ccef9816c9b420fd1", + "sha256": "1s7hjhcc09l026jaca3ndbb103s9d7qlx4vqzx2s6j4rr751nd70", + "type": "tarball", + "url": "https://github.com/ndmitchell/shake/archive/4536d9ce5cef0e56395fd61ccef9816c9b420fd1.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + } +} diff --git a/nix/sources.nix b/nix/sources.nix new file mode 100644 index 0000000..8a725cb --- /dev/null +++ b/nix/sources.nix @@ -0,0 +1,134 @@ +# This file has been generated by Niv. + +let + + # + # The fetchers. fetch_ fetches specs of type . + # + + fetch_file = pkgs: spec: + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; } + else + pkgs.fetchurl { inherit (spec) url sha256; }; + + fetch_tarball = pkgs: spec: + if spec.builtin or true then + builtins_fetchTarball { inherit (spec) url sha256; } + else + pkgs.fetchzip { inherit (spec) url sha256; }; + + fetch_git = spec: + builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; }; + + fetch_builtin-tarball = spec: + builtins.trace + '' + WARNING: + The niv type "builtin-tarball" will soon be deprecated. You should + instead use `builtin = true`. + + $ niv modify -a type=tarball -a builtin=true + '' + builtins_fetchTarball { inherit (spec) url sha256; }; + + fetch_builtin-url = spec: + builtins.trace + '' + WARNING: + The niv type "builtin-url" will soon be deprecated. You should + instead use `builtin = true`. + + $ niv modify -a type=file -a builtin=true + '' + (builtins_fetchurl { inherit (spec) url sha256; }); + + # + # Various helpers + # + + # The set of packages used when specs are fetched using non-builtins. + mkPkgs = sources: + let + sourcesNixpkgs = + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {}; + hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; + hasThisAsNixpkgsPath = == ./.; + in + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import {} + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; + + # The actual fetching function. + fetch = pkgs: name: spec: + + if ! builtins.hasAttr "type" spec then + abort "ERROR: niv spec ${name} does not have a 'type' attribute" + else if spec.type == "file" then fetch_file pkgs spec + else if spec.type == "tarball" then fetch_tarball pkgs spec + else if spec.type == "git" then fetch_git spec + else if spec.type == "builtin-tarball" then fetch_builtin-tarball spec + else if spec.type == "builtin-url" then fetch_builtin-url spec + else + abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; + + # Ports of functions for older nix versions + + # a Nix version of mapAttrs if the built-in doesn't exist + mapAttrs = builtins.mapAttrs or ( + f: set: with builtins; + listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) + ); + + # fetchTarball version that is compatible between all the versions of Nix + builtins_fetchTarball = { url, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchTarball; + in + if lessThan nixVersion "1.12" then + fetchTarball { inherit url; } + else + fetchTarball attrs; + + # fetchurl version that is compatible between all the versions of Nix + builtins_fetchurl = { url, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchurl; + in + if lessThan nixVersion "1.12" then + fetchurl { inherit url; } + else + fetchurl attrs; + + # Create the final "sources" from the config + mkSources = config: + mapAttrs ( + name: spec: + if builtins.hasAttr "outPath" spec + then abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = fetch config.pkgs name spec; } + ) config.sources; + + # The "config" used by the fetchers + mkConfig = + { sourcesFile ? ./sources.json + , sources ? builtins.fromJSON (builtins.readFile sourcesFile) + , pkgs ? mkPkgs sources + }: rec { + # The sources, i.e. the attribute set of spec name to spec + inherit sources; + + # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers + inherit pkgs; + }; +in +mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/shell.nix b/shell.nix index fad7781..bff21dd 100644 --- a/shell.nix +++ b/shell.nix @@ -1,15 +1,32 @@ -# { pkgs ? import {} }: -{ pkgs ? import (fetchTarball https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz) {} }: - let my_aspell = pkgs.aspellWithDicts(p: with p; [en fr]); - in - pkgs.mkShell { - buildInputs = [ pkgs.coreutils - pkgs.html-xml-utils - pkgs.zsh - pkgs.perl - pkgs.perlPackages.URI - pkgs.minify - pkgs.haskellPackages.sws - pkgs.cacert - ]; +let + sources = import ./nix/sources.nix; + pkgs = import sources.nixpkgs {}; + pkgs1909 = import (fetchTarball https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz) {}; + haskellDeps = ps : with ps; [ + shake + pandoc + data-default + protolude + pkgs1909.haskellPackages.sws + stache + ]; + ghc = pkgs.haskellPackages.ghcWithPackages haskellDeps; +in +pkgs.mkShell { + buildInputs = with pkgs; + [ cacert + coreutils + html-xml-utils + zsh + perl + perlPackages.URI + minify + niv + ghc + git + direnv + haskellPackages.shake + # for emacs dev + ripgrep + ]; } diff --git a/src/css/y.css b/src/css/y.css index edcd680..1afeece 100644 --- a/src/css/y.css +++ b/src/css/y.css @@ -309,9 +309,6 @@ dl dd { font-size: 85%; margin-bottom: 0.4rem; } -.footnotes { - border-top: 1px solid hsl(0, 0%, 39%); -} /* Center title and paragraph */ .abstract, @@ -319,7 +316,9 @@ dl dd { text-align: center; } .abstract { - margin: 2.25rem 0; + margin: 2.25rem; + font-size: 0.85rem; + font-style: italic; } /* Format the LaTeX symbol correctly (a higher up, e lower) */ @@ -430,6 +429,7 @@ footer { margin: 3em 0; border-bottom: solid 1px; line-height: 1em; font-size: 0.85em; + text-align: center; } td { border-bottom: none; padding: .2rem; } table { margin-top: 1rem; } @@ -554,17 +554,20 @@ body, body > div { } a,a:visited { color: var(--hl); } +figcaption { color: var(--fg0); } + /* ---- SYNTAX HIGHLIGHTING ---- */ #table-of-contents { text-align: left; } + .org-rainbow-delimiters-depth-1, .org-rainbow-delimiters-depth-9, .org-css-selector, .org-builtin, -.IN_REVIEW { +.IN_REVIEW, .ex { color:var(--c); } .org-rainbow-delimiters-depth-2, .org-nix-builtin, .org-variable-name, .org-haskell-definition, .org-haskell-operator, .org-function-name, .org-diff-changed, -.org-nix-attribute, .org-nxml-element-local-name { +.org-nix-attribute, .org-nxml-element-local-name, .op, .fu, .ot { color:var(--b); } @@ -573,21 +576,21 @@ a,a:visited { color: var(--hl); } } .org-rainbow-delimiters-depth-4, .org-diff-hunk-header, .org-sh-quoted-exec, -.CANCELED { +.CANCELED, .bu { color:var(--m); } .org-rainbow-delimiters-depth-5, .org-diff-removed, .TODO { color:var(--r); } -.org-rainbow-delimiters-depth-6, .org-haskell-constructor { +.org-rainbow-delimiters-depth-6, .org-haskell-constructor, .dt { color:var(--o); } .org-rainbow-delimiters-depth-7, .org-type, .org-constant, .org-diff-header, -.org-haskell-keyword, .org-haskell-type, .IN_PROGRESS { +.org-haskell-keyword, .org-haskell-type, .IN_PROGRESS, .kw { color:var(--y); } .org-rainbow-delimiters-depth-8, .org-sh-heredoc, .org-diff-added, .org-string, -.org-doc, .org-keyword, .DONE { +.org-doc, .org-keyword, .DONE, .st { color:var(--g); } @@ -595,6 +598,6 @@ a,a:visited { color: var(--hl); } .org-diff-none, .org-preprocessor, .org-comment-delimiter, .org-comment, .org-outshine-level-1, .org-outshine-level-2, .org-outshine-level-3, .org-outshine-level-4, .org-outshine-level-5, .org-outshine-level-6, -.org-outshine-level-7, .org-outshine-level-8, .org-outshine-level-9 { +.org-outshine-level-7, .org-outshine-level-8, .org-outshine-level-9, .co { color:var(--fg0); } diff --git a/src/demo.org b/src/demo.org index 91145e1..131689a 100644 --- a/src/demo.org +++ b/src/demo.org @@ -344,9 +344,9 @@ After the rule. an image: +#+ATTR_HTML: The Experiment #+CAPTION: Testing include an image #+NAME: fig:test-image -#+ATTR_HTML: The Experiment [[../img/a.png]] diff --git a/src/drafts/XXXX-how-i-use-nix/index.org b/src/drafts/XXXX-how-i-use-nix/index.org index ef44c01..29eed30 100644 --- a/src/drafts/XXXX-how-i-use-nix/index.org +++ b/src/drafts/XXXX-how-i-use-nix/index.org @@ -3,9 +3,7 @@ #+Email: yann@esposito.host #+Date: [2020-06-14 Sun] #+KEYWORDS: nix, programming -#+DESCRIPTION: In this article I explain how I use nix. -#+DESCRIPTION: As a brew replacement, as home environment manager, -#+DESCRIPTION: to have reproductible dev environment. +#+DESCRIPTION: In this article I explain how I use nix. As a brew replacement, as home environment manager, to have reproductible dev environment. #+LANGUAGE: en #+LANG: en #+OPTIONS: H:5 auto-id:t toc:nil diff --git a/src/posts/0002-troll-2/index.org b/src/posts/0002-troll-2/index.org index 2671b78..ed07fe5 100644 --- a/src/posts/0002-troll-2/index.org +++ b/src/posts/0002-troll-2/index.org @@ -44,9 +44,9 @@ goblins. Those costume looks very bad and cheap. So much you can only find them not terrorizing but funny and ridiculous. +#+ATTR_HTML: A goblin #+CAPTION: One goblin during the introduction scene of Troll 2 #+NAME: fig:troll-2-intro -#+ATTR_HTML: A goblin [[./Troll-2-intro.jpg]] Soon after that, you realize the acting of all actors is extremely bad. @@ -55,9 +55,9 @@ To give you an idea, the only equal bad acting I ever witnessed was while looking at amateurs first Youtube movies trying to follow a scenario. Apparently most actors were amateurs, it was their first and last movie. +#+ATTR_HTML: A bad acting demonstration #+CAPTION: One particularly terrible acting scene #+NAME: fig:bad-acting -#+ATTR_HTML: A bad acting demonstration [[file:bad-acting.png]] The dialog are, really something... @@ -83,9 +83,9 @@ They win against the monsters with, what I believe was a failed attempt at humor. It misses the point so bad, that the irony still make it funny. +#+ATTR_HTML: Eliott prevents his family to eat the food by urinating on the table #+CAPTION: Our hero save the day by urinating on the table. His family is frozen for 30s said grandpa, they were for 70s. #+NAME: fig:prevent-eating -#+ATTR_HTML: Eliott prevents his family to eat the food by urinating on the table [[./prevent-eating-scene.jpg]] Of course, the very last scene is a classical so terrible cliché. diff --git a/src/posts/0010-Haskell-Now/index.org b/src/posts/0010-Haskell-Now/index.org index 910d35a..4bc5b4f 100644 --- a/src/posts/0010-Haskell-Now/index.org +++ b/src/posts/0010-Haskell-Now/index.org @@ -4,13 +4,7 @@ #+author: Yann Esposito #+EMAIL: yann@esposito.host #+keywords: Haskell, programming, functional, tutorial -#+DESCRIPTION: A short and intense introduction to Haskell. -#+DESCRIPTION: This is an update of my old (2012) article. -#+DESCRIPTION: A lot of things have changed since then. -#+DESCRIPTION: Mostly I changed my approach about the easiest way to install -#+DESCRIPTION: a Haskell playground. -#+DESCRIPTION: I removed the not as important part, and added a short -#+DESCRIPTION: introduction about starting a new project. +#+DESCRIPTION: A short and intense introduction to Haskell. This is an update of my old (2012) article. A lot of things have changed since then. Mostly I changed my approach about the easiest way to install a Haskell playground. I removed the not as important part, and added a short introduction about starting a new project. #+OPTIONS: auto-id:t toc:t #+STARTUP: overview @@ -178,32 +172,32 @@ Otherwise, you can follow my advice to use nix: 4. Put the following =shell.nix= file inside it #+begin_src nix :tangle shell.nix -{ nixpkgs ? import (fetchTarball https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz) {} }: -let - inherit (nixpkgs) pkgs; - inherit (pkgs) haskellPackages; - - haskellDeps = ps: with ps; [ - base - protolude - containers - ]; - - ghc = haskellPackages.ghcWithPackages haskellDeps; - - nixPackages = [ - ghc - pkgs.gdb - haskellPackages.cabal-install - ]; -in -pkgs.stdenv.mkDerivation { - name = "env"; - buildInputs = nixPackages; - shellHook = '' - export PS1="\n\[[hs:\033[1;32m\]\W\[\033[0m\]]> " - ''; -} + { nixpkgs ? import (fetchTarball https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz) {} }: + let + inherit (nixpkgs) pkgs; + inherit (pkgs) haskellPackages; + + haskellDeps = ps: with ps; [ + base + protolude + containers + ]; + + ghc = haskellPackages.ghcWithPackages haskellDeps; + + nixPackages = [ + ghc + pkgs.gdb + haskellPackages.cabal-install + ]; + in + pkgs.stdenv.mkDerivation { + name = "env"; + buildInputs = nixPackages; + shellHook = '' + export PS1="\n\[[hs:\033[1;32m\]\W\[\033[0m\]]> " + ''; + } #+end_src 5. In the =hsenv= directory, in a terminal, run =nix-shell --pure=. @@ -219,11 +213,11 @@ pkgs.stdenv.mkDerivation { something like this: #+begin_src -~/hsenv> nix-shell -[nix-shell:~/hsenv]$ ghci -GHCi, version 8.6.5: http://www.haskell.org/ghc/ :? for help -Prelude> import Protolude -Prelude Protolude> + ~/hsenv> nix-shell + [nix-shell:~/hsenv]$ ghci + GHCi, version 8.6.5: http://www.haskell.org/ghc/ :? for help + Prelude> import Protolude + Prelude Protolude> #+end_src Congratulations you should be ready to start now. @@ -1492,7 +1486,7 @@ The only way to work around this problem is to use some meta-programming trick, for example using the pre-processor. In C++ there is a better way, C++ templates: -#+BEGIN_SRC c++ +#+BEGIN_SRC cpp #include #include using namespace std; @@ -3880,9 +3874,7 @@ I will not argue much, but mainly, semantic versionning and Haskell versionning are just a "right to break things to your users". I don't want to talk a lot more about this, but, it would be nice if more -people would watch this talk[fn:9] related to versionning. - -[fn:9]: [[https://www.youtube.com/watch?v=oyLBGkS5ICk][Spec-ulation Keynote - Rich Hickey]] +people would watch this talk[fn:8] related to versionning. If you want to know more about Haskell versionning convention: https://pvp.haskell.org @@ -4082,30 +4074,32 @@ Thank you man. As of today, the definition of =IO= is no more visible into =base=. We have the following explanation in [[http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.IO.html][=GHC.IO.hs=]]: #+begin_quote -#+begin_src -The IO Monad is just an instance of the ST monad, where the state is -the real world. We use the exception mechanism (in GHC.Exception) to -implement IO exceptions. - -NOTE: The IO representation is deeply wired in to various parts of the -system. The following list may or may not be exhaustive: - -Compiler - types of various primitives in PrimOp.hs - -RTS - forceIO (StgStartup.cmm) - - catchzh_fast, (un)?blockAsyncExceptionszh_fast, raisezh_fast - (Exception.cmm) - - raiseAsync (RaiseAsync.c) - -Prelude - GHC.IO.hs, and several other places including - GHC.Exception.hs. - -Libraries - parts of hslibs/lang. - ---SDM -#+end_src + #+begin_src + The IO Monad is just an instance of the ST monad, where the state is + the real world. We use the exception mechanism (in GHC.Exception) to + implement IO exceptions. + + NOTE: The IO representation is deeply wired in to various parts of the + system. The following list may or may not be exhaustive: + + Compiler - types of various primitives in PrimOp.hs + + RTS - forceIO (StgStartup.cmm) + - catchzh_fast, (un)?blockAsyncExceptionszh_fast, raisezh_fast + (Exception.cmm) + - raiseAsync (RaiseAsync.c) + + Prelude - GHC.IO.hs, and several other places including + GHC.Exception.hs. + + Libraries - parts of hslibs/lang. + + --SDM + #+end_src #+end_quote [fn:7] Well, you'll certainly need to practice a bit to get used to them and to understand when you can use them and create your own. But you already made a big step in this direction. + +[fn:8] [[https://www.youtube.com/watch?v=oyLBGkS5ICk][Spec-ulation Keynote - Rich Hickey]] diff --git a/src/posts/0012-solaryzed-theme/index.org b/src/posts/0012-solaryzed-theme/index.org index 1811773..26ecfe4 100644 --- a/src/posts/0012-solaryzed-theme/index.org +++ b/src/posts/0012-solaryzed-theme/index.org @@ -58,6 +58,7 @@ Here is the CSS you could use: {{{colorbox(b1,black,#a7abb5)}}} {{{colorbox(b2,black,#e5e8f0)}}} {{{colorbox(b3,black,#f3f6fe)}}} +@@html:
@@ {{{colorbox(y,white,#ad8c51)}}} {{{colorbox(o,white,#a9664b)}}} {{{colorbox(r,white,#af6256)}}} diff --git a/src/posts/0013-how-to-choose-your-tools/index.org b/src/posts/0013-how-to-choose-your-tools/index.org index 6aa6279..e345974 100644 --- a/src/posts/0013-how-to-choose-your-tools/index.org +++ b/src/posts/0013-how-to-choose-your-tools/index.org @@ -3,10 +3,7 @@ #+Email: yann@esposito.host #+Date: [2020-05-09 Sat] #+KEYWORDS: emacs, softwares -#+DESCRIPTION: Modern tools tend to disapears. -#+DESCRIPTION: An app on the web will change, and could break for the worst. -#+DESCRIPTION: Quite often investing in long living tools which are harder start -#+DESCRIPTION: with will be worth the investment. +#+DESCRIPTION: Modern tools tend to disapears. An app on the web will change, and could break for the worst. Quite often investing in long living tools which are harder start with will be worth the investment. #+LANGUAGE: en #+LANG: en #+OPTIONS: H:5 auto-id:t @@ -19,8 +16,10 @@ And this week-end, in the morning I read those: - [[https://news.ycombinator.com/item?id=23107123][Making Emacs popular again]] - [[https://news.ycombinator.com/item?id=23092904][Github Codespace]] -#+DOWNLOADED: https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.g7OSuCGH0u7OIUA9vdxlTAEsCo%26pid%3DApi&f=1 @ 2020-05-09 12:49:34 #+ATTR_HTML: :alt Midsommar Welcome +#+DOWNLOADED: https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.g7OSuCGH0u7OIUA9vdxlTAEsCo%26pid%3DApi&f=1 @ 2020-05-09 12:49:34 +#+NAME: Welcome +#+CAPTION: Midsommar Welcome [[file:2020-05-09_12-49-34_.jpeg]] @@ -93,8 +92,9 @@ For the single developers and open source developers this offer: But the price to pay is hidden. +#+ATTR_HTML: :alt Midsommar Sorrow #+DOWNLOADED: https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.FrCRrhENMjdfD4pUcAwKEgHaEK%26pid%3DApi&f=1 @ 2020-05-09 12:48:31 -#+ATTR_HTML: :alt Midsommar Cry +#+CAPTION: Midsommar Sorrow [[file:2020-05-09_12-48-31_.jpeg]] @@ -178,6 +178,7 @@ future. :CUSTOM_ID: post-conclusion :END: +#+ATTR_HTML: :alt Midsommar Joy #+CAPTION: Midsommar Joy [[./midsommar-joy.jpeg]] diff --git a/templates/main.mustache b/templates/main.mustache new file mode 100644 index 0000000..8629892 --- /dev/null +++ b/templates/main.mustache @@ -0,0 +1,55 @@ + + + + + + {{title}} + + + + + + + + + + +
+
+ | +
+
+
+
+ +

{{title}}

+
+
+ {{{ body }}} +
+
+
+ +
+
+
+ + diff --git a/templates/post.mustache b/templates/post.mustache new file mode 100644 index 0000000..e963c55 --- /dev/null +++ b/templates/post.mustache @@ -0,0 +1,64 @@ + + + + + + {{title}} + + + + + + + +
+
+ | +
+
+
+
+ +
+

{{title}}

+
{{author}}
+
on + Yann Esposito's blog - + source - + txt - + +
+
+ {{description}} +
+
+
+
+ {{{body}}} +
+
+
+ +
+
+
+ +