Org Babel을 사용한 문헌적 프로그래밍과 텡글링
Org Babel은 도널드 크누스(Donald Knuth)의 문헌적 프로그래밍(Literate Programming) 패러다임을 구현한 것입니다. 코드 사이에 주석을 끼워 넣는 대신, 인간을 위한 설명(prose), 아키텍처 노트, 설계 이유를 작성하고 그 안에 실행 가능한 코드 블록을 포함시킵니다.
하나의 Org 파일은 두 가지 다른 출력물에 대한 최종 진실 공급원(single source of truth) 역할을 합니다.
- 위빙 (인간 문서): 노트북을 포맷된 HTML, LaTeX/PDF, 또는 Markdown으로 내보내 독자를 위한 문서를 만듭니다.
- 텡글링 (기계 실행): 컴파일러, 인터프리터, 배포 시스템을 위한 순수하고 실행 가능한 소스 코드 파일(
.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 서브트리 수준 텡글링 (권장 패턴)
다른 제목(heading)들이 서로 다른 파일이나 모듈로 텡글링될 수 있습니다.
* 핵심 유틸리티
: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은 들여쓰기를 존중합니다.
def main():안에서<<argument-parser>>가 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-t | org-babel-tangle | 현재 버퍼의 모든 코드 블록을 텡글링합니다. |
C-c C-v f / C-c C-v C-f | org-babel-tangle-file | 디스크上的 Org 파일을 묻고 텡글링합니다. |
6.2 텡글링된 파일에서 Org 파일로 탐색
텡글링될 때 Babel은 생성된 파일에 역링크 주석을 삽입합니다.
# [[file:~/project/notebook.org::*핵심 유틸리티][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")'
# 셸 루프나 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. 텡글링 프로젝트를 위한 요약 체크리스트
- 명시적 텡글링 경로: 명시적인 파일 이름을 사용하세요 (예:
:tangle "src/core.py"). - 가독성을 위한 Noweb 사용: 복잡한 알고리즘을 논리적 단계(
<<init-weights>>,<<forward-pass>>,<<backprop>>)로 나누어 이야기가 독자에게 자연스럽게 흘러가도록 하세요. - 들여쓰기 유지: Python 코드의 경우 항상
org-src-preserve-indentation이t인지 확인하세요. - Make/CI로 자동화: 빌드 시스템에 헤드리스
emacs --batch텡글링 단계를 포함시켜 생성된 소스 코드가 Org 문서와 동기화된 상태를 유지하도록 하세요.