taiyoh's memorandum

@ttaiyoh が、技術ネタで気づいたことを書き溜めておきます。

ミサワのページから画像をブッコ抜きつつmarkdownのテキストも出力するchrome拡張書いた

taiyoh/misawa-markdown · GitHub
やっぱり使いたい画像で即座にミサワ画像使いたいよねー、というのと、
chromeの拡張書いたことなかったので、試しにやってみよう、ということで作ってみました。

f:id:sun-basix:20121009212020p:plain
↑使用イメージ

ストアとか載せるの面倒臭いし、多分もっと色々できることある気がするので、
もし追加機能あれば勝手にforkして使ってください。
使い勝手云々とかも色々ありそうだけど、当初の目的を果たしたのでもう飽きた。
JSのコード汚いのも分かるけど、もういいや><

f:id:sun-basix:20121009212050p:plain
chrome://chrome/extensions/ からデベロッパーモードを有効にして、
git cloneしたディレクトリをパッケージ化のところで指定すれば、
ビルドしてくれますので、あとはドラッグ&ドロップでインストールすれば使えます。

node.jsでtwitterのstatuses/update_with_mediaに対応

var Twitter = require('twitter')
  , request = require('request')
  , fs = require('fs');

Twitter.prototype.updateStatusWithMedia = function(text, params, callback) {
    var self = this
      , oauth = this.oauth
      , form, orderedParameters, r
      , url = 'http://upload.twitter.com/1/statuses/update_with_media.json';
    if (typeof params === 'function') {
        callback = params;
        params = {};
    }

    orderedParameters= oauth._prepareParameters(
        self.options.access_token_key,
        self.options.access_token_secret,
        'POST',
        url
    );
   
    r = request.post(url, callback)
    r.setHeaders({authorization: oauth._buildAuthorizationHeaders(orderedParameters)});
    form = r.form()

     form.append('status', text);
     for (i in params) {
         if (i == 'media[]') {
             form.append(i, fs.createReadStream(params[i]))
         }
         else {
             form.append(i, params[i]);
         }
     }

    return this;
};

var msg = 'API test',
    opt = {'media[]': __dirname+'/path/to/image'};

twit = new Twitter({
    consumer_key        : 'CONSUMER_KEY',
    consumer_secret     : 'CONSUMER_SECRET',
    access_token_key    : 'ACCESS_TOKEN_KEY',
    access_token_secret : 'ACCESS_TOKEN_SECRET',
});
twit.updateStatusWithMedia(msg, opt, function(err, res) {
    if (err) {
        console.log("twitter post error:\n", err);
    }
    else {
        res.setEncoding('utf8');
        var data = JSON.parse(res.body);
        console.log("twitter post success:\n", data);
    }
});

oauthライブラリの_performSecureRequestメソッドでやっているauthorizationヘッダの追加処理を入れて、form-dataライブラリを使ってアップすればOKだった。これなら、boundaryとかmime_typeを一切見る必要なく処理が書ける。

express.jsにgettext系の_関数を仕込む

引き続きnode.js系tips。
node-localizeというモジュールでの言語の管理がちょっとお手軽だったので、これとexpressを組み合わせるようにしてみた。

// express-localize.js
var Localize = require('localize');

module.exports = function(path, defaultLocale) {
    var locale, defaultLocale = defaultLocale || 'en';
    locale = new Localize(path, {}, defaultLocale);

    function _() {
        return locale.translate.apply(locale, arguments);
    }

    return function(req, res, next) {
        var render = res.render
          , lang   = req.query.lang || req.body.lang || req.session._lang || defaultLocale;
        req.session._lang = lang;
        locale.setLocale(lang);

        res.render = newRender;

        next();

        function newRender(template, stash) {
            stash._ = _;
            render.call(res, template, stash);
        }
    };
};

あとはapp.js内で

var expressLocalize = require('./lib/express-localize');
app.configure(function(){
  app.use(expressLocalize(__dirname + '/locale', 'ja'));
});

すれば使えるようになる。

node.jsでのクイックハック

jquery-deferredを使ったなんちゃってトランザクション
まだnode-mysqlでの使用しか想定してないので。

var $ = require('jquery-deferred');

function transaction(conn, callback) {
    var d = $.Deferred();
    d.done(function() {
        conn.query('COMMIT');
    }).fail(function() {
        conn.query('ROLLBACK');
    });
    conn.query('START TRANSACTION', function() {
        callback.apply({
            rollback: function() { d.reject.apply(d, arguments);  },
            commit  : function() { d.resolve.apply(d, arguments); }
        });
    });      
    return d.promise();
}

exports.transaction = transaction;

こんな感じでコードを書く

util.transaction(conn, function() {
    try {
        conn.query(SOME_SQL, function(err, results) {
            if (err) throw new Error;
        });
        this.commit();
    }
    catch(e) {
        this.rollback();
    }
}).done(function() {
    console.log("transaction success");
}).fail(function() {
    console.log("transaction fail");
});

有限オートマトンの習作として

PerlFSM::Simpleというモジュールを書いてみた。

#!perl -w
use strict;
use warnings;
use FSM::Simple;

my $count = 0;

my $fsm = FSM::Simple->new({
    rules => {
        init => sub {
            my $state = shift;
            if ($count < 20) {
                $state->next('add');
            }
            else {
                $state->next('end');
            }
        },
        add => sub {
            ++$count;
            shift->next('init');
        }
    }
});

$fsm->register(end => sub { $count *= 5; });

$fsm->run;

print "${count}\n"; # => 100

Perl Automata
ここを参考にしながら書きました。
ソースはこっちにあります。
taiyoh/p5-FSM-Simple · GitHub

(追記:9/3)
同僚からtransaction guardはちゃんと実装した方がいいと言われたので、
インターフェイスを大幅変更。
contextオブジェクトを内部で持つようにして、それで値を取り回すように。
あとはon_entry/on_exit/on_transactionも入れた。
stateのコードとtransaction guardを分けてみたらFSA::Rulesに近くなった。

use FSM::Simple;

my $fsm = FSM::Simple->new({
    on_enter      => sub {
        my $context = shift;
        $context->{count} = 0;
        $context->{str}   = "foo";
    },
    on_transition => sub { shift->{str} .= "bar" },
    on_exit       => sub { shift->{str} .= "baz" }
});

$fsm->register(init => sub {}, [
    add => sub { shift->{count} < 20 },
    end => sub { shift->{count} >= 20 }
]);

$fsm->register(add => sub { ++shift->{count} }, [
    init => 1
]);

$fsm->register(end => sub { shift->{count} *= 5 });

$fsm->run;

print $fsm->context->{count}, # => 100

Sub::Deferredなんてモジュール書いてみた

 最近、全然ブログ書いてないな、と思ったのですが、よくよく考えると、今年に入ってコード全然書いてなかったわけで、こりゃちょっといかん、と思いまして。
 リハビリついでに、jQuery1.7の$.Deferredの機能をなるべくほぼそのままPerlに移植してみました。完全じゃないけど。
 → taiyoh/p5-Sub-Deferred · GitHub
 これを使うと、

#!perl

use strict;
use warnings;
use utf8;

use FindBin;
use Perl6::Say;

use Sub::Deferred;
use AnyEvent;

my $cv = AE::cv;
my @defs = map { Sub::Deferred->new } 1 .. 3;
my $when = Sub::Deferred->when(@defs);
my $foo = 2;
my @timers = map {
    my $def = $_;
    my $sec = int(rand 5) + 1;
    AE::timer $sec, 0, sub {
        say "$sec sec after";
        $foo *= 2;
        $def->resolve;
    };
} @defs;
$when->done(sub {
    $foo *= 3;
    $cv->send($foo);
});

say "result: ". $cv->recv; # => 48

 こんな感じで、「あれ、なんかjQueryで見たことある書き方」みたいなことができるようになります。CPANにはObject::Deferredとかいうモジュールもあるのですが、機能が少ない上にそれのためにMoose使うってなんじゃ、と思ったもので。
 一応依存モジュールもなく Pure Perlで、5.8.8以降で動くはずですが、真っ当な方はData::Monadを応援するか、Coroを使いこなす方がいいと思います!

Test_myqld.phpを更新しました

以前のエントリ→ "Test_mysqld", ported from cpan's Test::mysqld - taiyoh独言
taiyoh/Test_mysqld-php · GitHub
このモジュールを作ってまもなく、id:memememomoさんに色々直していただいたのですが、

例外でプログラムが終了しないように気をつけるようにするのはそうなんですが、テスト中のプログラムではやっぱ例外で終了しちゃうケースもある気がするのです。そのたびに、mysqldが残っちゃうのはやだなーと思うのです。

確実にmysqldを終了させる方法があるのでしょうか?

[php]Test_mysqldというものができたけど、まだ問題があって悩んでいる件 - メメメモモ

というのを今更ながらに見つけまして><
あんまりいい解決策じゃないかもしれませんが、子プロセスのPIDを定数としてキープしておいて、register_shutdown_functionでサクッと該当するPIDを落とすようにしてみました。

<?php

// モジュール読み込み
include_once('Test_mysqld.php');

// 設定
$my_cnf = array('skip-networking' => '');
$opts   = array();

// mysqld起動
$mysqld = new Test_mysqld($my_cnf, $opts);

// 例外で終わる
throw new Exception();

上記コードで子プロセスがなくなっていることは確認しました。微妙に終わり方はアレなのですが。
あと、気づいたらテストコードがセグフォで落ちるようになってた。。。上のコードは問題ないのですが。。。

(追記)
PDOでなんか起きてた。うむむ。。。