feat: add blu-site static site generator and fix language issues
Build a complete static site generator in Lux that faithfully clones
blu.cx (elmstatic). Generates 14 post pages, section indexes, tag pages,
and a home page with snippets grid from markdown content.
Language fixes discovered during development:
- Add \{ and \} escape sequences in string literals (lexer)
- Register String.indexOf and String.lastIndexOf in type checker
- Fix formatter to preserve brace escapes in string literals
- Improve LSP hover to show documentation for let bindings and functions
ISSUES.md documents 15 Lux language limitations found during the project.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
---
|
||||
title: Stack Programs Like Legos with Nix!
|
||||
description: Learn how to stitch programs together reprodicibly with Nix
|
||||
date: 2025-11-21
|
||||
tags: nix software open-source
|
||||
---
|
||||
|
||||
Nix was made to solve the _software deployment problem_, concisely defined by creator Eelco Dolstra thus:
|
||||
|
||||
> [The software deployment problem] is about getting computer programs from one machine to another—and having
|
||||
> them still work when they get there.
|
||||
>
|
||||
> The Purely Functional Software Deployment Model, Eelco Dolstra
|
||||
|
||||
Nix allows you to setup software on your computer in such a way that your setup is _reproducible_, meaning your setup on machine A can be _exactly_ the same as your setup on machine B -- as long as you have Nix.
|
||||
|
||||
To most people, learning Nix is a pain, due to the new concepts, sparse and outdated documentation, and community infighting.
|
||||
|
||||
But I think Nix can make using computers _fun_ and _powerful_ and _less painful_, once you learn how to handle its' edges.
|
||||
|
||||
One fun way we can use Nix is to stitch together programs like Legos and have them interact with each other in a reproducible way. Let's create an example!
|
||||
|
||||
First [install Nix](https://nixos.org/download/) if you haven't.
|
||||
|
||||
Say we have a fun little `bash` script like this:
|
||||
|
||||
```sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# filename: pokefortune.sh
|
||||
|
||||
message="${1:-}"
|
||||
pokemon="${2:-slowking}"
|
||||
|
||||
# Silence Perl locale warnings.
|
||||
export LC_ALL=C
|
||||
|
||||
# Generate a fortune if user did not pass a message.
|
||||
if [[ -z "$message" ]]; then
|
||||
message=$(fortune)
|
||||
fi
|
||||
|
||||
echo "$message" | pokemonsay -p "$pokemon" -n
|
||||
```
|
||||
|
||||
It optionally takes in a message and a [Pokédex](https://pokemondb.net/pokedex/all) number, and prints out that Pokémon and message using the `pokemonsay` program. If either the message or the Pokemon aren't specified, it uses a default Pokemon and the program `fortune` to generate the message.
|
||||
|
||||
Save this in a file `pokefortune.sh`, make it executable, then run it:
|
||||
|
||||
```sh
|
||||
# Make the bash script executable
|
||||
chmod +x pokefortune.sh
|
||||
|
||||
./pokefortune.sh
|
||||
```
|
||||
|
||||
If you're like most people, you probably don't have `pokemonsay` or `fortune` installed on your system, so you'll likely see something like this:
|
||||
|
||||
```sh
|
||||
./pokefortune.sh: line 6: fortune: command not found
|
||||
./pokefortune.sh: line 7: pokemonsay: command not found
|
||||
```
|
||||
|
||||
Therefore this script, which works on my system because I have these programs installed, isn't _reproducible_ on your system. Let's create a Nix _derivation_ to make it so. Create a file called `pokefortune.nix` and copy the following:
|
||||
|
||||
```nix
|
||||
# filename: pokefortune.nix
|
||||
|
||||
{
|
||||
pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz") { },
|
||||
}:
|
||||
pkgs.writeShellScriptBin "pokefortune" ''
|
||||
|
||||
message="''${1:-}"
|
||||
pokemon="''${2:-slowking}"
|
||||
|
||||
# Silence Perl locale warnings.
|
||||
export LC_ALL=C
|
||||
|
||||
# Generate a fortune if user did not pass a message.
|
||||
if [[ -z "$message" ]]; then
|
||||
message=''$(${pkgs.fortune}/bin/fortune)
|
||||
fi
|
||||
|
||||
echo $message | ${pkgs.pokemonsay}/bin/pokemonsay -p "$pokemon" -n
|
||||
''
|
||||
```
|
||||
|
||||
Now run:
|
||||
|
||||
```sh
|
||||
nix-build pokefortune.nix
|
||||
```
|
||||
|
||||
This may take awhile, especially if this is your first time running Nix. Let's look at the output:
|
||||
|
||||
```sh
|
||||
nix-build src/scripts/nix/pokefortune.nix
|
||||
unpacking 'https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz' into the Git cache...
|
||||
this derivation will be built:
|
||||
/nix/store/vg4zmghqcnhjbs8kqhx04xixvm36d3ik-pokefortune.drv
|
||||
these 9 paths will be fetched (2.65 MiB download, 26.91 MiB unpacked):
|
||||
/nix/store/0vdf5mpd762bw53rgl5nkmhvzq8n4m0d-file-5.45
|
||||
/nix/store/77cdqhqprqbciyhzsnzmsk7azbk2xv6r-fortune-mod-3.20.0
|
||||
/nix/store/d0i8idmbb4jji9ml01xsqgykrbvm7dss-gnu-config-2024-01-01
|
||||
/nix/store/0554jm1l1qw1pcfqsliw91hnifn11w8m-gnumake-4.4.1
|
||||
/nix/store/2wp235bg03gykpixd9v2nyxp08w8xq8a-patchelf-0.15.0
|
||||
/nix/store/c5x32idp600dklz9n25q38lk78j5vwxb-pokemonsay-1.0.0
|
||||
/nix/store/pba53n11na87fs4c20mp8yg4j7qx1by2-recode-3.7.14
|
||||
/nix/store/zix67r268ihi4c362zw7c0989z12jmy7-stdenv-linux
|
||||
/nix/store/2329271b42wh6b6yhl7jmjyi0cs4428b-update-autotools-gnu-config-scripts-hook
|
||||
copying path '/nix/store/d0i8idmbb4jji9ml01xsqgykrbvm7dss-gnu-config-2024-01-01' from 'https://cache.nixos.org'...
|
||||
copying path '/nix/store/c5x32idp600dklz9n25q38lk78j5vwxb-pokemonsay-1.0.0' from 'https://cache.nixos.org'...
|
||||
copying path '/nix/store/0vdf5mpd762bw53rgl5nkmhvzq8n4m0d-file-5.45' from 'https://cache.nixos.org'...
|
||||
copying path '/nix/store/0554jm1l1qw1pcfqsliw91hnifn11w8m-gnumake-4.4.1' from 'https://cache.nixos.org'...
|
||||
copying path '/nix/store/2wp235bg03gykpixd9v2nyxp08w8xq8a-patchelf-0.15.0' from 'https://cache.nixos.org'...
|
||||
copying path '/nix/store/pba53n11na87fs4c20mp8yg4j7qx1by2-recode-3.7.14' from 'https://cache.nixos.org'...
|
||||
copying path '/nix/store/2329271b42wh6b6yhl7jmjyi0cs4428b-update-autotools-gnu-config-scripts-hook' from 'https://cache.nixos.org'...
|
||||
copying path '/nix/store/zix67r268ihi4c362zw7c0989z12jmy7-stdenv-linux' from 'https://cache.nixos.org'...
|
||||
copying path '/nix/store/77cdqhqprqbciyhzsnzmsk7azbk2xv6r-fortune-mod-3.20.0' from 'https://cache.nixos.org'...
|
||||
building '/nix/store/vg4zmghqcnhjbs8kqhx04xixvm36d3ik-pokefortune.drv'...
|
||||
/nix/store/vsv2spw517cwq791fl3f8iymm6hshhyq-pokefortune
|
||||
```
|
||||
|
||||
Nix fetched the packages we specified in our `.nix` file: `pokemonsay`, `fortune`, and some we didn't specify: such as `file`, `patchelf`, and `recode`, which one or both of the other two packages depends on. Then it copied them locally, built a binary, and placed it at `/nix/store/vsv2spw517cwq791fl3f8iymm6hshhyq-pokefortune/bin`. We can confirm this by running it:
|
||||
|
||||
```sh
|
||||
/nix/store/vsv2spw517cwq791fl3f8iymm6hshhyq-pokefortune/bin
|
||||
```
|
||||
|
||||
You should see something like this:
|
||||
|
||||
<img src="/images/blog/nix-programs-as-legos/img_2_pokefortune.webp" alt="pokefortune result" width="800" height="1240" loading="lazy">
|
||||
|
||||
To be reproducible, Nix ensures that it knows about every single dependency needed to create a package at all times. Because of that, you can do fun things like this:
|
||||
|
||||
```sh
|
||||
# Quickly enter a shell with the dependencies to create and display images.
|
||||
nix-shell -p graphviz chafa
|
||||
|
||||
# Query the derivation's dependency graph, create a .png from it and display it:
|
||||
nix-store --query --graph $(nix-build pokefortune.nix) | \
|
||||
dot -Tpng -o pokefortune-dependency-graph.png && chafa pokefortune-dependency-graph.png
|
||||
```
|
||||
|
||||
Resulting in a view of every single dependency (the "closure") that our `pokefortune` program requires:
|
||||
|
||||
<!-- <div style="text-align: center;"> -->
|
||||
|
||||
<img src="/images/blog/nix-programs-as-legos/img_3_pokefortune_dependency_graph.webp" alt="pokefortune dependency graph" width="800" height="634" loading="lazy">
|
||||
|
||||
<!-- <img src="assets/introduction/intro_img_3_pokefortune_dependency_graph.png" alt="pokefortune result" height="600"> -->
|
||||
<!-- </div> -->
|
||||
|
||||
How cool is that! We can see the whole dependency tree for anything packaged with Nix!
|
||||
|
||||
You have just done something very powerful with Nix: You've created a reproducible derivation that anyone who has the package manager installed on their system can use, which they couldn't before.
|
||||
|
||||
This is just a taste of what Nix can do, but I hope that the potential is clear. As Farid Zakaria says in his blog post "Learn Nix the Fun Way":
|
||||
|
||||
> Hopefully, seeing the fun things you can do with Nix might inspire you to push through the hard parts.
|
||||
>
|
||||
> There is a golden pot 💰 at the end of this rainbow 🌈 awaiting you.
|
||||
>
|
||||
> - <https://fzakaria.com/2024/07/05/learn-nix-the-fun-way>
|
||||
|
||||
---
|
||||
|
||||
_This article is directly inspired by Farid Zakaria's blog post [**Learn Nix the Fun Way**](https://fzakaria.com/2024/07/05/learn-nix-the-fun-way). Check out his excellent blog [here](https://fzakaria.com/)._
|
||||
|
||||
_Special thanks to [Russell Weas](https://github.com/russweas) and [veracius](https://veracius.dev) for their input to this article and code_
|
||||
|
||||
---
|
||||
|
||||
References:
|
||||
|
||||
- <https://edolstra.github.io/pubs/phd-thesis.pdf>
|
||||
- <https://web.archive.org/web/20171017151526/http://aptitude.alioth.debian.org/doc/en/pr01s02.html>
|
||||
- <https://fzakaria.com/2024/07/05/learn-nix-the-fun-way>
|
||||
- <https://zero-to-nix.com/start/nix-run/>
|
||||
Reference in New Issue
Block a user