#+title: 文学编程 #+html_head: : 文学编程希望能做到写的程序适于阅读,文档描述能遵循问题的逻辑顺序,同时不受制于编程语言的缺陷 支持LaTex - Unicode References :: for instance, \alpha, \beta and \gamma. - Subscripts :: like Hydrogen atoms, H_2, and Water, H_{2}O. - Superscripts :: The mass of the sun is 1.989 x 10^30 kg. - Embedded Equations :: Surrounded with either single =$=, like $a^2=b$, or escaped parenthesis, like: \( b=\frac{1}{2} \) - Separated equations :: Either in double =$$= or escaped brackets, like this: $$ a=\frac{1}{2}\sqrt{2} $$ or this: \[ a=-\sqrt{2} \] or this: \begin{equation} x=\sqrt{b} \end{equation} #+OPTIONS: tex:t #+begin_src emacs-lisp (progn (directory-files ".") (print "hello")) #+end_src #+RESULTS: : hello - 按下 =C-c C-c= 执行命令 - 按下 =C-c '(单引号)= 可以以指定的mode来编辑这段代码 (利用paredit插件来编写代码) * Python案例 需要明确的 =return= 语句 #+begin_src python from os import listdir return listdir(".") #+end_src * shell案例 shell使用输出到标准输入的内容作为结果 #+begin_src shell ls -l #+end_src 若按下 =C-c C-c= 没反映,需要执行 =M-x load-library ob-sh= * 其他语言 需要添加类似下面的配置到 =.emacs= 文件中: #+begin_src emacs-lisp (org-babel-do-load-languages 'org-babel-load-languages '((sh . t) (js . t) (emacs-lisp . t) (perl . t) (scala . t) (clojure . t) (python . t) (ruby . t) (dot . t) (css . t) (plantuml . t))) #+end_src * 代码块设置 代码块可以有0个或者多个头参数 ** 例子:dir参数 该参数会设置代码块执行的工作目录: #+begin_src shell :dir /etc ls #+end_src 该参数可以通过Tramp实现在远程服务器上执行代码 #+begin_src shell :exports both hostname -f #+end_src #+RESULTS: : WSSHA126093G1J1.local #+begin_src sh :dir /5zyx.com: hostname -f #+end_src * 设置头参数的位置 不同位置设置参数所影响的作用域不一样,下面以作用域从特殊到一般的顺序列出 - 头参数嵌入到代码块中,或者在代码块上面 - 设置某一标题下面所有代码块的默认头参数 - 设置整个文档中所有代码块的默认头参数 - 设置所有文档中所有代码块的默认头参数 若要设置所有文档的某个参数,可以设置下列参数: - org-babel-default-header-args - org-babel-default-header-args: ~注意:~ 可以在代码块被调用时设置头参数 * 太多参数了? 若需要设置许多的参数,可以考虑将一个或多个参数放到代码块的上面去。 以下几个例子等价: #+begin_src sh :dir /etc :var USER="root" grep $USER passwd #+end_src #+HEADER: :dir /etc #+begin_src sh :var USER="root" grep $USER passwd #+end_src #+HEADER: :dir /etc #+HEADER: :var USER="root" #+begin_src sh grep $USER passwd #+end_src * 设置某一标题下面所有代码块的默认头参数 :PROPERTIES: :dir: /etc :END: #+begin_src ruby File.absolute_path(".") #+end_src 可以将头参数的设置放入标题下的属性drawer中 1. 在org文件中创建一个标题 2. 输入 =C-c C-x p= 3. 输入属性名称 :dir 4. 输入属性值 /etc * 指定特定语言的默认头参数 :PROPERTIES: :header-args:shell: :dir /etc :header-args:ruby: :dir / :END: #+begin_src shell ls -d $(pwd) #+end_src #+RESULTS: : /private/etc #+begin_src ruby File.absolute_path(".") #+end_src #+RESULTS: : / 注意: 有些参数智能通过 ~header-args~ 的方式设置 * 为文档内的所有代码块设置默认参数 #+PROPERTY: header-args:shell :tangle no 注意: 需要在设置项上执行 ~C-c C-c~ 否则该配置项不生效 * 头参数的类型 ** 执行类参数 以 ~dir~ 为代表,这类参数影响代码块如何执行 ** 导出类参数 影响了把org文件导出成HTML(或其他格式)时,代码块以及代码块的执行结果如何展示 ** 文学编程类参数 将代码块连接起来,可能会改变实际的源代码 ** 变脸类参数 通过不同方式设置代码块终端变脸 ** 杂类 输入/输出 其他参数 * 执行类参数 ** results参数 *** 是表达式的返回值呢? *** 还是代码的输出结果? 以下Ruby代码块为例,默认情况下,得到的事最后表达式的返回结果 #+begin_src ruby puts 'Hello World' 5*6 #+end_src #+RESULTS: : 30 若将 ~:results~ 头参数的值改为 ~output~ ,则结果是程序的输出: #+begin_src ruby :results output puts 'Hello World' 5*6 #+end_src #+RESULTS: : Hello World 注意: sh代码块的 ~:results~ 默认值为 ~output~ ** 影响输出格式的结果 代码的执行结果会插入到文档中,它可能有以下几种格式的输出。 - table :: 若结果为单个数组,则插入一行,若结果为数组的数组,则插入一个表格 - list :: 按照普通org-mode列表的格式插入一个无序列 - verbatim :: 原样输出 - file :: 将结果写入到文件中 - html :: 认为执行结果是html代码,导出时原样导出 - code :: 认为执行的结果还是原语言的代码 - silent :: 只在mini-buffer中显示执行的结果 之所以有这么多的变种,是因为执行结果本身也可以被导出,同时这些执行结果还能作为其他代码块的变量输入 ** 输出成列表 #+begin_src ruby :results list Dir.entries('.').sort.select do |file| file[0] != '.' end #+end_src ** 原样输出 shell命令和日志输出比较适合原样输出,例如: #+BEGIN_SRC sh :results verbatim :exports both :output list # ssh -v goblin.howardabrams.com ls mossandcrow #+END_SRC #+RESULTS: OpenSSH_6.6.1, OpenSSL 1.0.1f 6 Jan 2014 debug1: Reading configuration data /etc/ssh/ssh_config debug1: /etc/ssh/ssh_config line 19: Applying options for * debug1: Connecting to goblin.howardabrams.com [162.243.135.186] port 22. debug1: Connection established. debug1: identity file /home/howard/.ssh/id_rsa type 1 debug1: identity file /home/howard/.ssh/id_rsa-cert type -1 * Session 默认情况下,每个代码块在每次运行时都会重启自己的一个解释器.通过为 ~:session~ 头参数设置一个标签值,则所有拥有同一标签的代码块在运行时都在同一个解释器中 注意:不同代码块之间可以传递值 下面例子说明每个代码块执行时都会重启自己的解释器: #+begin_src python :session foobar avar = 42 #+end_src #+RESULTS: #+begin_src python :session foobar avar /2 #+end_src #+RESULTS: : 21 * Confusing Stuff :PROPERTIES: :session: stateful :END: #+begin_src shell :results silent NUM_USERS=$(grep 'bash' /etc/passwd | wc -l --) #+end_src #+begin_src shell echo $NUM_USERS #+end_src #+RESULTS: #+begin_src ruby 21*2 #+end_src #+RESULTS: : 42 =警告=: 为整个section设置的 ~:session~ 参数会影响到每个代码块,而不管该代码块是哪种编程语言 ** 将结果写入到文件中 创建并执行下面代码块块: #+begin_src ruby :results file output :file primes.txt :exports both require 'prime' Prime.each(5000) do |prime| p prime end #+end_src #+RESULTS: [[file:primes.txt]] 注意: ~:file~ 参数需要与 ~:results output~ 共用,因为它不知道以哪种格式输出内部值 * 导出 按下 ~C-c C-e h o~ 会导出HTML文件,并用浏览器打开. ~:exports~ 头参数指明了哪些内容会被导出: - code :: 只导出代码 - results :: 只导出结果 - both :: 同时导出代码与结果 - none :: 跳过代码块,什么都不导出 注意: ~:exports~ 一般被设置成文件属性。 若导出成HTML时希望保持语法高亮,只需要加载 ~htmlize~ 库即可: #+begin_src emacs-lisp (require 'htmlize) #+end_src * 文学编程 ** Tangling 从org文件中抽取出源代码创建源码文件 ~:tangle~ 参数会将所有同类语言的代码块中内容都写入指定的源码文件中 #+begin_src ruby :tangle double-space.rb while s = gets print s ; puts end #+end_src #+RESULTS: : nil 输入 ~C-c C-v t~ 生成double-space.rb文件 拥有相同 =:tangle= 值的代码块内容会按顺序写入到同一个文件中。若 =:tangle= 参数值为 =yes= 则写入的文件其名称与原org文件名称一样(后缀不一样) 也可以用 =PROPERTY= 来为整个文件的所有代码块指定一个值: #+begin_src #+PROPERTY: tangle ~/.emacs.d/lisp/bling-mode.el #+end_src ** 注释 若需要与他人分享代码,可以将文档内容转换成注释: Precede each line in the text from standard in (or file) with the current line number. See [[http://benoithamelin.tumblr.com/ruby1line][one liners]]. #+begin_src ruby :results list :tangle lineno.rb comm :comments org while s = gets puts "#{$<.file.lineno}:#{s}" end #+end_src #+RESULTS: : - nil #+PROPERTY: tangle lineno.rb #+PROPERTY: comments org =:comments= 参数指明了是否将以及如何将文档内容作为注释插入tangle出的代码,其值为org表示将文档内容作为org code来格式化再做为注释插入。 注意:只有代码块上面的内容才会作为注释插入 默认值为 =no= 意味着不插入任何注释 ** Shebang 当创建脚本时,可以通过 =:shebang= 参数指定解释器(可以以代码块的header或文档属性的方式来指定) Precede each line in the text from standard in (or file) with the current line number. See [[http://benoithamelin.tumblr.com/ruby1line][one liners]]. #+BEGIN_SRC ruby :shebang "#!/usr/local/bin/ruby" while s = gets puts "#{$<.file.lineno}: #{s}" end #+END_SRC #+RESULTS: : nil #+PROPERTY: shebang #!/bin/ruby #+PROPERTY: tangle lineno ** Noweb 若你为某个代码块命了名,则其他代码块就可以包含该代码块了 使用 =:noweb= 参数。[fn:1] 假如有如下一个org文件: Print the last field of each line. #+NAME: the-script #+begin_src ruby puts $F.last #+end_src #+begin_src shell :noweb yes :tangle last-col.sh ruby -ane '<>' #+end_src #+RESULTS: 会创建一个名为 =last-col.sh= 的源代码文件,其内容为: #+begin_src ruby -ane 'puts $F.last' #+end_src ** 变量 org能够以变量的形式传递一个或多个值到你的代码块中。下面演示一个静态地设置变量的例子: #+begin_src python :var interest=13 return 313 * (interest / 100.0) #+end_src #+RESULTS: : 40.69 可以在一行或多行位置上同时定义多个变量,下面是个例子 #+HEADER: :var a=42 d=56 :var f=23 #+HEADERS: :var b=79 e=79 #+begin_src ruby :var c=3 g=2 [a,b,c,d,e,f,g] #+end_src #+RESULTS: | 42 | 79 | 3 | 56 | 79 | 23 | 2 | 这样静态的设置变量的值的意义? *** 将代码块的结果作为值传递给另一个代码块 #+NAME: twelve-primes #+begin_src ruby require 'prime' Prime.first 12 #+end_src #+RESULTS: twelve-primes | 2 | 3 | 5 | 7 | 11 | 13 | 17 | 19 | 23 | 29 | 31 | 37 | #+begin_src python :var primes=twelve-primes return primes[-1] #+end_src #+RESULTS: : 37 ruby 与python结合完成任务 *** 表格形式的变量数据 #+NAME: cool-numbers #+begin_src emacs-lisp (mapcar (lambda (i) (list i (random 10) (expt i 2) (random 100) (expt i 3) (random 1000))) (number-sequence 1 10)) #+end_src #+begin_src python :var nums=cool-numbers :results list return [cell +1 for row in nums for cell in row] #+end_src *** 表格分片 我们可以只传递表格中的某一行,方法是指定一个索引编号. #+begin_src ruby :var fifth=cool-numbers[4] fifth #+end_src #+RESULTS: | 5 | 7 | 25 | 66 | 125 | 867 | 类似方法,我们也能只传递表格中的某一列数据。下面是一个例子,其中的逗号表示任意行,后面的4则限制了只取第五列的数字 #+NAME: cubes #+begin_src elisp :var cubes=cool-numbers[,4] cubes #+end_src #+RESULTS: cubes | 1 | 8 | 27 | 64 | 125 | 216 | 343 | 512 | 729 | 1000 | *** Reprocessing 名为cool-numbers的表格被名为cubes的代码块所使用,然后cubes代码块的结果值又可以传递给其他代码块: #+NAME: roots_of_list #+begin_src python :var lst=cubes :results list import math return [math.sqrt(n) for n in lst] #+end_src #+RESULTS: roots_of_list - 1.0 - 2.8284271247461903 - 5.196152422706632 - 8.0 - 11.180339887498949 - 14.696938456699069 - 18.520259177452136 - 22.627416997969522 - 27.0 - 31.622776601683793 *** 保持代码块的整洁 代码块执行时可能与其他事物有关。若一段代码需要执行,但这段代码并不需要告诉其他人,则这段代码可以放置再代码块的外部,下面是些例子. #+begin_src source openrc nova list #+end_src 这里我想执行的代码是 =nova list=, 但是在执行该代码之前还需要执行source命令. 而该source命令我又不希望被导出. 则可以将这种不可见的代码放置在 =prologue= 中 #+HEADER: :prologue "source openrc" #+begin_src shell nova list #+end_src #+RESULTS: =:prologue= 中的代码不会被导出,只能看到 =nova list= 命令及其执行结果 *** Using RVM 可以在 ~:prologue~ 的命令后加上两个反斜杠来表示代码块执行时的前缀(只对shell调用有效) #+begin_src shell :prologue "~/.rvm/bin/rvm 1.9.3@msw exec \\" gem list #+end_src #+RESULTS: 注意: Ruby或者Python代码的执行时基于rvm,pyvenv或elpy的 ** 对结果进行修正 #+begin_src shell :post skip_first(data=*this*) ls -l #+end_src #+RESULTS: 可以使用 ~:post~ 参数来修正代码的执行结果 #+NAME: skip_first #+begin_src elisp :var data="" :exports none (cdr data) #+end_src * 其他特性 ** 调用代码块 可以再调用代码块时给代码块赋值 #+CALL: roots_of_list(lst='(16 144 81 61)) #+RESULTS: - 4.0 - 12.0 - 9.0 - 7.810249675906654 使用其他代码块的输出结果. #+CALL: roots_of_list(lst=cool-numbers[,2]) #+RESULTS: - 1.0 - 2.0 - 3.0 - 4.0 - 5.0 - 6.0 - 7.0 - 8.0 - 9.0 - 10.0 #+NAME: cube #+begin_src elisp :var n=0 :exports none (* n n n) #+end_src #+RESULTS: cube : 0 #+CALL:cub[:results table](n=3) ** 嵌入运算结果 如果想快速得到一门语言片段的计算结果,可以在大括号内嵌入这段代码。然后在行首按下 ~C-c C-c~ 看结果 src_ruby{5+6} } src_elisp{(+ 1 2 3 4 5)} } call_roots_of_list( lst=cool-numbers[,2] ) ** Library of Babel ~Library of Babel~ 是一系列可以在任意org-mode文件总调用的代码块. 就好像时Ruby中的Gem源一样, 你需要指定哪些包含有命名代码块的文件是可访问的. 按照以下步骤进行操作: - 新建一个org文件,并添加至少一个命名了的代码块 - 按下 ~C-c C-v i~ - 选择你新建的这个org文件,表示将该文件加入babel集合中. #+begin_src python :post take(data=*this*, only=3) return [x * x for x in range(1,20)] #+end_src #+RESULTS: | 1 | 4 | 9 | ... | 该功能在以下几种情况下很有用: - 用于 ~:post~ 参数中处理结果 - 通过 ~#+CALL~ 语句将运算结果嵌入到行中 - 通过 ~call_XYZ()~ 语句将运算结果嵌入到行中 要想让这些文件永久性的添加到 babel library中,需要在你的Emacs初始化文件中对每个想被添加的org文件调用 ~org-babel-lob-ingest~ 函数. * 专用语言 有些语言被org-mode支持是为了更好的编写文档 ** SQL 假设已经安装了 ~Sqlite~ , 并且通过 ~M-x load-library ob-sqlite~ 加载必要的库: 可以在sqlite命令行中使用 ~.backup~ 命令导出一个数据库,然后在 ~:db~ 参数中指定该数据库. LIKE: #+begin_src sqlite :db doplhins.db SELECT gender,COUNT(gender) FROM oasis GROUP BY gender; #+end_src ** Graphviz #+begin_src dot :file ../images/some-illustration.png :exports results digraph { a -> b; b -> c: c -> a; } #+end_src #+RESULTS: [[file:../images/some-illustration.png]] 警告: 若你希望执行代码块并生成图片,则需要设置代码块的语言类型为 ~dot~, 但若你想编辑该代码块,则又需要把语言类型设置为 ~graphviz-dot~. #+BEGIN_SRC plantuml :file ../images/sequence.png :exports results Alice -> Bob: synchronous call Alice ->> Bob: asynchronous call #+END_SRC #+RESULTS: [[file:../images/sequence.png]]