Org Babelで文芸的プログラミング

(This is a post for the Emacs Advent Calendar 2014 from Japan, a similar English version can be found here)

Org modeの話を聞いたことがある方は、Org modeは「TODOリスト管理ツールの 一つだ」と思われる方が多いかもしれません。それはその通りですが、 Org Babelというモードで、「文芸的プログラミング(Literate Programming)」を実現することができます。

Org Babelの使い方を示すために、あるホストのモニタリング情報を表示する、 単純なSinatraアプリケーションを例えにします。

例: ホストのモニタリング情報を表示するSinatra app

表示したい項目をシステムの uptime と ESTABLISHEDコネクションの数に絞り込みます。

uptime

20:37  up 4 days,  9:48, 5 users, load averages: 0.67 0.86 1.11

ESTABLISHED コネクションの数:

netstat -an | grep ESTABLISHED | wc -l

       5

この二つのコマンドの結果を集めて、 HTTPクライアントを使えば GET /vars で取れるようになります:

curl 127.0.0.1:9292/vars

事前準備

まず、ディレクトリの作成が必要です:

mkdir -p org
mkdir -p src
touch readme.org

(readme.org の作成は必要ではないですが、best practiceとしておすすめします…)

最終的に、ディレクトリ構成はこんな感じになります:

.
├── org
│   ├── app.org
│   └── run.org
├── readme.org
├── src
│   ├── Gemfile
│   ├── app.rb
│   └── config.ru

実装

アプリケーションの実装自体は org/app.org に書きます。

touch org/app.org

プロトタイプ

SinatraのWebアプリケーションを実装前に、Org Babel を使ってプロトタイプを作りましょう。 Rubyのcode blockを書いたら、 C-c C-c で実行することができます。

*** Notes on how to get the results

We could use something simple like:

#+begin_src ruby :results output code
s = <<VARS
Uptime:
#{`uptime`}

ESTABLISHED connections:
#{`netstat -an | grep ESTABLISHED | wc -l`}

VARS

puts s
#+end_src

#+RESULTS:
#+BEGIN_SRC ruby
Uptime:
22:23  up 4 days, 11:34, 5 users, load averages: 0.58 0.46 0.30


ESTABLISHED connections:
       8


#+END_SRC

:tangle の使い方

:tangle はOrg modeのcode block switch argumentsの一つです。 これを使えば、Org modeのcode blockは実装しているファイルを設定できます。

例えば、 :tangle を使って Gemfileconfig.ru の内容を書きましょう。

Dependencies from the app, just sinatra and webrick for the server is ok for now.

#+BEGIN_SRC ruby :tangle src/Gemfile
gem 'sinatra'
#+END_SRC

Needed to start the Sinatra application:

#+BEGIN_SRC ruby :tangle src/config.ru
require './app.rb'
run Sinatra::Application
#+END_SRC

全体例

EmacsのOrg modeバッファーで, C-c C-v tC-c C-v C-t を叩けば、 Org modeの org-babel-tangle の機能を呼び出します。 これで、動かせるプログラムは src/ ディレクトリの下に現れます。

#+TITLE: Monitoring HTTP endpoint

  This is a simple Sinatra application that provides
  the following endpoints:

  ‐ =/=        :: which responds =OK= in case all is good with the server.
  ‐ =/vars=    :: to get info about the system

  ** Bootstrapping the app

  *** Gemfile

  Dependencies from the app, just sinatra and webrick for now is ok.

  #+BEGIN_SRC ruby :tangle src/Gemfile
  gem 'sinatra'
  #+END_SRC

  *** Config.ru

  Needed to start the sinatra application

  #+BEGIN_SRC ruby :tangle src/config.ru
  require './app.rb'
  run Sinatra::Application
  #+END_SRC

  ** The App

  *** Import dependencies

  #+BEGIN_SRC ruby :tangle src/app.rb

  require 'sinatra'

  #+END_SRC

  *** =/= endpoint

  Just respond with =OK=.

  #+BEGIN_SRC ruby :tangle src/app.rb
  get '/' do
    'OK'
  end
  #+END_SRC

  *** =/vars= endpoint

  Respond with info about the system:

  #+BEGIN_SRC ruby :tangle src/app.rb
  get '/vars' do

  r = <<VARS
  Uptime:
  #{`uptime`}

  ESTABLISHED connections:
  #{`netstat -an | grep ESTABLISHED | wc -l`}

  VARS

  r
  end
  #+END_SRC

動かす方法

次に、プログラムを動かす方法を org/run.org にまとめます。 プログラムを動かす前に、Sinatraのプログラムに修正があったかも知れないので、 #+include: org/app.org でこの依存関係を設定します。

#+TITLE:   Running the Application
#+include: "org/app.org"

** Run it

To run it, we will need to get the dependencies first,
and then start it with bundler:

#+name: server
#+BEGIN_SRC sh :dir src
bundle install
bundle exec rackup
#+END_SRC

Now let's send some requests to it:

#+name: curl
#+BEGIN_SRC sh :wait 1
while true; do 
  curl 127.0.0.1:9292/vars 2> /dev/null
  sleep 1
done
#+END_SRC

Emacsでlong running processesを実行できますが、そうするとEmacsがコードブロック の結果待ちをしてしまいます… それを避けるため、 コードブロックの上に、 #+name を設定すれば、私が作ったgemで叩くことができます:

gem install org-converge
org-run org/run.org

そうすると結果は以下のようになります:

org-run org/run.org
...
Running code blocks now! (2 runnable blocks found in total)
server    -- started with pid 71840
curl      -- started with pid 71841
server    -- Using rack 1.5.2
server    -- Using rack-protection 1.5.3
server    -- Using tilt 1.4.1
server    -- Using sinatra 1.4.5
server    -- Using bundler 1.7.1
server    -- Your bundle is complete!
server    -- Use `bundle show [gemname]` to see where a bundled gem is installed.
server    -- [2014-12-11 22:56:42] INFO  WEBrick 1.3.1
server    -- [2014-12-11 22:56:42] INFO  ruby 2.1.2 (2014-05-08) [x86_64-darwin11.0]
server    -- [2014-12-11 22:56:42] INFO  WEBrick::HTTPServer#start: pid=71848 port=9292
server    -- 127.0.0.1 - - [11/Dec/2014 22:56:42] "GET /vars HTTP/1.1" 200 110 0.0527
curl      -- Uptime:
curl      -- 22:56  up 4 days, 12:07, 5 users, load averages: 0.78 0.84 0.76
curl      -- 
curl      -- 
curl      -- ESTABLISHED connections:
curl      --       12
curl      -- 
curl      -- 
server    -- 127.0.0.1 - - [11/Dec/2014 22:56:43] "GET /vars HTTP/1.1" 200 110 0.0210
curl      -- Uptime:
curl      -- 22:56  up 4 days, 12:07, 5 users, load averages: 0.78 0.84 0.76
curl      -- 
curl      -- 
curl      -- ESTABLISHED connections:
curl      --       12
curl      -- 
curl      -- 

まとめ

Org modeはとても便利です!皆さんぜひお試しください。 しかも、Githubさんが org-rubyを使って .org ファイルをHTMLに変換してくれるのです。 Ruby の Org modeパーサはまだOrg mode のすべての機能に追いついていませんが、 私がある程度メンテナンスしています。もしOrg Rubyに何かの問題があれば、 こちらに issue を発行していただければと思います。 :)

(そして、日本語が間違っている場合、こちらに PR ください…)