RubyのgsubをCoffeeScriptで

Rubyのgsubはblockを受け取ってマッチするたびにブロック内の処理内容を変えられるので便利。

irb(main):002:0> i = 0
=> 0
irb(main):003:0> 'a-a-a-a'.gsub(/a/){i += 1}
=> "1-2-3-4"

Node.jsでも同じようなことがやりたかったのでRuby's String gsub in Javascriptをちょっと変更して配列を受け取れるようにしてみた。

gsub = (source, pattern, replacements) ->
  unless pattern? and replacements?
    return source

  result = ''
  while source.length > 0
    if (match = source.match(pattern))
      result += source.slice(0, match.index)
      result += replacements.shift()
      source = source.slice(match.index + match[0].length)
    else
      result += source
      source = ''
   result

console.log gsub('a-a-a-a', /a/, [1..4])

Node.jsのspawnをつかってcoffescriptでps ax | grep sshを実行させるサンプル

spawnとexecの違いがわからんのでちょっと調べた。

Difference between spawn and exec of Node.js child_process

spawnはストリームをexecはバッファーを返す

で、Node.jsのドキュメントを読んでて spawnをつかった 'ps ax | grep ssh' の例が書いてあったので、これをcoffeescriptで書いた。

ターミナルで普通に動くのでvowsでテストしたら、callbackが返ってくる前に変なオブジェクトが返ってきてなんじゃこりゃ?と。

coffeescriptをjavascriptにコンパイルして読んだら、変なとこにreturnが挿入されてたのであちこちにreturnを入れて勝手にreturnされないようにした。

psax.coffee

util = require('util')
spawn = require('child_process').spawn

psax = (callback) ->
  ps    = spawn('ps', ['ax'])
  grep  = spawn('grep', ['ssh'])

  d = ""

  ps.stdout.on 'data', (data) -> grep.stdin.write(data); return
  ps.stderr.on 'data', (data) -> console.log('ps stderr: ' + data); return
  ps.on 'exit', (code) ->
    console.log('ps process exited with code ' + code) if code isnt 0
    grep.stdin.end(); return

  grep.stdout.on 'data', (data) -> d += data; return
  grep.stderr.on 'data', (data) -> console.log('grep stderr: ' + data); return
  grep.on 'exit', (code) ->
    console.log('grep process exited with code ' + code) if code isnt 0
    return

  grep.stdout.on 'end', ->
    callback(null, d); return

  return

exports.psax = psax

returnを入れて最後に評価した式がかえらなくてしなくていいような記法は用意されてないんだろうか? >>なんかモナドっぽくて素敵じゃないか。

vowsのテスト。宣言(文字列)の書き方のお作法がまだよくわかってない。should beとかshoud haveはわかるんだけど、whenとかAfterとか使い分けってどうなってんの?

そういうお作法が書いてあるサイトないかなぁ。

vows = require('vows')
assert = require('assert')

sp = require('./psax')

psax = sp.psax

vows.describe('Psax').addBatch({
  'A psax': {
    topic: -> psax
    'should be a function': (px) ->
      assert.equal(typeof px, "function")
    'After psax function called': {
      topic: (psax) -> psax(this.callback)
      'callback should return a result': (err,result) ->
        assert.isString(result)
    }
  }
}).export(module)

ProductName テスト駆動JavaScript
Christian Johansen
アスキー・メディアワークス / 3780円 ( 2011-11-25 )


Node.jsでYahoo!日本語形態素解析APIを使う

紆余曲折の末Yahoo! APIに落ち着いた。

http = require('http')
querystring = require('querystring')

qst = querystring.stringify({
    appid: '################',
    sentence: 'もももすももももものうち',
    results: 'ma'
})

options = {
  host: 'jlp.yahooapis.jp',
  port: 80,
  path: '/MAService/V1/parse?' + qst
};

http.get(options,
  (res) ->
    body = ""
    res.on('data', (data) -> body += data)
    res.on('end', -> console.log(body))
  ).on('error',
  (e) -> console.log("Got error: " + e.message)
)

追記12.02.22

結果がXMLで返ってくるのでパースしないといけないが、node.js で libxml を使うにはどのライブラリをつかうべきかというエントリを参考にしてlibxml-to-jsを選択。

http = require('http')
querystring = require('querystring')
parser = require('libxml-to-js')

qst = querystring.stringify({
    appid: '######',
    sentence: 'あたしはプログラムの女の子です',
    results: 'ma'
})

options = {
  host: 'jlp.yahooapis.jp',
  port: 80,
  path: '/MAService/V1/parse?' + qst
};

http.get(options,
  (res) ->
    body = ""
    res.on('data', (data) -> body += data)
    res.on('end', -> parser(body, (err, result) -> console.log(result.ma_result.word_list)))
  ).on('error',
  (e) -> console.log("Got error: " + e.message)
)

結果

{ word: 
   [ { surface: 'あたし', reading: 'あたし', pos: '名詞' },
     { surface: 'は', reading: 'は', pos: '助詞' },
     { surface: 'プログラム', reading: 'ぷろぐらむ', pos: '名詞' },
     { surface: 'の', reading: 'の', pos: '助詞' },
     { surface: '女の子', reading: 'おんなのこ', pos: '名詞' },
     { surface: 'です', reading: 'です', pos: '助動詞' } ] }

Node.jsで外部プロセスを実行する

とりあえず先に進みたかったので、mecabコマンドを叩くことにした。

var exec = require('child_process').exec;

exec('echo "もももすももももものうち" | mecab', 
     function(err, stdout){ console.log(stdout); }
    );

というわけで7章をやっている。

ProductName 恋するプログラム―Rubyでつくる人工無脳
秋山 智俊
毎日コミュニケーションズ / ?円 ( 2005-04 )


追記 12.02.21

子プロセスなのでコレじゃうまくいかない。コレで解決するか、Yahooの形態素解析APIを使うか。

pyVows - Vows for Python

最近CoffeeScriptで書くときにはvowsを使っていてるが、Emacsを左右に分割して、左にコード、右にテストのBDDが快適すぎて癖になっている。

BDD Emacs

Pythonで書くときはnoseを使っているのだけど、最近pyVowsを見つけたので、これを使ってみようかなとドキュメントを読んでみた。

pyvowsのverbosityオプションがvを増やすということに気付かかなくて、詳しい表示どうすんの?ってなった。デフォルトのvvくらいだと頑張った気がしないのでvvvくらいがいいかも。

アサーションは種類が色々あるし、カスタムのアサーションもVows.create_assertionsデコレータで簡単にかけるし、構造が一致するかとかSMILESが正しいかとかのchemoinformatics用のカスタムアサーションがかけそうなので生産性が上がっていい。

node-mecabを使おうとしたらSegmentation faultした

恋するプログラムをCoffeeScriptで書いているんだけど、やっと7章まで進んだ。この章ではNode.jsでmecabが使う必要があるので調べてみたらnode-mecabっていうのを見つけた。

コンパイルは出来たんだけど、実際にrequireしようとしたらSegmentation faultした(Mac OSX(10.6)+Node6.8)

デバッグどうすんのかなぁ、あとnvmで入れたnodeってdebugオプションついてんだっけと見よう見真似で実行してみたがさっぱりわからん。

$ gdb --args /Users/kzfm/.nvm/v0.6.8/bin/node t.js
(gdb) run
Starting program: /Users/kzfm/.nvm/v0.6.8/bin/node t.js
...
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: 13 at address: 0x0000000000000000
0x0000000100de8cd1 in btrie_close ()
(gdb) backtrace
#0  0x0000000100de8cd1 in btrie_close ()
#1  0x0000000100de5fbc in MeCab::Dictionary::close ()
#2  0x0000000100dded6a in MeCab::Dictionary::~Dictionary ()
#3  0x0000000100ddb3c2 in MeCab::TokenizerImpl<mecab_node_t, mecab_path_t>::~TokenizerImpl ()
#4  0x0000000100de5844 in MeCab::TaggerImpl::~TaggerImpl ()
#5  0x0000000100de435a in MeCab::createTagger ()
#6  0x00000001007db588 in Tagger::New (args=@0x7fff5fbfddc0) at ../mecab.cc:17

t.js

var mecab = require('mecab')
console.log( mecab.parse("こちら葛飾区亀有公園前派出所"));

いい機会なのでデバッグのやり方を学んでおこう。

ProductName Debug Hacks -デバッグを極めるテクニック&ツール
吉岡 弘隆
オライリージャパン / 3360円 ( 2009-04-27 )


追記 12.02.14

MeCab::createTaggerでこけてるっぽいが、理由がよくわからんlinuxへのインストールはうまくいくのでとりあえずこっちで凌ぐか、別プロセスで呼び出すようにしようかね。

TodoっていうNode.js製のtodo管理ツール

CLIで良い感じ

$ npm install -g todo

でインストールできる。

node_todo

help:   
help:   todo - Todos in the CLI like what.
help:   
help:   Usage:
help:   
help:          todo Go shopping. - Adds new item.
help:          todo ls.          - Lists not finished items.
help:          todo ls --all     - Lists all items.
help:          todo rm 1         - Removes #1 item.
help:          todo check 1      - Marks #1 item as done.
help:          todo undo 1       - Marks #1 item as not done yet.
help:          todo clear        - Clears the whole list.
help:          todo version      - Lib version.
help:   
help:   Author: Veselin Todorov <hi@vesln.com>
help:   
help:

Node.jsのテスト系のライブラリなんかもそうなんだけど、ターミナルに色付きで表示されると見やすくていいですね。オプションもわかりやすいので、端末にちょっと入れておけばtodo管理ができてイイかも。普段はGmailのタスクを使っているので同期できると嬉しいんだけどねー

と思いながら調べたらGoogle Tasks APIってのがあったけど、これでいいんだろうか?あとで試してみよう。

テスト駆動Javascriptでテスト駆動開発を学ぶ

中級者向けのJavascript開発の本として読んでもいいし、テスト駆動開発の入門書として読んでも良い感じ。名著だと思う。

ProductName テスト駆動JavaScript
Christian Johansen
アスキー・メディアワークス / 3780円 ( 2011-11-25 )


自分はプログラマーの集団的な組織に属したことがないため、開発の色々(特にペアプロ)なイメージが良くわからんし、TDDもイマイチ理解していない感があったので、本書のように丁寧に書いてある本はありがたかったりする。

第三部の「Javascriptテスト駆動開発の実際」のパートが面白いが、その中でも特にNode.jsによるサーバーサイドのテスト(14章)とクライアントサイドのテスト(15章)が非常に良かった。この2章だけで90ページくらいあるし、じっくり写経したら2日を費やしてしまったが、得るものは多かった。特に実際のコードとテストコードが絡み遭いながら完成に向かって進んでいく感を味わえたのはなかなか素晴らしい体験だった。

ちなみに14章ではNodeunitを使ってたけど、実際に自分が使うならVowsかなぁと(CoffeeScriptでも使えるしね)

エディタに関しては、Emacsの場合、縦分割して左にコード、右にテスト出して交互に切り替えながら進めていくのが調子良かった。

一部がテスト駆動開発の一般論で、二部がJavascriptの概論だけど、第4章の「学ぶためのテスト」というパートが面白かったかな。他の人が書いたライブラリを観察するために、テストの形で評価した内容を記述しておくという方法論は斬新だった。 まぁ、よく考えてみると、自分も新しいモジュールを理解したい場合はまずテストを眺めて、よく分からない場合は、対応する部分のソースコードを読んでふるまいを理解したりするので、結局そういったやりかたの形を変えて、積極的に観察とその記録を取っていく手法って考えればいいのかと思った。

おまけ

Node.jsでAmazon Product Advertising APIをさわる

静岡デベロッパーズつくる会#4お疲れ様でした。最終的に5人でしたが、いままでで一番黙々度が高かったような。Node.jsの情報交換も出来てなかなか有意義だったですね。

昼はカフェプレアーテでつけナポリタン。スープの酸味がマイルドでなかなか美味かった。

1326098364

個人的にちょっとつくりたいものがあったので、ntwitterで遊びつつ、ExpressにoAuth認証を実装して、Node.jsからAmazon Product Advertising APIをさわるとこまでやりました。

var util = require('util'),
    OperationHelper = require('apac').OperationHelper;

var opHelper = new OperationHelper({
    awsId:     '####',
    awsSecret: '####',
    assocId:   '####-22'
});

opHelper.execute('ItemSearch', {
    'SearchIndex': 'Books',
    'Keywords': 'Node.js',
    'ResponseGroup': 'ItemAttributes,Offers'
}, function(error, results) {
    if (error) { util.print('Error: ' + error + "\n"); }
    for (index in results.Items.Item) {
    util.print( results.Items.Item[index].ItemAttributes.Title + "\n"); 
    }
});

実行結果

Node Web Development
Node: Up and Running: Scalable Server-Side Code with JavaScript
Getting Started with GEO, CouchDB, and Node.js
Hands-on Node.js
Programming Node.js
The Node Beginner Book
JavaScript Patterns
Supercharged JavaScript Graphics: with HTML5 canvas, jQuery, and More
Eloquent JavaScript: A Modern Introduction to Programming
What Is Node?

あとはExpressに取り込んで、Initializrのテンプレートをかぶせて早めに公開したい

それから今月末にもHTML5読書会があるので興味があれば参加すると良いです。Canvasタグの章をやる予定

Node.jsでtwitterのストリームをgrowlで通知する(改)

静岡デベロッパーズつくる会#4やってます。

前に作ったやつがうごかなくなってるのでリハビリがてらなおしてみた。

twitter-nodeってのが新しくなってntwitterになっているのとnode-growlのAPIが変わってた。

#!/usr/bin/env node

var twitter = require('ntwitter');
var growl = require('growl');

var twit = new twitter({
               consumer_key: '####',
               consumer_secret: '####',
               access_token_key: '####',
               access_token_secret: '####'
               });

twit.stream('statuses/filter', {track:['#shizudev','#shizuru']}, function(stream) {
        stream.on('data', function (data) {
                  growl("@" + data.user.screen_name + ": " + data.text);
              });
        });

ProductName Node: Up and Running: Scalable Server-Side Code With Javascript
Tom Hughes-croucher
Oreilly & Associates Inc / 2877円 ( 2011-09-22 )