A blog about stuff but also things.
As I was writing yesterday's post, I wanted to count the number of posts I had written during my last involuntary vacation in the summer of 2022. There is of course a very simple way to do this:
user.clj
on the blog's classpath {:deps {jmglov/jmglov {:local/root "."}
io.github.borkdude/quickblog {:local/root "../clojure/quickblog"}
#_"You use the newest SHA here:"
#_{:git/sha "b69c11f4292702f78a8ac0a9f32379603bebf2af"}
}
;; ...
}
deps.edn
, because of this incantation: {jmglov/jmglov {:local/root "."}
deps.edn
instead: {:paths ["." "classes"]
:deps {markdown-clj/markdown-clj {:mvn/version "1.10.7"}
org.babashka/cli {:mvn/version "0.8.55"}
babashka/fs {:mvn/version "0.1.6"}
org.clojure/data.xml {:mvn/version "0.2.0-alpha6"}
hiccup/hiccup {:mvn/version "2.0.0-alpha2"}
babashka/pods {:git/url "https://github.com/babashka/pods"
:git/sha "93081b75e66fb4c4d161f89e714c6b9e8d55c8d5"}
rewrite-clj/rewrite-clj {:mvn/version "1.1.45"}
selmer/selmer {:mvn/version "1.12.53"}}}
user.clj
there: (ns user)
cider-jack-in-clj
) and select babashka from the REPL options! 🎉bb.edn
to see how the quickblog opts are defined: {:deps { ; ...
}
:tasks
{:init (def opts {:blog-title "jmglov's blog"
:blog-author "Josh Glover"
:blog-description "A blog about stuff but also things."
:blog-root "https://jmglov.net/blog/"
:about-link "https://jmglov.net/"
:twitter-handle "jmglov"
:assets-dir "blog/assets"
:num-index-posts 3
:cache-dir ".cache"
:favicon true
:favicon-dir "favicon"
:out-dir "public/blog"
:posts-dir "blog/posts"
:templates-dir "blog/templates"})
:requires ([babashka.cli]
[babashka.fs :as fs]
[clojure.string :as str]
[quickblog.api :as qb]
[quickblog.cli :as cli])
;; ...
}}
(def opts {...})
bit into user.clj
, but then, struck by a blinding flash of insight that bb.edn
is just EDN, decide to Not Repeat Yourself (NRY, obv) and just read in bb.edn
and set opts from what's defined there: (ns user
(:require [clojure.edn :as edn]))
(comment
(-> (slurp "bb.edn")
edn/read-string
:tasks
:init)
;; (def opts {:blog-title "jmglov's blog"
:blog-author "Josh Glover"
:blog-description "A blog about stuff but also things."
:blog-root "https://jmglov.net/blog/"
:about-link "https://jmglov.net/"
:twitter-handle "jmglov"
:assets-dir "blog/assets"
:num-index-posts 3
:cache-dir ".cache"
:favicon true
:favicon-dir "favicon"
:out-dir "public/blog"
:posts-dir "blog/posts"
:templates-dir "blog/templates"})
)
(def opts ...)
and nothing untoward and/or sinister)! (comment
(let [[form & params :as expr] (-> (slurp "bb.edn")
edn/read-string
:tasks
:init)]
(when (and (= 'def form) (= 'opts (first params)))
(eval expr)))
;; => #'user/opts
opts
;; => {:blog-description "A blog about stuff but also things.",
;; :blog-author "Josh Glover",
;; :num-index-posts 3,
;; :favicon-dir "favicon",
;; :posts-dir "blog/posts",
;; :assets-dir "blog/assets",
;; :templates-dir "blog/templates",
;; :favicon true,
;; :out-dir "public/blog",
;; :blog-root "https://jmglov.net/blog/",
;; :link-posts true,
;; :cache-dir ".cache",
;; :about-link "https://jmglov.net/",
;; :blog-title "jmglov's blog"}
)
opts.edn
... {:blog-title "jmglov's blog"
:blog-author "Josh Glover"
:blog-description "A blog about stuff but also things."
:blog-root "https://jmglov.net/blog/"
:about-link "https://jmglov.net/"
:assets-dir "blog/assets"
:num-index-posts 3
:cache-dir ".cache"
:favicon true
:favicon-dir "favicon"
:out-dir "public/blog"
:posts-dir "blog/posts"
:templates-dir "blog/templates"
:link-posts true}
bb.edn
: { ; ...
:tasks
{:requires ([babashka.cli]
[babashka.fs :as fs]
[clojure.edn :as edn]
[clojure.string :as str]
[quickblog.api :as qb]
[quickblog.cli :as cli])
:init (def opts (slurp "opts.edn"))
;; ...
}}
user.clj
: (ns user
(:require [clojure.edn :as edn]))
(comment
(def opts (-> (slurp "opts.edn") edn/read-string))
;; => #'user/opts
opts
;; => {:blog-description "A blog about stuff but also things.",
;; :blog-author "Josh Glover",
;; :num-index-posts 3,
;; :favicon-dir "favicon",
;; :posts-dir "blog/posts",
;; :assets-dir "blog/assets",
;; :templates-dir "blog/templates",
;; :favicon true,
;; :out-dir "public/blog",
;; :blog-root "https://jmglov.net/blog/",
;; :link-posts true,
;; :cache-dir ".cache",
;; :about-link "https://jmglov.net/",
;; :blog-title "jmglov's blog"}
)
(render opts)
Renders posts declared in posts.edn to out-dir.
render
must contain incantations of great power that load posts before rendering them: (defn render
"Renders posts declared in `posts.edn` to `out-dir`."
[opts]
(let [{:keys [assets-dir
assets-out-dir
cache-dir
favicon-dir
favicon-out-dir
out-dir
posts-file
templates-dir]
:as opts}
(-> opts apply-default-opts lib/refresh-cache)]
;; ...
))
user.clj
: (ns user
(:require ; ...
[quickblog.api :as qb]
[quickblog.internal :as lib]))
(defn load-opts [base-opts]
(-> base-opts
#'qb/apply-default-opts
lib/refresh-cache))
(comment
(def base-opts (-> (slurp "opts.edn") edn/read-string))
;; => #'user/base-opts
(def opts (load-opts base-opts))
;; => #'user/opts
(keys opts)
;; => (:blog-description
;; :blog-author
;; :num-index-posts
;; :favicon-dir
;; :posts-dir
;; :assets-dir
;; :modified-posts
;; :cached-posts
;; :templates-dir
;; :deleted-posts
;; :favicon
;; :modified-metadata
;; :out-dir
;; :blog-root
;; :modified-tags
;; :link-posts
;; :cache-dir
;; :about-link
;; :blog-title
;; :posts)
)
(comment
(->> opts
:posts
count)
;; => 71
)
(comment
;; => (["2022-08-26-doing-software-wrong.md"
;; {:description
;; "In which I make a bold statement, but then rather than explaining it or providing any evidence whatsoever, go on to talk about something completely different.",
;; :tags #{"waffle"},
;; :date "2022-08-26",
;; :file "2022-08-26-doing-software-wrong.md",
;; :title "We're doing software wrong",
;; :image-alt
;; "A man on a mobile phone stands in front of a wall with the word \"productivity\" written on it - Photo by Andreas Klassen on Unsplash",
;; :image "assets/2022-08-26-preview.jpg",
;; :html #<Delay@6b4fc6d6: :not-delivered>}])
)
:posts
is a map of filename to post, but no matter!(comment
(->> opts
:posts
vals
(map :date))
;; => ("2022-08-26"
;; "2022-06-22"
;; "2023-11-12"
;; "2022-07-01"
;; "2022-07-31"
;; "2022-07-09"
;; "2022-06-21"
;; ...
;; "2022-07-02")
)
user.clj
:(ns user
(:require [babashka.deps :as deps]
;; ...
))
(comment
(deps/add-deps '{:deps {tick/tick {:mvn/version "0.7.5"}}})
;; => nil
(require '[tick.core :as t])
;; => clojure.lang.ExceptionInfo: Could not resolve symbol: java.time.temporal.TemporalQuery user cljc/java_time/format/date_time_formatter.clj:33:444
)
java.time.temporal.TemporalQuery
and have a look at src/babashka/impl/classes.clj in the Babashka codebase to see when it was added $ bb --version
babashka v1.1.173
{ stdenv, ... }:
let
arch = if stdenv.isAarch64 then "aarch64" else "amd64";
osName = if stdenv.isDarwin then
"macos"
else if stdenv.isLinux then
"linux"
else
null;
sha256 = assert !isNull osName;
{
linux = {
aarch64 =
"bc7e733863486b334b8bff83ba13b416800e0ce45050153cb413906b46090d68";
amd64 =
"25975d5424e7dea9fbaef5a6551ce7d3834631b5e28bdc4caf037bf45af57dfd";
};
macos = {
# No MacOS builds for ARM at the moment
# aarch64 =
# "11c4b4bd0b534db1ecd732b03bc376f8b21bbda0d88cacb4bbe15b8469029123";
amd64 =
"792ade86e61703170f3de3082183173db66a9a98b11d01c95ace0235f0a5e345";
};
}.${osName}.${arch};
in stdenv.mkDerivation rec {
pname = "babashka";
version = "1.1.173";
filename = if osName == "macos" then
# No static builds for MacOS
"babashka-${version}-${osName}-${arch}.tar.gz"
else
"babashka-${version}-${osName}-${arch}-static.tar.gz";
src = builtins.fetchurl {
inherit sha256;
url =
"https://github.com/babashka/babashka/releases/download/v${version}/${filename}";
};
dontFixup = true;
dontUnpack = true;
installPhase = ''
mkdir -p $out/bin
cd $out/bin && tar xvzf $src
'';
}
$ curl -L https://github.com/babashka/babashka/releases/download/v1.3.188/babashka-1.3.188-linux-aarch64-static.tar.gz.sha256
417280537b20754b675b7552d560c4c2817a93fbcaa0d51e426a1bff385e3e47
$ curl -L https://github.com/babashka/babashka/releases/download/v1.3.188/babashka-1.3.188-linux-amd64-static.tar.gz.sha256
89431b0659e84a468da05ad78daf2982cbc8ea9e17f315fa2e51fecc78af7cc0
$ curl -L https://github.com/babashka/babashka/releases/download/v1.3.188/babashka-1.3.188-macos-aarch64.tar.gz.sha256
77eb9ec502260fa94008e1e43edc5678fab8dc1a5082b7eb3d28ae594ea54e09
$ curl -L https://github.com/babashka/babashka/releases/download/v1.3.188/babashka-1.3.188-macos-amd64.tar.gz.sha256
d8854833a052bb578360294d6975b85ed917b9f86da0068fb3c263f8cbcc9e15
let
# ...
sha256 = {
linux = {
aarch64 =
"417280537b20754b675b7552d560c4c2817a93fbcaa0d51e426a1bff385e3e47";
amd64 =
"89431b0659e84a468da05ad78daf2982cbc8ea9e17f315fa2e51fecc78af7cc0";
};
macos = {
aarch64 =
"77eb9ec502260fa94008e1e43edc5678fab8dc1a5082b7eb3d28ae594ea54e09";
amd64 =
"d8854833a052bb578360294d6975b85ed917b9f86da0068fb3c263f8cbcc9e15";
};
}.${osName}.${arch};
in stdenv.mkDerivation rec {
pname = "babashka";
version = "1.3.188";
# ...
}
$ sudo nixos-rebuild switch
building Nix...
building the system configuration...
these 8 derivations will be built:
/nix/store/x9c0ip7xchwzhkhznvjz5r57krcqjm3r-babashka-1.3.188.drv
/nix/store/lsq07jvqmk5kywbdrj55vh3ndjrw2vwm-home-manager-path.drv
[...]
building '/nix/store/x9c0ip7xchwzhkhznvjz5r57krcqjm3r-babashka-1.3.188.drv'...
patching sources
updateAutotoolsGnuConfigScriptsPhase
configuring
no configure script, doing nothing
building
no Makefile or custom buildPhase, doing nothing
installing
bb
[...]
activating the configuration...
setting up /etc...
reloading user units for jmglov...
setting up tmpfiles
restarting the following units: home-manager-jmglov.service
$ bb --version
babashka v1.3.188
deps.edn
so you won't have to hotload it in user.clj
: {:paths ["." "classes"]
:deps { ; ...
tick/tick {:mvn/version "0.7.5"}}}
user.clj
, then C-c C-z to hop to your REPL buffer, C-c C-q to quit it, then C-c M-j to start a new REPL, then require tick in the ns form: (ns user
(:require ; ...
[tick.core :as t]))
(comment
(t/date "2022-07-02")
;; => #time/date "2022-07-02"
)
(comment
(def opts (-> (slurp "opts.edn") edn/read-string load-opts))
;; => #'user/opts
(->> opts
:posts
vals
(map (comp t/date :date)))
;; => (#time/date "2022-08-26"
;; #time/date "2022-06-22"
;; #time/date "2023-11-12"
;; #time/date "2022-07-01"
;; ...
;; #time/date "2022-07-02")
)
(comment
(->> opts
:posts
vals
(remove #(= "FIXME" (:date %)))
(map (comp t/date :date))
(filter #(t/< % (t/date "2022-09-01")))
count)
;; => 55
)