Magnus web site
Random stuff
Comments and org-static-blog
I'm using org-static-blog to generate the contents of this site. So far I'm very happy with it, but I've gotten a few emails from readers who've wanted to comment on something I've written and they always point out that it's not easy to do. It's actually not a coincidence that it's a bit difficult!
Yesterday I came up with a way that might make is slightly easier without involving JavaScript from a 3rd party. By making use of the built-in support for adding HTML code for comments. One slight limitation is that it's a single variable holding the code, and I'd really like to allow for both
- using a link to a discussion site, e.g. reddit, as well as
- my email address
As the comment support in org-static-blog comes in the form of a single variable
this seems a bit difficult to accomplish. However, it isn't difficult at all to
do in elisp due to the power of advice-add
.
By using the following advice on org-static-blog-publish-file
(advice-add 'org-static-blog-publish-file :around (lambda (orig-fn filename &rest args) (let* ((comments-url (with-temp-buffer (insert-file-contents filename) (or (cadar (org-collect-keywords '("commentsurl"))) my-blog-default-comments-url))) (org-static-blog-post-comments (concat "Comment <a href=" comments-url ">here</a>."))) (apply orig-fn filename args))))
and defining my-blog-default-comments-url
to a mailto:...
URL I get a link
to use for commenting by either
- set
commentsurl
to point to discussion about the post on reddit, or - not set
commentsurl
at all and get themailto:...
URL.
If you look at my previous post you see the result of the former, and if you look below you see the result of the latter.
A little Haskell: epoch timestamp
A need of getting the current UNIX time is something that comes up every now and then. Just this week I needed it in order to add a k8s liveness probe1.
While it's often rather straight forward to get the Unix time as an integer in other languages2, in Haskell there's a bit of type tetris involved.
- getPOSIXTime gives me a POSIXTime, which is an alias for NominalDiffTime.
NominalDiffTime
implements RealFrac and can thus be converted to anything implementing Integral (I wanted it asInt64
).NominalDiffTime
also implements Num, so if the timestamp needs better precision than seconds it's easy to do (I needed milliseconds).
The combination of the above is something like
truncate <$> getPOSIXTime
In my case the full function of writing the timestamp to a file looks like this
writeTimestampFile :: MonadIO m => Path Abs File -> m () writeTimestampFile afn = liftIO $ do truncate @_ @Int64 . (* 1000) <$> getPOSIXTime >>= writeFile (fromAbsFile afn) . show
Footnotes:
Over the last few days I've looked into k8s probes. Since we're using Istio TCP probes are of very limited use, and as the service in question doesn't offer an HTTP API I decided to use a liveness command that checks that the contents of a file is a sufficiently recent epoch timestamp.
Rust's Chrono package has Utc.timestamp(t). Python has time.time(). Golang has Time.Unix.
Simple nix flake for Haskell development
Recently I've moved over to using flakes in my Haskell development projects. It took me a little while to arrive at a pattern a flake for Haskell development that I like. I'm hoping sharing it might help others when doing the same change
{ inputs = { nixpkgs.url = "github:nixos/nixpkgs"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: with nixpkgs.legacyPackages.${system}; let t = lib.trivial; hl = haskell.lib; name = "project-name"; project = devTools: # [1] let addBuildTools = (t.flip hl.addBuildTools) devTools; in haskellPackages.developPackage { root = lib.sourceFilesBySuffices ./. [ ".cabal" ".hs" ]; name = name; returnShellEnv = !(devTools == [ ]); # [2] modifier = (t.flip t.pipe) [ addBuildTools hl.dontHaddock hl.enableStaticLibraries hl.justStaticExecutables hl.disableLibraryProfiling hl.disableExecutableProfiling ]; }; in { packages.pkg = project [ ]; # [3] defaultPackage = self.packages.${system}.pkg; devShell = project (with haskellPackages; [ # [4] cabal-fmt cabal-install haskell-language-server hlint ]); }); }
The main issue I ran into is getting a development shell out of
haskellPackages.developPackage
, it requires returnShellEnv
to be true
.
Something that isn't too easy to find out. This means that the only solution
I've found to getting a development shell is to have separate expressions for
building and getting a shell. In the above flake the build expression, [3],
passes an empty list of development tools, the argument devTools
at [1],
while the development shell expression, [4], passes in a list of tools needed
for development only. The decision of whether the expression is for building or
for a development shell, [2], then looks at the list of development tools
passed in.
Keeping Projectile's cache tidy
A while back I added a function to find all projects recursively from a
directory and add them to Projectile's cache (see the Populating Projectile's
cache). Since then I've just made a tiny change to also include containing a
.projectile
file.
(defun projectile-extra-add-projects-in-subfolders (projects-root) (interactive (list (read-directory-name "Add to known projects: "))) (message "Searching for projects in %s..." projects-root) (let* ((proj-rx (rx (and line-start ?. (or "projectile" "git") line-end))) (dirs (seq-map 'file-name-directory (directory-files-recursively projects-root proj-rx t)))) (seq-do 'projectile-add-known-project dirs) (message "Added %d projects" (length dirs))))
Since then I've also found a need for tidying the cache, in my casse that means removing entries in the cache that no longer exist on disk. I didn't find a function for it, so I wrote one.
(defun projectile-extra-tidy-projects () (interactive) (let ((missing-dirs (seq-remove 'file-directory-p projectile-known-projects))) (seq-do 'projectile-remove-known-project missing-dirs) (message "Tidied %d projects" (length missing-dirs))))
Accessing the host from inside a Docker container
To give the container access to a service running on the the host add
extra_hosts
to its definition in the Compose file:
svc: ... extra_hosts: - "host.docker.internal:host-gateway"
Then it's possible to access it as host.docker.internal
. Just don't forget to
bind the service on the host to something else than 127.0.0.1
.