Community编程与开发github.com

pwquint24/org-babel-literate-tangle

使用 Org Babel 进行文学编程、代码提取(tangling)、Noweb 宏展开、批量编译和配置管理的指南。

org-babel-literate-tangle 是什么?

该技能详细介绍了如何使用 Emacs Org Babel 实现 Donald Knuth 提出的文学编程范式。它指导用户通过 Org 文件同时生成面向人类的文档(编织)和面向机器的可执行代码文件(缠绕)。技能重点讲解了 `:tangle` 系列头参数以控制代码块的提取路径和权限,并通过 Noweb 语法实现模块化代码组装,允许将分散的代码片段组合成完整脚本。还涵盖了子树和缓冲级别的缠绕作用域、缩进处理(对 Python 等敏感语言至关重要)、表格变量内联、无头命令行批处理以及 Emacs 配置管理的 `init.org` 模式。该技能适用于所有支持 Emacs Org Babel 的开发环境,如本地 Emacs、CI 流水线和构建系统。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/pwquint24/org-babel-agent-skills/tree/HEAD/skills/org-babel-literate-tangle

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

使用 Org Babel 进行文学编程与代码缠绕(Tangling)

Org Babel 是 Donald Knuth 的文学编程范式的一种实现。它不是编写带有零散注释的代码,而是编写面向人类叙述的散文、架构笔记和设计原理,并将功能性代码块嵌入其中。

一个单一的 Org 文件作为两个不同输出的最终真相来源:

  1. 编织(面向人类文档): 将笔记本导出为格式化的 HTML、LaTeX/PDF 或 Markdown,供读者阅读。
  2. 缠绕(面向机器执行): 提取纯净、可运行的源代码文件(.py、.sh、.c、.conf、.el),用于编译器、解释器和部署系统。

1. 缠绕基础::tangle 头参数

默认情况下,代码块不会被缠绕。:tangle 系列头参数指定代码块提取到磁盘的位置和方式。

1.1 :tangle 参数的值

值行为
:tangle no(默认)块不会写入任何文件。(但仍可通过 <<noweb>> 包含在其他块内部)。
:tangle "path/to/file.py"将块主体直接写入指定的文件路径(相对于缓冲区目录或绝对路径)。
:tangle yes缠绕到与 Org 文件基本名匹配且带有语言标准扩展名的文件(例如,model.org -> model.py)。

1.2 其他缠绕头参数

  • :shebang "#!/usr/bin/env python3": 将指定的 shebang 字符串作为缠绕文件的第一行前置。
  • :tangle-mode (identity #o755): 设置生成文件上的文件系统权限(例如,#o755 使脚本可执行)。
  • :no-expand: 阻止变量赋值(:var)内联到缠绕代码中;仅写入原始块主体。
  • :mkdirp yes: 自动创建缠绕路径中任何不存在的父目录。

2. 缠绕作用域:块级、子树级和缓冲区级

缠绕参数可以在整个项目中清晰地设置作用域:

2.1 子树级缠绕(推荐模式)

不同的标题可以缠绕到不同的文件或模块:

* 核心工具
:PROPERTIES:
:header-args:python: :tangle src/utils.py :mkdirp yes
:END:

#+BEGIN_SRC python
  def slugify(text: str) -> str:
      return "-".join(text.lower().split())
#+END_SRC

* 数据处理流水线
:PROPERTIES:
:header-args:python: :tangle src/pipeline.py :mkdirp yes
:END:

#+BEGIN_SRC python
  from utils import slugify

  def process_record(rec: dict) -> dict:
      rec["slug"] = slugify(rec["name"])
      return rec
#+END_SRC

2.2 缓冲区级缠绕默认值

在文件顶部设置:

#+PROPERTY: header-args:python :tangle src/main.py

3. Noweb 语法:模块化代码组装

Noweb 使您能够从更小、模块化、易于解释的片段中组装更大的源文件。您可以在另一个代码块中使用 <<block_name>> 引用一个代码块。

3.1 启用 Noweb 展开

Noweb 默认是禁用的。使用 :noweb 头参数控制它:

:noweb 值在 Emacs 中求值?缠绕到磁盘?在文档中导出?
no(默认)否否否
yes是是是
tangle否是否
strip-tangle是否(引用被剥离)是
no-export是是否
eval是否否

3.2 Noweb 示例:文学化脚本构建

#+NAME: imports
#+BEGIN_SRC python :exports none
  import sys
  import argparse
  from pathlib import Path
#+END_SRC

#+NAME: argument-parser
#+BEGIN_SRC python :exports code
  parser = argparse.ArgumentParser(description="处理数据集。")
  parser.add_argument("--input", required=True, type=Path, help="输入 CSV 路径")
  parser.add_argument("--threshold", default=0.5, type=float, help="分数阈值")
  args = parser.parse_args()
#+END_SRC

#+NAME: process-data
#+BEGIN_SRC python :exports code
  print(f"从 {args.input} 加载数据...")
  # 此处为处理逻辑
#+END_SRC

# 组装最终的可执行脚本:
#+NAME: main-cli
#+BEGIN_SRC python :tangle bin/run_pipeline.py :shebang "#!/usr/bin/env python3" :tangle-mode (identity #o755) :noweb yes :exports none
  <<imports>>

  def main():
      <<argument-parser>>
      <<process-data>>

  if __name__ == "__main__":
      main()
#+END_SRC

当缠绕时,bin/run_pipeline.py 被写成一个干净、可执行的脚本,所有片段都正确放置并缩进!

3.3 Noweb 前缀与缩进

  • Noweb 尊重缩进:如果 <<argument-parser>> 在 def main(): 内部缩进了 4 个空格,则展开块的每一行都将缩进 4 个空格。
  • 要禁用前缀行为::noweb-prefix no。
  • 要插入块执行的结果而不是其源代码,请附加括号: <<compute_constant()>>

3.4 调试展开

要在缠绕或运行之前验证 noweb 块将如何展开:

  • 将光标放在块内,按 C-c C-v v(org-babel-expand-src-block)。
  • Org 会打开一个临时缓冲区,显示所有 noweb 和 :var 替换后的确切代码。

4. 将配置和数据内联到缠绕文件中

Babel 可以将 Org 表格或标题属性中的变量直接内联到缠绕代码中:

# 存储在 Org 表格中的配置:
#+NAME: server_config
| Key     | Value            |
|---------+------------------|
| host    | 10.0.1.25        |
| port    | 8080             |
| timeout | 30               |

# 缠绕后的配置模块:
#+BEGIN_SRC python :tangle config.py :var cfg=server_config :colnames yes
  HOST = cfg[0][1]
  PORT = int(cfg[1][1])
  TIMEOUT = int(cfg[2][1])
#+END_SRC

要防止 Babel 在缠绕代码中生成变量包装器,请传递 :no-expand。


5. 缩进处理:对空格敏感的语言

在像 Python 或 YAML 这样对空格敏感的语言中,自动的 Org 重新缩进可能会破坏语法。

Python 缠绕的关键规则:

将 org-src-preserve-indentation 设置为 t。当为 t 时,Org 在导出或缠绕期间不会删除或添加缩进:

;; 在 Emacs 配置中:
(setq org-src-preserve-indentation t)

或者每个块使用开关 -i:

#+BEGIN_SRC python -i :tangle script.py

6. 如何执行缠绕

6.1 在 Emacs 内交互式操作

快捷键命令描述
C-c C-v t / C-c C-v C-torg-babel-tangle缠绕当前缓冲区中的所有代码块。
C-c C-v f / C-c C-v C-forg-babel-tangle-file提示选择一个磁盘上的 Org 文件并缠绕它。

6.2 从缠绕文件导航回 Org

当缠绕时,Babel 会在生成的文件中插入反向链接注释:

# [[file:~/project/notebook.org::*Core Utilities][core-slugify]]
def slugify(text: str) -> str:
...
# core-slugify ends here

在 Emacs 中,打开生成的文件并运行 M-x org-babel-tangle-jump-to-org 可以立即跳转回源 .org 文档中的确切标题和块!

6.3 无头命令行 / CI / Make 批量缠绕

无需打开 Emacs GUI 窗口即可缠绕文件(非常适合构建脚本、Makefile 和持续集成):

# 缠绕单个文件:
emacs -Q --batch \
  --eval "(require 'org)" \
  --eval "(require 'ob-tangle)" \
  --eval '(org-babel-tangle-file "analysis.org")'

# 从 shell 循环或 Makefile 缠绕多个文件:
emacs -Q --batch --eval "
  (progn
    (require 'org)
    (require 'ob-tangle)
    (dolist (file command-line-args-left)
      (with-current-buffer (find-file-noselect file)
        (org-babel-tangle))))" *.org

7. 文学化配置管理:init.org 模式

Org Babel 文学编程的一个流行用例是管理 Emacs 配置或系统 dotfiles。

在 ~/.emacs.d/init.el 中:

;; 引导 Org Babel 文学化配置
(require 'org)
(require 'ob-tangle)
(org-babel-load-file (expand-file-name "config.org" user-emacs-directory))

org-babel-load-file 自动检查 config.org 自上次生成 config.el 以来是否已被修改,如果需要则缠绕新版本,并加载生成的 .el 文件。


8. 缠绕项目总结清单

  1. 显式缠绕路径: 使用显式文件名(例如 :tangle "src/core.py")。
  2. 使用 Noweb 提高可读性: 将复杂算法分解为逻辑步骤(<<init-weights>>、<<forward-pass>>、<<backprop>>),以便叙述自然地呈现在读者面前。
  3. 保留缩进: 确保对于 Python 代码,org-src-preserve-indentation 始终为 t。
  4. 使用 Make/CI 自动化: 在构建系统中包含无头 emacs --batch 缠绕步骤,以使生成的源代码与 Org 文档保持同步。

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

相关技能