CUE Does It All, But Can It Literate?
Source: Dev.to
CUE – The Swiss‑Army Knife of File Generation (and Literate Programming)
CUE is the tool you reach for when you need to:
- generate complex JSON,
- validate YAML,
- make sure your integers are really integers,
and, surprisingly, when you want a literate‑programming workflow.
Why a Literate‑Programming Tool?
The current “king” of literate programming is org‑mode.
It works great inside Emacs, but as soon as you try to share a document with someone who uses VS Code (or any other editor) you end up with vendor lock‑in.
You want your documentation – your literate code – to be portable, not a magic spell that only works in one editor.
When you write about code (blog post, tutorial, internal docs) you rarely dump a single gigantic file. Instead you:
- Show a snippet of the logic.
- Show a snippet of the config.
- Maybe a piece of CSS.
You stitch these disparate parts together with prose.
The “Copy‑Paste Tribute”
You write a brilliant tutorial, copy the working code into the Markdown file, then later rename a variable (
t→timeout). You fix the source but not the Markdown. Readers now copy broken code, the tutorial lies, and you look sloppy.
CUE solves this by letting us define parts and then stitch them together programmatically. It doesn’t just hold text – it validates that the pieces actually fit, guaranteeing that the code in your explanation is exactly the code used in the final build.
Think of it as a Lego set where the bricks refuse to click if the structure is unsound.¹
Stopping the Tribute: Treat the Document Like a Build Target
CUE’s tool/file creates files, while tool/exec runs commands.
We’re not merely templating strings; we’re defining a dependency graph. If any dependency (e.g., a code snippet) is invalid, the document simply doesn’t exist.
Example 1 – Generating a Markdown File with a Dynamic Version
package example
import (
"tool/file"
"tool/exec"
)
// Build command – for every entry in the `files` struct, create a file.
command: example: {
for k, v in files {
"(k)": file.Create & {
filename: k
contents: v
}
}
}
// Run a shell script to obtain a version number.
data: exec.Run & {
cmd: ["sh", "-c", "echo '1.2.3'"]
stdout: string
}
// Final document, stitched together.
// `(data.stdout)` is a promise that `data` will run before the string is finalized.
files: {
"output.md": """
My Awesome Project
Current version: (data.stdout)
"""
}
Without CUE: you’d type 1.2.3 manually. If the version later changes to 1.2.4, the file becomes stale until you edit it again.
With CUE: if data.stdout fails (e.g., the shell command crashes), the file isn’t generated, and the build fails – protecting readers from bad information.
Running the Build – cue cmd
CUE is declarative: it describes the desired state but doesn’t change it on its own. file.Create and exec.Run are just data until you invoke the task engine:
cue cmd
cue cmd is the only part of CUE allowed to have side effects. It:
- Looks for
command:blocks. - Executes the tasks sequentially.
- Bridges the gap between “I want a file” (definition) and “Here is your file” (reality).
Important: For
cue cmdto pick up your commands, the filename must end with_tool.cue(e.g.,build_tool.cue). Naming itexample.cuewill cause CUE to ignore the commands entirely.
Extending CUE – Custom Renderers with tool/exec
Simple shell commands are fine for toy examples, but real projects need more power (e.g., compiling Haskell, rendering diagrams). CUE makes it easy to wrap any CLI tool as a renderer.
Example 2 – Multi‑Language Renderers
package example
import (
"tool/exec"
)
// Renderers – wrappers around CLI tools.
// Each renderer takes a `code` string and pipes it into a command.
renderers: pikchr: exec.Run & {
cmd: ["pikchr", "--svg-only", "-"]
code: string
stdin: code
stdout: string
}
renderers: haskell: exec.Run & {
cmd: ["stack", "runghc"]
code: string
stdout: string
// Wrap the snippet in a module so it compiles standalone.
stdin: """
module Program where
(code)
"""
}
renderers: bash: exec.Run & {
cmd: ["bash", "-e"]
code: string
stdin: code
stdout: string
}
// And, because sometimes Bash isn’t hip enough…
renderers: zsh: exec.Run & {
cmd: ["zsh"]
code: string
stdin: code
stdout: string
}
What this gives you: a tiny, custom CI runner inside your CUE configuration. You can now:
- Render Pikchr diagrams to SVG.
- Compile and run Haskell snippets on the fly.
- Execute Bash or Zsh scripts safely.
Testing and CI
Now, when I want to include a diagram or a code output, I just pass my snippet through the appropriate renderer. CUE handles the heavy lifting of calling the shell, piping the input via stdin, and capturing the stdout. It turns your documentation into a living program.
The best part? You can “test” your article. By running cue cmd example, CUE actually executes the shell commands, compiles the Haskell, renders the Pikchr diagram, and generates the final files.
- If your code snippet has a syntax error, the generation fails.
- If your Haskell code doesn’t compile, the generation fails.
- If you try to create a Pikchr diagram with invalid coordinates, the generation fails.
It turns “writing a blog post” into “passing a CI/CD pipeline”.
It is a bit like having a proofreader who is also a very strict compiler 2. It ensures that every diagram in my post was actually rendered from the code right next to it—no stale images, no manual exports, and no “magic” steps I forgot to write down. It is independent of any specific editor ecosystem, effectively solving the “works on my machine” problem for documentation. You aren’t just writing; you are engineering the text.
Or if you are trying to put a Megablok on a Lego. CUE has high standards and will not hesitate to judge your poor structural choices. ↩
And unlike a human proofreader, CUE doesn’t ask for a salary, drink all your coffee, or complain about your excessive use of puns. ↩
Takeaway
- CUE lets you treat documentation as a first‑class build artifact.
- The code you show in prose is guaranteed to be the code that actually runs.
- By using
cue cmdand naming your file*_tool.cue, you get a reproducible, side‑effect‑aware pipeline. - Extending CUE with
tool/execmeans you can wrap any language or tool you need, keeping your literate‑programming workflow both portable and trustworthy.
Footnote
- The “Lego set where the bricks refuse to click if you are building something structurally unsound” is a metaphor for CUE’s validation: it won’t let you generate a file if the underlying pieces don’t satisfy the constraints.