16.11 Noweb Reference Syntax

代码块可使用 noweb158 风格语法引用其他代码块:

<<CODE-BLOCK-ID>>

其中 CODE-BLOCK-ID 可以是单个代码块的 ‘NAME’ ,或是一组共享相同 ‘noweb-ref’ 头参数的代码块集合(见 Using Header Arguments)。Org 可将此类引用替换为被引用块的代码;若为具名 ‘NAME’ 的单个代码块,也可替换为该块的执行结果。

noweb’ 头参数控制 noweb 语法引用的展开行为,展开发生在代码块执行、导出或导出时。

no

默认值。在执行、导出、导出时均不展开代码中的 noweb 引用。

yes

在执行、导出、导出时均展开代码中的 noweb 引用。

tangle

仅在导出时展开 noweb 引用,执行和导出时不展开。

strip-tangle

在执行和导出时展开 noweb 引用,导出时移除引用。

no-export

在执行和导出时展开 noweb 引用,导出时不展开。

strip-export

在执行或导出前展开 noweb 引用,导出时移除引用。

eval

仅在执行前展开代码中的 noweb 引用。

最简单的用法是将单个代码块内容插入其他块中。如下例所示:

#+NAME: initialization
#+BEGIN_SRC emacs-lisp
  (setq sentence "Never a foot too far, even.")
#+END_SRC

#+BEGIN_SRC emacs-lisp :noweb yes
  <<initialization>>
  (reverse sentence)
#+END_SRC

第二个代码块展开后为:

#+BEGIN_SRC emacs-lisp :noweb yes
  (setq sentence "Never a foot too far, even.")
  (reverse sentence)
#+END_SRC

注意 noweb 展开不会自动继承 ‘:var’ 头参数159

也可以引用共享同一 ‘noweb-ref’ 头参数的多个代码块,该参数可在文件、子树或代码块层级设置。下例 Org 文件中,所有代码块内容会在导出时拼接为纯代码文件。

#+BEGIN_SRC sh :tangle yes :noweb yes :shebang #!/bin/sh
  <<fullest-disk>>
#+END_SRC
* the mount point of the fullest disk
  :PROPERTIES:
  :header-args: :noweb-ref fullest-disk
  :END:

** query all mounted disks
#+BEGIN_SRC sh
  df \
#+END_SRC

** strip the header row
#+BEGIN_SRC sh
  |sed '1d' \
#+END_SRC

** output mount point of fullest disk
#+BEGIN_SRC sh
  |awk '{if (u < +$5) {u = +$5; m = $6}} END {print m}'
#+END_SRC

默认情况下,换行符会分隔每个 noweb 引用的拼接内容。如需使用其他分隔符,可修改 ‘noweb-sep’ 头部参数。

此外,Org 可以插入单个代码块的执行结果,而非其代码体本身[160。当在代码块名称后附加括号(可包含参数)时,便会触发执行,如下所示。

<<NAME(optional arguments)>>

注意这种用法必须使用 ‘NAME’ 关键字指定的名称,使用 ‘noweb-ref’ 设置的引用无法触发执行。

下例展示带括号与不带括号的 noweb 引用在导出内容上的区别。给定:

#+NAME: some-code
#+BEGIN_SRC python :var num=0 :results output :exports none
print(num*10)
#+END_SRC

该代码块:

#+BEGIN_SRC text :noweb yes
  <<some-code>>
#+END_SRC

展开为:

print(num*10)

下面使用带括号的 noweb 引用,并将变量 ‘num’ 设为 10:

#+BEGIN_SRC text :noweb yes
  <<some-code(num=10)>>
#+END_SRC

展开结果为代码块 ‘some-code’ 的执行结果,而非代码本身:

100

noweb 插入会保留引用前的前缀字符。下例演示该行为:由于 ‘<<example>>’ 引用位于 SQL 注释符后,展开后的每一行都会被注释。给定:

#+NAME: example
#+BEGIN_SRC text
  this is the
  multi-line body of example
#+END_SRC

该代码块:

#+BEGIN_SRC sql :noweb yes
 ---<<example>>
#+END_SRC

展开为:

#+BEGIN_SRC sql :noweb yes
 ---this is the
 ---multi-line body of example
#+END_SRC

该行为不会影响不含换行的内联 noweb 引用,因此内联引用可以正常使用。

该特性也可用于管理导出代码片段的缩进。给定:

#+NAME: if-true
#+BEGIN_SRC python :exports none
print('do things when true')
#+end_src

#+name: if-false
#+begin_src python :exports none
print('do things when false')
#+end_src

该代码块:

#+begin_src python :noweb yes :results output
if true:
    <<if-true>>
else:
    <<if-false>>
#+end_src

展开为:

if true:
    print('do things when true')
else:
    print('do things when false')

在代码块中设置 ‘noweb-prefix’ 为 ‘no’ 可关闭此前缀行为,例如:

#+BEGIN_SRC elisp :noweb-prefix no
  (setq example-data "<<example>>")
#+END_SRC

展开为:

(setq example-data "this is the
multi-line body of example")

如不确定代码块展开后的结果,可使用以下命令预览

C-c C-v vC-c C-v C-v (org-babel-expand-src-block)

根据头参数展开当前代码块,并在预览缓冲区中显示结果。


Footnotes

(158)

有关 noweb 文学编程详情,参见 https://www.cs.tufts.edu/~nr/noweb/.

(159)

下例中尝试执行第二个代码块会报错,因为第一个代码块中定义的变量在第二个块中无效。

#+NAME: get-prompt
#+BEGIN_SRC emacs-lisp :var prompt="root> " :var command="ls"
  (concat prompt command)
#+END_SRC
#+RESULTS: get-prompt
: root> ls
#+BEGIN_SRC emacs-lisp :noweb yes
  <<get-prompt>>
#+END_SRC

上一代码块展开时未设置 promptcommand 的值。

#+BEGIN_SRC emacs-lisp
  (concat prompt command)
#+END_SRC
(160)

引用执行时,光标会定位在被引用代码块处,并使用其头部参数(含继承而来的参数)