Hendrik Mans

June 15

May 27

ATOM Feeds, yay!

Crankypants now serves ATOM feeds! Here’s mine.

This was a slightly larger project than anticipated, mostly because the Crankypants code base was in need of some cleanup, and I had to take some (small) steps towards a unified interface for run-time settings first.

I couldn’t find any Crystal library for generating ATOM feeds, so I ended up creating my own ATOM module heavily inspired by ActionView’s AtomFeedHelper. Once it’s had some more time to mature, I will happily extract it into its own shard.

This is how I currently use it in Crankypants:

get "posts.atom" do
  uri = URI.parse("http://#{request.host_with_port}/")
  posts = Data.load_posts(limit: 15)

  feed = ATOM.build do |feed|
    feed.title   Crankypants.settings.site_title
    feed.link    URI.join(uri, "/").to_s
    feed.id      URI.join(uri, "/").to_s
    feed.updated posts.map { |p| p.updated_at.not_nil! }.max

    feed.author do |author|
      author.name Crankypants.settings.site_title
      author.uri  URI.join(uri, "/").to_s

    posts.each do |post|
      feed.entry do |entry|
        entry.id      URI.join(uri, post.url).to_s
        entry.title   post.title.presence || "Post from #{post.created_at}"
        entry.link    URI.join(uri, post.url).to_s
        entry.updated post.updated_at || Time.now
        entry.content post.body_html || ""

  render xml: feed

Please ignore the way I’m messing around with URI; I do want to add something soon that will make generating links more convenient, but I’m still trying to figure out the interface.

May 11

Crystal is Not Ruby, Part 1: first part of a series detailing the differences between Ruby and Crystal. The author claims that this part focuses on “the bad”, but to be honest, the points he talks about can only be considered “bad” if you’re expecting Crystal to be Ruby. Which it isn’t! Looking forward to part two.

May 4

Good old ImpactJS, which has had a commercial license so far, is now available as MIT-licensed open-source software. ImpactJS was the first HTML5 game framework I’ve ever used, but it’s probably never really made its move against the big dog, PhaserJS. If you’re into HTML5 game development, go grab a copy – it’s incredibly fun to use, and even comes with its own level editor.

Ruby on WebAssembly: compiling Ruby to mruby bytecode, then moving forward to Emscripten, LLVM, finally ending up in WebAssembly… it’s crazy, but apparently it works!

Includes a really nice high-level introduction to lovely mruby, a cool little piece of technology that I would have liked to see better adoption of.

Earl: Crystal service objects (Agents) on top of Crystal’s fibers. Need to take a closer look at this eventually.

May 3

Progress Report of Ruby 3 Concurrency, including a fun “proposal of new concurrency model for Ruby 3”. I like the many references to Elixir/Erlang, and I’m particularly fond of the slide that states:

We need to design a library like OTP.


Electron 2.0! Say about it what you want, but Electron is making writing desktop software easier, and that’s – all in all – A Good Thing.

April 28

Oh boy oh boy oh boy. As you can see, I'm deep in code hacking mode right now, which means I don't write much prose. But I'm so looking forward to writing more about this thing that I'm building here. Soon!

April 24

Even more speed. 🚀

2018-04-24 18:25:53 +00:00 200 GET / 421.0µs
2018-04-24 18:25:53 +00:00 200 GET /blog.css 177.0µs
2018-04-24 18:25:53 +00:00 200 GET /blog-bundle.js 233.0µs

That / page is getting slow... ;-)

April 23

April 21

Posting from mobile. It's nice!

April 19

Oh, and I've added some minor responsive touches to the blog design. I like looking at this thing. Mmm.

Reduced the size of the bundle served by the public-facing blog from a ridiculously broken 560 KB to a mere 22 KB with a few tricks.

First of all, I've replaced highlight.js with prism.js, which gave me better control over which languages to load (loading all of them really blew up the bundle size, so now I'm picking which ones to support.)

But also, I've finally bothered to serve gzipped assets, which is a bit more work than usual when you statically compile assets into your executable like a madman. I'm using baked_file_system to bake the contents of my Webpack-controlled public/ directory into Crankypants, and I've written this handy macro to serve assets gzipped (or not) depending on the request's headers:

macro serve_static_asset(name)
  if env.request.headers["Accept-Encoding"] =~ /gzip/
    env.response.headers.add "Content-Encoding", "gzip"
    Assets.get("{{ name.id }}.gz").gets_to_end
    Assets.get("{{ name.id }}").gets_to_end

I can use it from a Kemal action like this:

get "/blog-bundle.js" do |env|
  env.response.headers.add "Cache-Control", "max-age=600, public"
  env.response.content_type = "text/javascript"
  serve_static_asset "blog-bundle.js"

Good times! Especially response times. Haha!

April 18

April 17

Good night everybody!

I've tweeted.

(No, I can't embed tweets... yet. But still, I have tweeted. There you go.)

This is happening in my server log right now (yup, those are microseconds):

2018-04-17 16:41:58 +00:00 200 GET /posts/3 200.0µs
2018-04-17 16:41:59 +00:00 200 GET / 245.0µs
2018-04-17 16:42:00 +00:00 200 GET /posts/4 216.0µs
2018-04-17 16:42:01 +00:00 200 GET / 242.0µs
2018-04-17 16:42:16 +00:00 200 GET / 287.0µs
2018-04-17 16:42:22 +00:00 200 GET /posts/3 200.0µs
2018-04-17 16:42:27 +00:00 200 GET / 282.0µs
2018-04-17 16:43:30 +00:00 200 GET / 276.0µs
2018-04-17 16:44:50 +00:00 200 GET / 289.0µs
2018-04-17 16:44:51 +00:00 200 GET /posts/4 217.0µs
2018-04-17 16:46:54 +00:00 200 GET / 312.0µs
2018-04-17 16:48:02 +00:00 200 GET / 241.0µs
2018-04-17 16:48:03 +00:00 200 GET / 280.0µs

Note that we're not talking about static documents here -- every of these requests loads one or more posts from a database and renders them to HTML.

Let's embed some CoffeeScript code for fun and profit!

console.log "hi from blog!"

import "normalize.css"
import "highlightjs/styles/kimbie.light.css"
import "./blog.scss"

# Initialize Turbolinks
import Turbolinks from "turbolinks"

# hightlight.js
import hljs from "highlightjs"

# This function will be called every time a page is loaded.
initPage = ->
  hljs.initHighlighting.called = false

document.addEventListener "turbolinks:load", initPage

Just in case anyone's wondering, this is a new version of Pants. It's called Crankypants and it's... quite good.

Okay, so let's do this, okay?

April 16

Hello world

This is the first post.