Cue 无所不能,但能识字吗?
Cue Does It All, but Can It Literate?

原始链接: https://xlii.space/cue/cue-does-it-all-but-can-it-literate/

## CUE:超越配置 – 一种强大的文学编程工具 CUE 传统上是一个用于生成可靠文件(JSON、YAML 等)的工具,但令人惊讶的是,它在文学编程方面表现出色——这是一种将代码和文档交织在一起的方法。虽然 org-mode 很受欢迎,但它将用户锁定在 Emacs 生态系统中。CUE 提供了可移植性,允许在 VS Code 等编辑器之间共享文档,而不会出现兼容性问题。 CUE 解决的核心问题是“复制粘贴税”——文档中的代码片段与实际源代码之间不可避免的差异。CUE 将文档视为构建目标,定义代码“部分”和散文之间的依赖关系。它*验证*这些连接,确保文档中的代码始终准确且可执行。 使用 `cue cmd`,CUE 在生成最终文档之前执行文件中定义的任务,例如运行 shell 命令或编译代码。这确保了动态内容(如版本号)始终是最新的。CUE 的可扩展性允许通过 `tool/exec` 与各种语言集成,有效地在文档过程中创建自定义 CI/CD 管道。 最终,CUE 将编写文档转变为可测试、可重现的过程,消除了过时的示例并确保了准确性——这是超越传统写作工作流程的重要一步。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 Cue 无所不能,但能识字吗? (xlii.space) 7 分,来自 xlii 2 小时前 | 隐藏 | 过去 | 收藏 | 讨论 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

CUE is the Swiss Army knife of file generation. It is the tool you grab when you need to generate complex JSON, validate YAML, or generally stop configuration files from ruining your life. It slices, it dices, it ensures your integers are actually integers. But guess what else it can be? It turns out, it is also a surprisingly effective Literate Programming tool.

This is important because, let’s be honest, the current king of this hill is org-mode. And while org-mode is powerful, it is also a bit of a golden cage. It works perfectly as long as you never leave the Emacs ecosystem. But the moment you try to export a workflow to a colleague who uses VS Code, you realize you have accidentally signed up for vendor lock-in. You want your documentation - your “literate code” - to be portable, not a magic spell that only works inside one specific editor.

The “Frankenstein” Problem

When you write about code - whether it is a blog post, a tutorial, or internal docs - you rarely show one giant, boring file. That puts people to sleep. Instead, you break it down. You show a snippet of the logic. Then a snippet of the config. Then maybe a piece of the CSS. You stitch these disparate body parts together with prose.

The problem is what I call the “Copy-Paste Tribute”.

Let’s say you write a brilliant tutorial. You copy your working code into the Markdown file. Then, three days later, you realize you named a variable t instead of timeout. You fix it in the source code. But did you fix it in the Markdown file? Probably not. Now your readers are copying broken code, your tutorial is lying to them, and you look like you don’t know what you are doing.

CUE solves this by letting us define “parts” and then stitching them together programmatically. It does not just hold the text; it validates that the pieces actually fit. It ensures that the code in your explanation is the exact same code in your final build. It is like having a Lego set where the bricks refuse to click if you are building something structurally unsound .

The Basic Engine

So, how do we stop paying this tribute? We treat our document like a build target.

We can use CUE’s tool/file to create the document and tool/exec to run commands. The beauty here is that we are not just templating strings; we are defining a dependency graph. If the dependencies (the code snippets) aren’t valid, the document doesn’t exist.

package example

import (
  "tool/file"
  "tool/exec"
)

// This is our "build" command. It tells CUE:
// "For every file defined in the 'files' struct, please create it"
command: example: {
  for k, v in files {
    "(k)": file.Create & {
      filename: k
      contents: v
    }
  }
}

// Imagine this is calling a script to get a version number.
// We aren't guessing the version; we are asking the shell for it.
data: exec.Run & {
  cmd: ["sh", "-c", "echo '1.2.3'"]
  stdout: string
}

// Our final document, stitched together.
// The '(data.stdout)' isn't just a placeholder; it's a promise
// that 'data' will run before this string is finalized.
files: {
  "output.md": """
My Awesome Project

Current version: (data.stdout)
"""
}

In a normal editor, that version number 1.2.3 would just be static text I typed in while caffeinated. If the underlying logic changed to 1.2.4, the document would lie to the reader until I manually updated it. With CUE, if data.stdout fails to populate - say, if the shell command crashes - the file simply doesn’t generate. The build breaks, protecting the user from bad info.

The Ignition Key: cue cmd

There is a catch, though. If you just stare at the code above or run a standard cue eval, nothing happens. CUE is declarative by nature; it describes the state of the world, it doesn’t inherently change it. Your file.Create and exec.Run blocks are just data sitting there, looking pretty.

To actually turn that potential energy into kinetic energy, you need cue cmd.

cue cmd is the built-in task engine. It is the part of CUE that is allowed to have side effects. It looks for those command: blocks we defined earlier and executes the tasks inside them sequentially. It is the crucial bridge between “I want a file” (the definition) and “Here is your file” (the reality). Without invoking cue cmd, your literate programming setup is just a very fancy, inert config file.

Crucial Detail: There is a trap for the uninitiated here. For cue cmd to actually pick up your file, the filename must end with _tool.cue (e.g., build_tool.cue). If you name it just example.cue, CUE will ignore your commands completely, and you will be left wondering why your genius code is doing absolutely nothing.

The Polyglot Pipeline

Once you have the engine running, you might realize that simple shell commands aren’t enough. echo '1.2.3' is fine for toddlers, but what if you need to compile a Haskell module or render a diagram?

The real magic happens when you realize CUE is incredibly easy to extend. It isn’t limited to its own syntax. You can define your own “renderers” using tool/exec, essentially building a tiny, custom CI runner inside your config file.

Here is how you can teach CUE to speak multiple languages by wrapping standard CLI tools. Note the trick with the Haskell renderer: we aren’t just passing raw code; we are wrapping it in a module structure so it compiles standalone.

package example

import (
  "tool/exec"
)

// Define our "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 & {
  code:   string
  stdout: string
  cmd: ["stack", "runghc"]
  // Here we wrap the snippet in a module so it compiles standalone
  stdin: """
module Program where
(code)
"""
}

renderers: bash: exec.Run & {
  code:   string
  stdout: string
  cmd: ["bash", "-e"]
  stdin: code
}

// Just for good measure, ZSH too.
// Why? Because sometimes Bash isn't hipster enough.
renderers: zsh: exec.Run & {
  code:   string
  stdout: string
  cmd: ["zsh", "-e"]
  stdin: code
}

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.

Testing the Narrative

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 . 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.

联系我们 contact @ memedata.com