Gleam-inspired typed configuration language (POC)
1{
2 description = "Glint — a Gleam-inspired typed configuration language (POC)";
3
4 inputs = {
5 nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6 systems.url = "github:nix-systems/default";
7
8 # Wasm-capable Gleam (Zed extension export). Override locally with:
9 # nix build --override-input gleam path:../gleam
10 gleam = {
11 url = "git+https://tangled.org/nandi.uk/gleam?ref=wasm";
12 inputs.nixpkgs.follows = "nixpkgs";
13 };
14 };
15
16 outputs =
17 {
18 self,
19 nixpkgs,
20 systems,
21 gleam,
22 ...
23 }:
24 let
25 forEachSystem = nixpkgs.lib.genAttrs (import systems);
26 in
27 {
28 packages = forEachSystem (
29 system:
30 let
31 pkgs = nixpkgs.legacyPackages.${system};
32 inherit (pkgs) lib;
33
34 gleamToml = lib.importTOML ./gleam.toml;
35 manifestToml = lib.importTOML ./manifest.toml;
36
37 hexPackages = builtins.filter (p: p.source == "hex") manifestToml.packages;
38
39 packagesTOML = lib.concatStringsSep "\n" (
40 [ "[packages]" ] ++ map (p: "${p.name} = \"${p.version}\"") hexPackages
41 );
42
43 # Wasm-capable gleam from the flake input.
44 gleamBin = gleam.packages.${system}.gleam;
45
46 # Self-contained store package: Erlang shipment + fixed-path wrapper.
47 glint = pkgs.stdenv.mkDerivation {
48 pname = "glint";
49 version = gleamToml.version;
50
51 src = lib.cleanSourceWith {
52 src = ./.;
53 filter =
54 path: type:
55 let
56 base = baseNameOf path;
57 in
58 lib.cleanSourceFilter path type
59 && base != "build"
60 && base != "result"
61 && base != "result-zed"
62 && base != "erl_crash.dump"
63 && base != "editors"
64 && !(lib.hasSuffix ".escript" base);
65 };
66
67 nativeBuildInputs = [
68 pkgs.gleam
69 pkgs.beamPackages.erlang
70 pkgs.rebar3
71 pkgs.beamPackages.hex
72 pkgs.rsync
73 ];
74
75 configurePhase = ''
76 runHook preConfigure
77
78 mkdir -p build/packages
79 cat <<EOF > build/packages/packages.toml
80 ${packagesTOML}
81 EOF
82
83 # Vendor Hex deps so the pure Nix build never hits the network.
84 ${lib.concatMapStringsSep "\n" (p: ''
85 rsync --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r -r ${
86 pkgs.fetchHex {
87 pkg = p.name;
88 version = p.version;
89 sha256 = p.outer_checksum;
90 }
91 }/* build/packages/${p.name}/
92 '') hexPackages}
93
94 runHook postConfigure
95 '';
96
97 buildPhase = ''
98 runHook preBuild
99 export REBAR_CACHE_DIR="$TMP/.rebar-cache"
100 gleam export erlang-shipment
101 runHook postBuild
102 '';
103
104 installPhase = ''
105 runHook preInstall
106
107 mkdir -p $out/{bin,lib/glint}
108 rsync --exclude=entrypoint.sh --exclude=entrypoint.ps1 \
109 -r build/erlang-shipment/* $out/lib/glint/
110
111 # Bake -pa paths at install time (each ebin needs its own -pa).
112 ebin_args=()
113 for d in $out/lib/glint/*/ebin; do
114 ebin_args+=(-pa "$d")
115 done
116
117 {
118 echo "#!${pkgs.runtimeShell}"
119 echo "set -eu"
120 echo "exec ${pkgs.beamPackages.erlang}/bin/erl ''${ebin_args[*]} -eval \"glint@@main:run(glint)\" -noshell -extra \"\$@\""
121 } > $out/bin/glint
122 chmod +x $out/bin/glint
123
124 runHook postInstall
125 '';
126
127 meta = {
128 description = gleamToml.description;
129 mainProgram = "glint";
130 license = lib.licenses.asl20;
131 };
132 };
133
134 # Prebuilt Glint Zed extension (Gleam guest). Point Install Dev
135 # Extension at a *writable copy* of this dir, or use `nix run .#install-zed-extension`.
136 zed-extension = pkgs.stdenv.mkDerivation {
137 pname = "glint-zed-extension";
138 version = gleamToml.version;
139
140 src = lib.cleanSourceWith {
141 src = ./editors/zed;
142 filter =
143 path: type:
144 let
145 base = baseNameOf path;
146 in
147 lib.cleanSourceFilter path type
148 && base != "target"
149 && base != "build"
150 && base != "grammars"
151 && !(lib.hasSuffix ".wasm" base)
152 && base != "Cargo.lock";
153 };
154
155 nativeBuildInputs = [
156 gleamBin
157 ];
158
159 buildPhase = ''
160 runHook preBuild
161 export HOME=$TMPDIR
162 # `gleam export zed-extension` writes extension.wasm in the package root.
163 gleam export zed-extension
164 runHook postBuild
165 '';
166
167 installPhase = ''
168 runHook preInstall
169 mkdir -p $out
170 cp extension.wasm $out/
171 cp -r languages $out/
172
173 # No [lib] section: Install Dev Extension must not run cargo.
174 # Prebuilt extension.wasm is the Gleam guest.
175 cat > $out/extension.toml <<'EOF'
176 id = "glint"
177 name = "Glint"
178 version = "0.1.0"
179 schema_version = 1
180 authors = ["nandi"]
181 description = "Glint language support — highlighting and glint LSP (Gleam Wasm guest)"
182 repository = "https://tangled.org/nandi.uk/glint"
183
184 [language_servers.glint]
185 name = "Glint LSP"
186 languages = ["Glint"]
187
188 [language_servers.glint.language_ids]
189 Glint = "glint"
190
191 [grammars.gleam]
192 repository = "https://github.com/gleam-lang/tree-sitter-gleam"
193 rev = "6ea757f7eb8d391dbf24dbb9461990757946dd5e"
194 EOF
195
196 cat > $out/README.txt <<EOF
197 Glint Zed extension (Gleam Wasm guest)
198 Built with: ${gleamBin}
199
200 Install:
201 nix run .#install-zed-extension -- ~/glint-zed-dev
202 # then Zed → Extensions → Install Dev Extension → ~/glint-zed-dev
203 EOF
204 runHook postInstall
205 '';
206
207 meta = {
208 description = "Prebuilt Glint Zed extension (extension.wasm from Gleam)";
209 license = lib.licenses.asl20;
210 };
211 };
212 in
213 {
214 default = glint;
215 inherit glint;
216 inherit gleamBin;
217 gleam = gleamBin;
218 inherit zed-extension;
219 }
220 );
221
222 apps = forEachSystem (
223 system:
224 let
225 pkgs = nixpkgs.legacyPackages.${system};
226 zed = self.packages.${system}.zed-extension;
227 installScript = pkgs.writeShellApplication {
228 name = "install-zed-extension";
229 text = ''
230 set -euo pipefail
231 dest="''${1:-$HOME/glint-zed-dev}"
232 echo "Installing Glint Zed extension → $dest"
233 mkdir -p "$dest"
234 # Store path is immutable; copy so Zed can re-scan / write if needed.
235 cp -a ${zed}/. "$dest/"
236 chmod -R u+w "$dest"
237 echo "Done. In Zed: Extensions → Install Dev Extension → $dest"
238 echo " (prebuilt extension.wasm; no cargo rebuild required if Zed accepts the tree)"
239 '';
240 };
241 in
242 {
243 default = {
244 type = "app";
245 program = "${self.packages.${system}.glint}/bin/glint";
246 };
247 install-zed-extension = {
248 type = "app";
249 program = "${installScript}/bin/install-zed-extension";
250 };
251 }
252 );
253
254 devShells = forEachSystem (
255 system:
256 let
257 pkgs = nixpkgs.legacyPackages.${system};
258 gleamBin = self.packages.${system}.gleam;
259 in
260 {
261 default = pkgs.mkShell {
262 packages = with pkgs; [
263 # Stock gleam for normal package work, plus wasm-capable gleam as GLEAM.
264 gleam
265 gleamBin
266 beamPackages.erlang
267 rebar3
268 nodejs
269 rustc
270 cargo
271 ];
272 GLEAM = "${gleamBin}/bin/gleam";
273 shellHook = ''
274 echo "glint dev shell"
275 echo " GLEAM=$GLEAM (wasm-capable, for editors/zed cargo driver)"
276 echo " gleam run -- check examples/hello.glint"
277 echo " nix build .#zed-extension"
278 echo " nix run .#install-zed-extension"
279 '';
280 };
281 }
282 );
283
284 checks = forEachSystem (system: {
285 package = self.packages.${system}.glint;
286 # zed-extension is heavier (builds gleam); enable when CI can afford it.
287 });
288
289 formatter = forEachSystem (system: nixpkgs.legacyPackages.${system}.nixfmt-rfc-style);
290 };
291}