すぎゃーんメモ Twitter

2012-02-09

marmaladeからmagitを入れてみた

emacsからgitを使いたいと思って、訊いてみたところmagitとかeggとかがあるそうで。

It’s Magit!

Bogosities

みたところmagitは何ファイルかに分かれていてmake installするカンジで、eggはほぼ一つにまとまっている。egg本家じゃなくてforkの方が開発進んでいるかんじなのかな。

あとちょっと気になってるMarmaladeとかからパッケージをインストールするのはどうなんだろう?と思ってチャレンジしてみることにした。

Marmalade: Spreadable Elisp

ここにmagit-1.1.1が入っているので、ここからインストールすることができそう。eggは無いようだ。

まずはpackage.elを入れてパスを通す。既にauto-installを使っているのでそれを使って入れてしまった

(auto-install-from-url "http://repo.or.cz/w/emacs.git/blob_plain/1a0a666f941c99882093d7bd08ced15033bc3f0c:/lisp/emacs-lisp/package.el")

で、準備

(require 'package)
(add-to-list 'package-archives '("marmalade" . "http://marmalade-repo.org/packages/"))

これでmarmaladeに登録されているものをインストールしたりできるようになるらしい。magitは簡単に入った。デフォルトでは~/.emacs.d/elpa以下に入るようだ。あとは初期化ファイル

(require 'package)
(package-initialize)
(package-activate 'magit '(1 1 1))

とか書いておけば使えるようになるらしい。


magit使ってみたカンジではとりあえずシェルでやっている操作はだいたいちゃんと出来そうではある。まだ慣れなくて戸惑うのもあって時間かかるけど。eggの方が良いのかなぁとかも思いつつも、まずはしばらくmagitを練習してみるつもり

2012-01-20

起動時間を計測する 改訂版

起動時間を計測する - すぎゃーんメモ の続き。

id:tomoyaさんからコメントで教えていただきましてemacs-init-timeという関数存在していることを知りました。定義

(defun emacs-init-time ()
  "Return a string giving the duration of the Emacs initialization."
  (interactive)
  (let ((str
	 (format "%.1f seconds"
		 (float-time
		  (time-subtract after-init-time before-init-time)))))
    (if (called-interactively-p 'interactive)
        (message "%s" str)
      str)))

となっていて、まさに中でafter-init-timebefore-init-time差分計算している。わざわざ自分で書かなくてもこのコマンド一発で起動にかかった時間を知ることができるんですね、、。ただ、表示されるのは秒単位小数点第一位までのようで、そこらへんカスタマイズは出来ないぽい。そうか、時間の加減を扱う場合float-timeとかtime-subtractとかを使うと良いんだ。

ということで結局自分~/.emacs.elでは

(add-hook 'after-init-hook
  (lambda ()
    (message "init time: %.3f sec"
             (float-time (time-subtract after-init-time before-init-time)))))

と書いて小数点第三位までを表示するようにした。

2012-01-15

ddskk 14.4

設定を見直し中 - すぎゃーんメモ シリーズ

SKK(ddskk)を入れてみた - すぎゃーんメモ のときに導入していたけど、改めて入れてみようと思ってみてみたら、DDSKK 14.2以降からはAPELなどを入れる必要がなくなったそうで。

no title

$ curl -O http://openlab.ring.gr.jp/skk/maintrunk/ddskk-14.4.tar.gz
$ tar zxvf ddskk-14.4.tar.gz
$ cd ddskk-14.4
$ sudo make install EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs

とするだけで使えるようになるっぽい。

起動時の設定には

(require 'skk-autoloads)
(custom-set-variables
 '(skk-sticky-key ";"))
(define-key global-map (kbd "C-x C-m") 'skk-mode)

とだけ書いて使ってみてる。

2012-01-11

anything-c-yasnippetを使わずにyasnippetをanythingインタフェースで選択する

設定を見直し中 - すぎゃーんメモ シリーズ

yasnippetを使ったスニペット補完を初めて使い始めている。で、補完の呼び出し方には通常のyas/expandコマンドを呼ぶ以外に、auto-complete-configに含まれるac-source-yasnippetを使う方法や、anythingインタフェースで選択するためのanything-c-yasnippetがある。

yasnippet, anything-c-yasnippetのまとめエントリー - IMAKADO::BLOG

個人的にはauto-completeの補完とyasnippetスニペット補完は別々にしたいな、と思ってanything-c-yasnippetを使おうと思ったのだけど、どうやらコレは最新のyasnippetでは動かないらしい。

yasnippet,anything-c-yasnippetをインストールした - Dive into the Tech World!

yasnippet-0.6.1b(c?)を使うとanything-c-yasnippet.elが動かない - 放牧日記

これらの記事のように、パッチを当てる必要がある。


で、このanything-c-yasnippetyasnippetソースを読んでいて気付いたのだけど、今のyasnippetにはyas/prompt-functionsという変数定義されている。ログ見る限りでは2009-07-13、v0.6.0あたりから出てきたのかな?

(defcustom yas/prompt-functions '(yas/x-prompt
                                  yas/dropdown-prompt
                                  yas/completing-prompt
                                  yas/ido-prompt
                                  yas/no-prompt)

複数の補完候補があった場合に、ここに登録されている関数が呼ばれ、ユーザに選択させることになるらしい。それらの関数プロンプト文字列と、選択候補のリスト引数として受け取り、確定させる場合はそのリスト内の要素を返す。nilが返された場合は次の関数に選択を任せることになるようだ。デフォルトでは上記のように定義されていて、例えばyas/x-promptx-popup-menu関数を使ってユーザに選択させることになる。同じkeyで異なる定義スニペットを複数登録してある場合や、2つ以上のスニペットが登録してあるモードyas/insert-snippetを呼んだときはコレが呼ばれるはず。

なので、このprompt-functionsを自分定義してやれば、anythingインタフェースを簡単に作ることができる。

(defun my-yas/prompt (prompt choices &optional display-fn)
  (let* ((names (loop for choice in choices
                      collect (or (and display-fn (funcall display-fn choice))
                                  coice)))
         (selected (anything-other-buffer
                    `(((name . ,(format "%s" prompt))
                       (candidates . names)
                       (action . (("Insert snippet" . (lambda (arg) arg))))))
                    "*anything yas/prompt*")))
    (if selected
        (let ((n (position selected names :test 'equal)))
          (nth n choices))
      (signal 'quit "user quit!"))))
(custom-set-variables '(yas/prompt-functions '(my-yas/prompt)))

丁寧なことにオプショナルな第3引数としてchoicesリストそれぞれの要素(templateのオブジェクト)から登録してあるテンプレート名を引くための関数が渡されてきてくれるので、それを使って候補名のリストを作って、anything-other-bufferに渡してやるだけで良い。actionでは選択した名前をそのまま渡すようにすることで、変数selectedに選択されたテンプレート名を得ることができる。中断した場合nilが返ってくるので、quitシグナルを送ってやることで補完も中断される。

これだけで、anything-c-yasnippetを導入してパッチを当てたりしなくても、yasnippetの補完をanythingインタフェースで実現することができた。さくさく選べるようになるのでちょっとしたモノもどんどんsnippetに登録していくと効率が上がりそう。

試しにauto-completeとyasnippetの補完を使ってコードを書く例をyoutubeに上げてみた。

D

スラスラっと書けるカンジがして良いですね。

2012-01-10

expressでapp全体の設定値をroutesで使う(module間で変数を受け渡す)方法いろいろ

Expressテンプレートからプロジェクトを作ると、現在の最新版2.5.4では

.
├── app.js
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   └── index.js
└── views
    ├── index.jade
    └── layout.jade

6 directories, 6 files

のようにファイルディレクトリが作られる。app.jsが本体で、

var express = require('express')
  , routes = require('./routes')

var app = module.exports = express.createServer();

...

app.get('/', routes.index);

のような形でパスに対するハンドラを登録する形になっている。routes/index.jsでは

exports.index = function(req, res){
  res.render('index', { title: 'Express' });
};

となっていて、ここでそれぞれリクエストに対する処理を記述する形で作っていける。

問題

例えばoauth認証を行うハンドラを作る際に、oauthのconsumer_keyやconsumer_secretなどの値はroutes/index.jsにはベタ書きしたくない。環境変数管理しても良いけど、設定値の数が多くなってくるとつらいので、例えば起動時にapp.jsconfigファイルを読み込んで使うようにする(その上で環境変数ちょっと弄ったりもできる)。*1

var config = JSON.parse(require('fs').readFileSync('config.json'));
config.foo = process.env.NODE_ENV === 'production' ? 'bar' : 'baz';

ここで作ったconfig変数で一元管理するようにしたい。が、そうすると、その変数routes/index.jsからは使えない。

こういうときにどうすれば良いか。近い話が先日、nodejs_jpのMLで議論されていた。

モジュール分割とグローバル変数のお作法 - nodejs_jp | Google グループ

自分で読んで理解した範囲で、幾つかの方法検証してメリットデメリットを考えてみた。自分だったらこうするかな、という形で書いたのでMLの内容とは違うアプローチだったりするけど。

  1. global変数にする
  2. app.setを使ってmodule.parentから参照する
  3. クロージャを使う
  4. 共有オブジェクトとして使うmoduleを作る

1. global変数にする

var宣言を外す*2ことで、変数グローバルになる。

config = JSON.parse(require('fs').readFileSync('config.json'));
config.foo = process.env.NODE_ENV === 'production' ? 'bar' : 'baz';

グローバル変数ならroutes/index.jsからも参照できる。

exports.index = function (req, res) {
    console.log(config);        // available
    res.render('index', { title: 'Express' });
};
長所

簡単に使える…ってことくらい?

短所

グローバル汚染は致命的。どこで上書きされるか分からないし管理出来ない。


2. app.setを使ってmodule.parentから参照する

MLを読むまでmodule.parentって使ったことなくて知らなかった。requireした側のexportsオブジェクトを参照出来る…?

var app = module.exports = express.createServer();

var config = JSON.parse(require('fs').readFileSync('config.json'));
config.foo = process.env.NODE_ENV === 'production' ? 'bar' : 'baz';
app.set('config', config);

とセットしておくと、routes/index.jsから

exports.index = function (req, res) {
    console.log(module.parent.exports.set('config')); // available
    res.render('index', { title: 'Express' });
};

といったかたちで、module.parent.exportsでapp.jsのexports(すなわちapp.js内のvar app)を得られるので、そこからsetを使って値を参照できる。

長所

グローバル汚染せずに、コード変更も少なくて済む

短所

app.setexpress依存しているし、express内でもapp.setを使っているので"env"や"view"などのキー名で設定値を弄ると危険。そしてmodule.parentを使って参照するのは相互の依存が強すぎて、例えばapp.js

var app = express.createServer();

としか書いていなくてexportsにappを入れていないと参照できないなどの問題が考えられる。


3. クロージャを使う

routesの使い方から変更する形になるけれど、例えばroutes/index.js

module.exports = function (config) {
    return {
        index: function (req, res) {
            console.log(config);
            res.render('index', { title: 'Express' });
        }
    };
};

のように「引数として設定値を受け取る関数」を返すように変形してやるこの関数がハンドラやオブジェクトを返す。で、app.js

var config = JSON.parse(require('fs').readFileSync('config.json'));
config.foo = process.env.NODE_ENV === 'production' ? 'bar' : 'baz';
routes = routes(config);

と、require('routes')で返ってきた「関数」にconfig引数として渡してやることで、そのconfigを内部で使用するハンドラやオブジェクトを得ることができる。この例では実行結果を同名の変数に上書きしているけど、変えてやれば混乱することはないと思う。

長所

スコープが絞れる。引数にapp丸ごと渡して2.と同様にapp.setで参照することもできるが、そこまでやる必要がないので最低限のデータ受け渡しで済む。また、app->routesの一方的な渡し方になるので、例えばroutes側でconfig勝手に書き換えられるといった心配がなくなる。

短所

関数呼び出しを忘れないように気をつけなければならない、とか routes側のコードの見通しが少し悪くなる…とか? まぁちょっとカッコ悪い気はする


4. 共有オブジェクトとして使うmoduleを作る

例えばlib/share.jsというのを作って、

module.exports = {};

とだけ書いておく。これを使って、データを共有する。app.jsでは

var config = JSON.parse(require('fs').readFileSync('config.json'));
config.foo = process.env.NODE_ENV === 'production' ? 'bar' : 'baz';
require('./lib/share').config = config;

といったかたちでshareオブジェクトに代入。routes/index.jsでは

exports.index = function (req, res) {
    console.log(require('./../lib/share').config); // available
    res.render('index', { title: 'Express' });
};

という具合に参照できる。

パスに"."とか".."とか書きたくない場合環境変数NODE_PATHに通して起動してやれば良いらしい。

$ NODE_PATH=lib node app.js

もしくはpackage.json

{
    "name": "application-name"
  , "version": "0.0.1"
  , "private": true
  , "dependencies": {
      "express": "2.5.4"
    , "jade": ">= 0.0.1"
  }
  , "scripts": {
    "start": "NODE_PATH=lib node app.js"
  }
}

とstartコマンド環境変数指定を書いてしまって

$ npm start

で起動。こうしてパスを通しておくと、どこからでもrequire('share')で参照できるようになる。ただし例えば"share"とうモジュールはnpmパッケージに存在しているので、このネーミングには注意する必要がある。

長所

共有するオブジェクト定義しておくだけでrequire使えばどこからでも使えるようになるので便利

短所

上述のパス指定とモジュール名の衝突の懸念くらいか。個人的にはrequireしてきた値を変更する、というのが何か違和感があるのだけど… どこからでも変更できてしまうので最終的にどこでどういう変更が行われてどういう値になっているのか、がわかりにくくなりそうな気はする。


まとめ

MLの議論では他の方法も紹介されていたし他にも方法はあると思うので上記4つから選ばないといけないわけではない。結局それぞれ長所短所があるので、それらを把握した上で使いどころを考えるしかないかな、と。ただグローバル汚染は問題外だと思うのでよっぽどのことがない限り使うべきではないでしょう。

個人的には3. の方法が好きなので自分はこの方式でやっていくつもり。

*1:追記: 知らなかったけどNode v0.6以降はjsonファイルrequireで読み込めるそうで… var config = require('./config');だけで良い

*2:追記: もしくはglobalオブジェクトを使って明示的にglobal.config = ...と書く