Using Gleam Packages in Your Elixir Project

Posted on .

Using Gleam packages in an Elixir project is not as simple as adding {:lib, "1.2.3"} to your mix.exs, but it's pretty close. There are two issues preventing this simple usage:

  1. Elixir's Mix doesn't know how to compile a Gleam package, which does not have any build setup. It's literally a bunch of Erlang files in a folder. I'm not sure if there is an issue about this.
  2. Gleam writes dev dependencies into the built app.src file, which causes the app to crash on startup, as the dev dependencies aren't there. Gleam issue #3035 deals with this matter.

To circumvent these issues, add your Gleam dependency to mix.exs like this:

{:scriptorium, "~> 2.0.0", app: false, manager: :rebar3}

The first option prevents reading the app file of the dependency entirely. It seems to work for libraries, but I'm not sure of its effects on Gleam projects with startable apps. I believe they won't be automatically started at least. Most Gleam projects at this point are libraries or are explicitly started, so this is likely not an issue for you.

The second tells Mix to use Rebar to build the package. This seems to work even for a plain "Erlang files in a folder" type package that Gleam generates.

You can read more about these options in Mix's documentation.

Once you've gotten your dependency installed, remember that the calling syntax is different from a typical Elixir module. Gleam modules are named according to their path with @ as a separator. For example, to call parse_header from scriptorium/parser/common, use the following:

:scriptorium@parser@common.parse_header("...")

Also note that Gleam custom types map to tuples in Elixir, and more specifically records. In fact you can define a record on the Elixir side to match your Gleam types and operate on them. For example, here's a record from one of my early mixed Elixir/Gleam projects, GeoTherminator:

defmodule GeoTherminator.PumpAPI.Device do
  require Record

  Record.defrecord(:record, :device, [
    :id,
    :device_id,
    :is_online,
    :last_online,
    :created_when,
    :mac_address,
    :name,
    :model,
    :retailer_access
  ])

  # Create idiomatic Elixir type as an alias for Gleam side type
  @type t :: :pump_api@device.device()
end

Now we can use GeoTherminator.PumpAPI.Device.t() in typespecs, and calls like GeoTherminator.PumpAPI.Device.record(my_device, :id) and GeoTherminator.PumpAPI.Device.record(my_device, name: "My Device") to operate on the type in Elixir land.

Minimise Caddy Logging

Posted on .

I've been moving stuff to my new home server and been setting up services with Caddy. It has been a breath of fresh air after my messy Nginx configurations and Let's Encrypt setup. But I found the default logs to contain waaay more information than I needed. So here's a snippet to configure logging to stdout (for Systemd's journald) and remove some extraneous stuff that I don't need to see:

Combining Audio Tracks in a Video With Ffmpeg

Posted on .

I use OBS Studio to store replays of my games with my friends for later. OBS writes two audio tracks to the file, one for game audio and one for my mic. I was surprised to find that multi-track files were not properly supported by many programs like Handbrake (or YouTube for that matter), so I needed to combine the audio tracks.