2008/03/31


コンパイルは通るだろうけど、ちょっと本文から直してほしいですね。影響力のある場所でしかも「推薦する」なんてリンクが付いている状態で放置は間違いを広めてしまうよ。
C/C++のポインタの機能--参照渡しのような処理 - builder by ZDNet Japan
で、Electric Fenceの紹介につなげる記事にしようと思ったのですが
electric-fence-win32 - Google Code
Electric Fenceのwin32版なんてものを見つけてしまった。
てっきりUNIX版と同様、リンクすれば動くと思って色々試したけど、どうやらそうじゃないみたい。
//#include <efence.h>
#include <stdio.h>
int main( void ) {
    char *a = (char*)malloc(12);
    a[ 0] = 'H';
    a[ 1] = 'e';
    a[ 2] = 'l';
    a[ 3] = 'l';
    a[ 4] = 'o';
    a[ 5] = ',';
    a[ 6] = ' ';
    a[ 7] = 'W';
    a[ 8] = 'o';
    a[ 9] = 'r';
    a[10] = 'l';
    a[11] = 'd';
    a[12] = '\0';
    return 0;
}
オーバーランを検知してくれなかった。README.win32によると

Since you need to modify your own project anyway, simply add efence.c, page-win32.c, and print.c to your project.

と書いてありました。てっきりスタートアップルーチンを入れ替えてくれてくれる物かと思って少しだけ期待してしまいました。
mingw32ならば Index: Makefile
===================================================================
--- Makefile    (revision 7)
+++ Makefile    (working copy)
@@ -9,7 +9,7 @@
 MAN_INSTALL_DIR= /usr/man/man3
 
 PACKAGE_SOURCE= README libefence.3 Makefile efence.h \
-   efence.c page.c print.c eftest.c tstheap.c CHANGES COPYING
+   efence.c page-win32.c print.c eftest.c tstheap.c CHANGES COPYING
 
 # Un-comment the following if you are running HP/UX.
 # CFLAGS= -Aa -g -D_HPUX_SOURCE -DPAGE_PROTECTION_VIOLATED_SIGNAL=SIGBUS
@@ -26,7 +26,7 @@
 # as well if using Sun's compiler, -static if using GCC.
 # CFLAGS= -g -Bstatic -DPAGE_PROTECTION_VIOLATED_SIGNAL=SIGBUS
 
-OBJECTS= efence.o page.o print.o
+OBJECTS= efence.o page-win32.o print.o
 
 all:   libefence.a tstheap eftest
    @ echo
@@ -63,7 +63,7 @@
 
 tstheap: libefence.a tstheap.o
    - rm -f tstheap
-   $(CC) $(CFLAGS) tstheap.o libefence.a -o tstheap -lpthread
+   $(CC) $(CFLAGS) tstheap.o libefence.a -o tstheap -lpthreadGC2
 
 eftest: libefence.a eftest.o
    - rm -f eftest
こんな風に修正して mingw32-make CC=gcc libefence.a でlibefence.aが出来上がり、上のソースの冒頭の「//」を外して #include <efence.h>
#include <stdio.h>
int main( void ) {
    char *a = (char*)malloc(12);
    a[ 0] = 'H';
    a[ 1] = 'e';
    a[ 2] = 'l';
    a[ 3] = 'l';
    a[ 4] = 'o';
    a[ 5] = ',';
    a[ 6] = ' ';
    a[ 7] = 'W';
    a[ 8] = 'o';
    a[ 9] = 'r';
    a[10] = 'l';
    a[11] = 'd';
    a[12] = '\0';
    return 0;
}
あぶないコードに修正した後 gcc -g -o dame.exe dame.c -lefence とすれば、efenceビルドされたdame.exeが出来上がり実行すると、正しくクラッシュしてくれる。gdbで確認すれば
C:¥temp¥electric-fence-win32>gdb dame.exe
GNU gdb 6.3
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i686-pc-mingw32"...
(gdb) run
Starting program: C:¥temp¥electric-fence-win32/dame.exe
...
Program received signal SIGSEGV, Segmentation fault.
0x00401396 in main () at dame.c:17
17              a[12] = '\0';
(gdb)
とクラッシュ場所も分かると。でもUNIX版みたくソースは修正したくないなぁ。
あとmallocでなく char a[12];
とした場合にクラッシュしてくれないのならば、威力半減ってところか。
Posted at by




嘘。難読化っていうか、これバグだな...
Vim script line continuation? - vim_dev | Google グループ

Well, it seems the new line continuation can be placed weirdly, which will make the vim script syntax file even messy when considering the line continuation. Any comments?

検証コード e
\c
\h
\o
\ "
\
\
\P
\l
\a
\!"

それPla!
Posted at by



2008/03/28


またまた知らなかった。勉強不足。
例えば <div class="foo">
    <p>
        <span class="test1">title1</span>
    </p>
    <ul>
        <li>list1</li>
        <li>list2</li>
        <li>list3</li>
    </ul>
</div>

<div class="foo">
    <div>
        <span class="test1">title2</span>
    </div>
    <ul>
        <li>list1</li>
        <li>list2</li>
        <li>list3</li>
    </ul>
</div>

<div class="foo">
    <div>
        <span class="test2">title3</span>
    </div>
    <ul>
        <li>list1</li>
        <li>list2</li>
        <li>list3</li>
    </ul>
</div>
こんなHTMLで
  • class属性"foo"を持つdiv
  • その孫にclass属性"test1"を持つspan
  • 上の条件下にある上記divの孫にあるli
を検索したい場合 //div[@class="foo"]//span[@class="test1"]/../..//li
こう書いてたんですが、これだとliの階層が深い場合に".."を書く個数が限定されてしまっていました。
で、今日知ったのですが、expr部には「@xxx="yyy"」といったexprだけでなくパスも書けるのを知った。 //div[@class="foo" and .//span[@class="test1"]]//li
これだとcurrent-contextとして
  • class属性"foo"を持つdiv
を保ったままliを検索出来る訳。勉強不足だな。
上の例だとtitle1の下のliと、title2の下のliがマッチする。

ところで皆さんはXPathをテストしたい場合、何を使ってますか?
私はjAutoPagerizeを使っています。jAutoPagerizeはcho45氏作のAutoPagerizeクローンで、私は本家を使わずこちらを使っています。
なぜこれを使っているかというと、jAutoPagerize本来の機能も良いのですがXPathGeneratorが付いているからです。
jAutoPagerizeをインストールすると
jautopagerize-icon
というアイコンが画面右上に出るのですが、これをクリックすると
jautopagerize-xpathgenerator
といった形でXPathの入力画面が現れます。ここにXPathを書いて"TAB"キー等でフォーカスを外すと
jautopagerize-xpathresult
と赤くハイライトされるのです。視覚的にも分かりやすいですね。他直接ノードからXPathを取得するInspectボタンもクラス名を知るのに使えます。
また、AutoPagerize対応でないページでアイコンが出ていなくても
XPathGenerator
こんなブックマークレットさえ用意しておけば、何時でもXPathGeneratorを表示出来るようになります。

XPathGeneratorかわいいよXPathGenerator
Posted at by




追記
os0xさんが、マルチプラットフォームで動くしかもブックマークレットで起動出来るAutoPagerize for IE6公開されておられます。
「できた!」とほぼ同時にtwitterに書き込んでたのが笑った。
確かに、iframeを使えばXHRを使った場合と違ってresponseTextからcreateContextualFragment等で苦労する事無く、同じドメイン内のiframeだからDOM操作が出来ると...
諦め所がXPath(responseTextからcreateDocumentが出来ないという意味で)でなく、XHRを諦めるべきだったかも。

でけれた。
AutoPagerizeとか、oAutoPagerizeとか、jAutoPagerizeとかをベースに改造しようかと思ったけど、結局XPathでつまづいた。
AutoPagerizeを実現するには、カレントのdocumentとは別のdocumentに読み込んだHTMLを入れてページ部を抽出する必要があるんだけど、IE6ではカレントdocumentにappencChildしないとXPathセレクタが動かない(amachangのJavaScript-XPathで検証)。また、createRange/createContextualFragmentも動かないだろうし、どちみち駄目。
つまり、XPathは実現出来ない事になる。
諦めて、mala氏が作ったオリジナル、「GoogleAutoPager」をベースに作り直した。
で、最初はGoogleだけでいいかと思ったけど、欲が出てtwitterやtumblr、d.hatena.ne.jp、b.hatena.ne.jpでも動かせるようにした。
iAutoPagerize
アイコンとかはありません。ダブルクリックで起動です。
但し、セレクタとしてはCSSセレクタを使ってるので、特殊な調べ方は出来ない。しかもIE6なのでCSSのバージョンが低い。

セレクタライブラリとしてはjQueryを使用しています。動かす為にはTrixieが必要です。Trixieをインストール後
C:¥Program Files¥Bhelpuri¥Trixie¥Scripts¥
に以下のリンクにあるiAutoPagerize.user.jsを放り込めばインストールは完了。現状、SITEINFOはベタ打ちです。今後SITEINFOサーバを用意するつもりもありません。ただ、codereposに置いておくので誰でも修正出来る様にしておきます。

ネーミングは、「OperaのがoAutoPagerize」なら「IEはiAutoPagerize?」くらいの思考能力で付けた名前です。

よろしかったらどうぞ。
iAutoPagerize

Posted at by



2008/03/26


なんか久々。
/lang/perl/plagger/lib/Plagger/Plugin/Publish/Diigo.pm - CodeRepos::Share - Trac
www.diigo.com...ブックマークレットから実行出来る専用ポスト画面がなかなかカッコイイ。
今回はこのブックマークレットのhackで作った。

これで、私が同期しているソーシャルブックマークは
  • Publish::Delicious
  • Publish::LivedoorClip
  • Publish::Buzzurl
  • Publish::LivedoorCilp
  • Publish::Buzzurl
  • Publish::GooBookmark
  • Publish::NiftyClip
  • Publish::Pookmark
  • Publish::YahooBookmark
  • Publish::FC2Bookmark
  • Publish::BlueDot という名の Pubilsh::Delicious
  • Publish::Magnolia という名の Publish::Delicious
  • Publish::Diigo
となった。
以下、今後変わっていくかもしれないけどコード。

続きを読む...

Posted at by




昨日kuさんが頑張って探してたのはなんだったんだろう...苦笑
朝起きたらAPIが出来てました。確か昨日「/api」にアクセスしても無かったのになぁ。
FriendFeed Blog: FriendFeed API: Extend and improve FriendFeed

We are very excited to announce the launch of the FriendFeed API, which enables developers to interact with the FriendFeed site programmatically. It's designed to make it possible for anyone to improve FriendFeed or integrate FriendFeed into other applications. You can develop a FriendFeed interface for a mobile phone, build a FriendFeed widget for your blog, or develop an application that makes it easy to post photos to your feed from your iPhone.

さっそく使ってみました。APIはJSON、XML、RSS、ATOMで提供されており、クライアントライブラリとしてPHPとpythonのライブラリが用意されています。
つまり、XMLフォーマットを使っているgtktwitterを改造してgtkfriendfeedなんて事も出来る訳ですね。
ApiDocumentation - friendfeed-api - Google Code
friendfeed-api - Google Code
twitterと同じくprivateでなければ認証無しでアクセス出来ます。
認証が必要な場合は、「remotekey」と呼ばれるアプリケーションキーを取得する必要があります。remotekeyは
FriendFeed - Remote Key
で取得出来ます。認証方法はBasic認証です。今日は認証を使わない例ですが、javascriptからjsonで私と、kuさんと、otsuneさんのフィードをミックス表示する例をサンプルとして上げておきます。
まずコード
<style type="text/css"><!--
#mattn_friendfeed {
    margin: 1em;
    font-family: Verdana;
    width: 400px;
}
#mattn_friendfeed a {
    color: blue;
}
.friendfeed-line {
    padding-bottom: 1em;
}
.friendfeed-link {
    font-size: small;
}
--></style>
<script type="text/javascript"><!--
function mattn_friendfeed_callback(data) {
    var container = document.createElement('div');
    for(var n = 0; n < data.entries.length; n++) {
        var user = document.createElement('a');
        var link = document.createElement('a');
        var line = document.createElement('div');
        var icon = document.createElement('img');
        var br = document.createElement('br');
        user.href = data.entries[n].user.profileUrl;
        link.className = 'friendfeed-link';
        link.href = data.entries[n].link;
        link.appendChild(document.createTextNode(link.href));
        icon.src = user.href + '/picture?size=small';
        icon.setAttribute('align', 'left');
        user.appendChild(icon);
        br.setAttribute('clear', 'all');
        line.className = 'friendfeed-line';
        line.appendChild(user);
        line.appendChild(document.createTextNode(' ' + data.entries[n].title + ' '));
        line.appendChild(document.createElement('br'));
        line.appendChild(link);
        line.appendChild(br);
        container.appendChild(line);
    }
    document.getElementById('mattn_friendfeed').innerHTML = container.innerHTML;
}
function show_mattn_friendfeed() {
    document.getElementById('mattn_friendfeed').innerHTML = '<img src="http://mattn.kaoriya.net/images/ajax-loader.gif">';
    var s = document.createElement('script');
    s.charset = 'utf-8';
    s.src = 'http://friendfeed.com/api/feed/user?nickname=mattn,otsune,ku0522&callback=mattn_friendfeed_callback';
    document.body.appendChild(s);
}
--></script>
<input type="button" value="mattnのfriendfeedエントリを表示" onclick="show_mattn_friendfeed();" />
<br />
<em>mattnと、ku0522さんと、otsuneさんのmix表示</em><br />
<div id="mattn_friendfeed"></div>

そして実行例

続きを読む...

Posted at by



2008/03/25


最近私もFirefox3を使い始めたのですが、Operator 0.9.1と組み合わせると、microformatsが「hCard」と「adr」だけになってしまい、少し困ってました。 何かのバグにしてはエラーコンソールに何も出ないし悩んでいたのですが、今日ソースを見て納得。
Firefox3からは、ブラウザ自身でmicroformatsを解析出来る仕組みが入ったのですが、その対応がOperatorに中途半端に入っている様です。
chromeからmicroformatsを扱う為には Components.utils.import("resource://gre/modules/Microformats.js");
というコードが必要なのですが、この処理がコメントアウトされていました。
実際には $(FIREFOX_PROFILE)/extensions/{95C9A302-8557-4052-91B7-2BB6BA33C885}/chrome/operator.jar に含まれる content/operator.js が原因で、パッチで表すならば以下の様な感じ。
--- chrome/content/operator.js.orig Wed Mar 19 11:44:26 2008
+++ chrome/content/operator.js  Tue Mar 25 19:11:01 2008
@@ -77,7 +77,7 @@
     /* Attempt to use the Microformats module if available (Firefox 3) */
     if (Components.utils.import) {
       try {
-//        Components.utils.import("resource:///modules/Microformats.js");
+        Components.utils.import("resource:///modules/Microformats.js");
       } catch (ex) {
         /* Unable to load system Microformats - use builtin */
       }
おしいなぁ...作者。もしくは開発時はバギーだったかな?
実際の作業としては「operator.jar」を解凍し上記パッチを当てた(もしくは手編集)後に再度zip(拡張子jar)化すればOK。見事以前までのmicroformat Operatorが帰ってきました。
相変わらずウチのサイトはmicroformatsだらけですが...
bigsky-microformats-20080325

ところでFirefox3で扱えるmicroformats、「Describing microformats in JavaScript - MDC」を見るとどうやら自前でmicroformatの定義が出来る様です。今後draftとして拡張されていくであろうmicroformatsへの配慮ですかね。せっかくmicrosummaryのOperatorプラグイン作ってたのに、こっちの方向で作り直しか?

で、さらにドキュメントの例を見ていると var adr_definition = {
  mfVersion: 0.8,
  mfObject: adr,
  className: "adr",
  properties: {
    "type" : {
      plural: true,
      types: ["work", "home", "pref", "postal", "dom", "intl", "parcel"]
    },
    "post-office-box" : {
    },
    "street-address" : {
      plural: true
    },
    "extended-address" : {
    },
    "locality" : {
    },
    "region" : {
    },
    "postal-code" : {
    },
    "country-name" : {
    }
  }
};
という記述が...。これOperatorのソースコードのままだね。ってことはOperatorのソースをFirefox3に持ってったのかな?
とにかく簡単に拡張出来そうな仕組みなので、しばらく追ってみる。
Posted at by




たしかにコレ、やばいっす。
ku spreadsheetとつながってるってやばいじゃんこれspreadsheetをバックエンドdbにしていろいろできるってことでしょ
ku's post on twitter
使い方次第では、結構強力な物になりそうな気がする。
ちなみに
F's Garage:iPhone SDKを読み解くのに必須! Google AJAX Language APIを使ったブックマークWidget作った。
Google AJAX Language APIは、document.writeで翻訳機能読み込みのscript要素コードを出力するようにできており、ブックマークレットには必須の遅延ロードができないようなので、なんだかいろいろ苦労しちゃいました。
多分以下の様にすれば行けるはず。(jsapiのクエリパラメータ「callback」と、loadの第三引数「callback」)
wikiも確かに管理しやすいかもしれないけど、Google SpreadSheetも可能性があると思う。
ちなみに今回作ったSITEINFOは簡単な物なので、「ネタフル」と「IDEA * IDEA」くらいしかない。
google-spreadsheets-siteinfo
あと、クエリのfrom区には「sheet1」とか使えるのが分かった。
以下、Google SpreadSheetからSITEINFOを読み込むサンプル
var siteinfo = [];
function handleQueryResponse(response) {
    var data = response.getDataTable();
    if (!data || response.isError()) {
        alert(response.getMessage() + ':' + response.getDetailedMessage());
        return;
    }
    for (var row = 0; row < data.getNumberOfRows(); row++) {
        siteinfo.push({
            'name'  : data.getFormattedValue(row, 0),
            'link'  : data.getFormattedValue(row, 1),
            'url'   : data.getFormattedValue(row, 2),
            'xpath' : data.getFormattedValue(row, 3),
            'base'  : data.getFormattedValue(row, 4)
        });
    }
    var html = '';
    for (var n = 0; n < siteinfo.length; n++) {
        html += '<b><a href="' + siteinfo[n].link + '">' + siteinfo[n].name + '</a></b><br />'
            html += '<blockquote' + '><' + 'pre>';
        html += '<b>url</b>:' + siteinfo[n].url + '<br />';
        html += '<b>xpath</b>:' + siteinfo[n].xpath + '<br />';
        html += '<b>base</b>:' + siteinfo[n].base + '<br />';
        html += '<' + '/pre></' + 'blockquote>';
    }
    document.getElementById('ExampleSITEINFO').innerHTML = html;
}

function querySITEINFO() {
    var query = new google.visualization.Query(
            "http://spreadsheets.google.com/tq?key=psdg8ZffuCtcsEuIuzzd1Mw&pub=1");
    query.setQuery("select A, B, C, D, E from sheet1 order by A desc");
    query.send(handleQueryResponse);
}

function jsapi_loaded() {
    google.load("visualization", "1", {"callback" : querySITEINFO});
}

function loadExampleSITEINFO() {
    var s = document.createElement('script');
    s.charset = 'utf-8';
    s.src = 'http://www.google.com/jsapi?callback=jsapi_loaded';
    document.body.appendChild(s);
}
とその実行結果。
IE6、Firefox、Safari3、Opera9.50bで動作確認。

続きを読む...

Posted at by




The Unofficial Blosxom User Group :: Using Blosxom as Feed Generator

Gavin Carr, one of the Blosxom developers, recently posted a simple yet somehow not obvious idea on his blog: He uses Blosxom to automatically generate feeds for software which hasn't feeds, in this case the network monitoring system Nagios, so that he get's all Nagios events delivered to his feed reader.

A similar yet local use of this idea is to let blosxom or a wrapper script be called by your feed reader directly. Liferea (GUI, GNOME) and Snownews (text-mode, ncurses) have this feature and there is already a big repository of plugins. The same way as those plugins are called, you can easily use blosxom as a plugin to those two feed readers so you don't need to care about how to generate RSS, blosxom does that for you. You just need to set the environment variable PATH_INFO to /index.rss before, e.g. by calling blosxom like this: env PATH_INFO=/index.rss blosxom.

That way I currently monitor the NVidia Unix Drivers Portal Page for changes, using this script, the libwww-perl (LWP), wdiff, a little bit of Perl glue and of course Blosxom. (In this case I use Debian's version of Blosxom which is able to have several configuration per installation by adding a -f flag for config files.)

blosxomの開発者の一人、Gavin Carr氏がblosxomを使って面白いアイデアを出しているようです。
Hackery :: Blosxom4Nagiosと題した記事に記されている中では、Nagiosというモニタリングシステムの結果をフィード化するにあたり、snownewsやLifereaといったクライアント向けフィードリーダのフィード読み込みコマンドを利用するという例が紹介されています。
実際には記事の中で紹介されている、changes2rss.plというperlスクリプト内からblosxomを実行し、結果をRSSとして出力するという代物です。
なかなか面白いアイデアですね。フィード読み込みコマンドをサポートしているフィードリーダでしか有効ではないですが
  • 情報収集
  • システム監視
を同じフィードリーダで扱えるというのは便利かもしれません。
Posted at by



2008/03/24


最近はオセロって言っちゃダメなんだっけ?
vim-reverse
コンピュータ対戦です。

続きを読む...

Posted at by



2008/03/21


C言語される方は見ておいた方が良いかもしれない。
ロベールのC++入門講座を読んで C++ を初歩の初歩から再入門するよ - 前編 - ひげぽん OSとか作っちゃうかMona-
[] 演算子は a[b] と b[a] はおなじ意味らしい。なんと!
それぞれ *(a + b) 、*(b + a) になるので等しいのですね。
うむ。しらんかった。確かに出力されるアセンブリも
c-code-1
c-code-2
となり、結果同じ操作なのだけれど、まさか文法的にコレが通るとは思ってなかった。
int main(void) {
    int a[3];
    0[a] = 1;
    1[a] = 2;
    printf("%d,%d,%d,%d\n", a[0], a[1], 0[a], 1[a]);
    return 0;
}
知らない事だらけだ...
Posted at by



2008/03/19


私もこれまで色々なWindowsアプリケーションを作ってきましたが、それらの多くはデスクトップ上で目的の動作だけを実行する単純なアプリケーションだったりします。
最近のテキストエディタ等では、マクロ等と呼ばれる拡張言語を使用してエディタ本来の動作では実現出来ない色々な追加機能を実行する事が出来る様になっています。
今日は、既存のWin32アプリケーションにJavaScriptでマクロが実行出来る様にする為のtipsをご紹介。
拡張言語といってもJavaScriptの様に柔軟性のある言語を作り直すとなると程遠い工数を掛けてしまう事になりますが、Windowsには「ScriptControl」というスクリプト実行コンポーネントが用意されています。
今回はこれを使って外部にあるJavaScriptファイルを実行し、かつそのJavaScriptからアプリケーション内のオブジェクトを操作するまでを説明します。ScriptControlはCOMで実装されており、以下の様にインスタンスを生成します。     hr = CoCreateInstance(
            CLSID_ScriptControl,
            NULL,
            CLSCTX_ALL,
            IID_IScriptControl,
            (void**)&pScriptCtrl);
そしてJavaScript(JScript)を実行させる為にLanguageプロパティを設定してExecuteStatementを実行します。
    hr = pScriptCtrl->put_Language(_bstr_t("JScript"));
    hr = pScriptCtrl->put_Timeout(-1);
    hr = pScriptCtrl->put_AllowUI(VARIANT_FALSE);
    hr = pScriptCtrl->ExecuteStatement(A2BSTR("var a = 'test'"));
これだけで既存のアプリケーションからJavaScriptが実行出来るようになります。
ただこれだけでは既存アプリケーションとの連携はまったく無く、面白味がありませんし単なる計算言語にしか成り得ません。
ブラウザ上で実行されるJavaScriptの様にwindowオブジェクトも無ければdocumentオブジェクトもありません。
つまりalertは使えません
このオブジェクトをJavaScript上に追加するのがAddObjectメソッドです。
AddObjectメソッドは名称指定でIUnknownオブジェクトをJavaScriptの実行スコープに追加出来ます。
ここに既存アプリケーションのオブジェクトを差し込む事になります。JavaScriptではメソッドを呼び出そうとする際にそのオブジェクトに対してIDispatchへのQueryInterfaceを試み、GetIDsOfNamesでdispIDを取得した後にInvokeメソッドを呼び出します。
この辺は、COMの知識のある方ならば既にご存知ですね。
で実装したIDispatchは以下の様なコードになりました。
class MyObject : public IDispatch
{
private:
    LONG m_cRef;

    // method type
    typedef HRESULT (MyObject::*Func)(DISPPARAMS*, VARIANT*);

    // method structure
    typedef struct _MY_OBJECT_METHOD_TABLE {
        _MY_OBJECT_METHOD_TABLE(DISPID dispid, const char* name, Func fn) {
            this->dispid = dispid;
            this->name = name;
            this->fn = fn;
        }
        DISPID dispid;
        const char* name;
        Func fn;
    } MY_OBJECT_METHOD_TABLE;

    // method table
    std::vector<MY_OBJECT_METHOD_TABLE> myObjectMethodTable;

public:
    // method: say
    //  show MessageBox
    HRESULT say(DISPPARAMS* pDispParams, VARIANT* ret) {
        USES_CONVERSION;

        for(int n = 0; n < pDispParams->cArgs; n++) {
            HRESULT hr;
            VARIANT arg;
            VariantInit(&arg);
            hr = VariantChangeType(&arg, &pDispParams->rgvarg[n], 0, VT_BSTR);
            MessageBox(0, OLE2T(arg.bstrVal), _T("MyObject"), MB_OK);
        }
        if (ret) {
            VariantInit(ret);
            V_VT(ret) = VT_BOOL;
            V_BOOL(ret) = VARIANT_TRUE;
        }
        return S_OK;
    }

    // method: start
    //  start program by arguments
    HRESULT start(DISPPARAMS* pDispParams, VARIANT* ret) {
        USES_CONVERSION;

        for(int n = 0; n < pDispParams->cArgs; n++) {
            HRESULT hr;
            VARIANT arg;
            VariantInit(&arg);
            hr = VariantChangeType(&arg, &pDispParams->rgvarg[n], 0, VT_BSTR);
            ShellExecute(NULL, _T("open"), OLE2T(arg.bstrVal), NULL, NULL, SW_SHOW);
        }
        if (ret) {
            VariantInit(ret);
            V_VT(ret) = VT_BOOL;
            V_BOOL(ret) = VARIANT_TRUE;
        }
        return S_OK;
    }

    // constructor
    MyObject() : m_cRef(0) {
        myObjectMethodTable.push_back(MY_OBJECT_METHOD_TABLE(1, "say", say));
        myObjectMethodTable.push_back(MY_OBJECT_METHOD_TABLE(2, "start", start));
    }

    STDMETHODIMP QueryInterface(REFIID rid, LPVOID *ppv) {
        *ppv = NULL;
        if (::IsEqualIID(rid, IID_IUnknown)) {
            *ppv = this;
            AddRef();
            return S_OK;
        }
        if (::IsEqualIID(rid, IID_IDispatch)) {
            *ppv = this;
            AddRef();
            return S_OK;
        }
        return E_NOINTERFACE;
    }
    ULONG STDMETHODCALLTYPE AddRef() {
        ULONG ref = InterlockedIncrement(&m_cRef);
        return ref;
    }
    ULONG STDMETHODCALLTYPE Release() {
        ULONG ref = InterlockedDecrement(&m_cRef);
        return ref;
    }
    STDMETHODIMP GetTypeInfoCount(UINT *ptiCount) {
        if (ptiCount) *ptiCount = 0;
        return S_OK;
    }
    STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **pptInfo) {
        if (pptInfo) *pptInfo = NULL;
        return S_OK;
    }
    STDMETHODIMP GetIDsOfNames(REFIID riid, OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID) {
        USES_CONVERSION;

        for(UINT n = 0; n < cNames; n++) {
            std::vector<MY_OBJECT_METHOD_TABLE>::iterator it = myObjectMethodTable.begin();
            for(it = myObjectMethodTable.begin(); it != myObjectMethodTable.end(); it++) {
                if (!strcmp(it->name, OLE2A(rgszNames[n]))) {
                    rgDispID[n] = it->dispid;
                    break;
                }
            }
            if (it == myObjectMethodTable.end()) return E_UNEXPECTED;
        }
        return S_OK;
    }
    STDMETHODIMP Invoke(DISPID dispID, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) {
        std::vector<MY_OBJECT_METHOD_TABLE>::iterator it = myObjectMethodTable.begin();
        for(it = myObjectMethodTable.begin(); it != myObjectMethodTable.end(); it++) {
            if (it->dispid == dispID) {
                return (this->*(it->fn))(pDispParams, pVarResult);
            }
        }
        if (it == myObjectMethodTable.end()) return E_UNEXPECTED;
        return S_OK;
    }
};
単純に引数の文字列をメッセージボックスで表示する「say」メソッドと、引数の文字列をプログラムとして起動する「start」メソッドを実装しています。
これを実行する「plugin.js」は以下の様になります。
MyObject.say('Hello');
MyObject.start('http://mattn.kaoriya.net');
これを実行すると「Hello」というメッセージボックスが表示された後、このサイトがブラウザで表示される結果となります。
20080319121041
全体のソースコードは以下の通り。
#include <atlbase.h>
#include <windows.h>
#include <string>
#include <vector>
#import <msscript.ocx> raw_interfaces_only, named_guids, no_namespace
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "shell32.lib")

class MyObject : public IDispatch
{
private:
    LONG m_cRef;

    // method type
    typedef HRESULT (MyObject::*Func)(DISPPARAMS*, VARIANT*);

    // method structure
    typedef struct _MY_OBJECT_METHOD_TABLE {
        _MY_OBJECT_METHOD_TABLE(DISPID dispid, const char* name, Func fn) {
            this->dispid = dispid;
            this->name = name;
            this->fn = fn;
        }
        DISPID dispid;
        const char* name;
        Func fn;
    } MY_OBJECT_METHOD_TABLE;

    // method table
    std::vector<MY_OBJECT_METHOD_TABLE> myObjectMethodTable;

public:
    // method: say
    //  show MessageBox
    HRESULT say(DISPPARAMS* pDispParams, VARIANT* ret) {
        USES_CONVERSION;

        for(int n = 0; n < pDispParams->cArgs; n++) {
            HRESULT hr;
            VARIANT arg;
            VariantInit(&arg);
            hr = VariantChangeType(&arg, &pDispParams->rgvarg[n], 0, VT_BSTR);
            MessageBox(0, OLE2T(arg.bstrVal), _T("MyObject"), MB_OK);
        }
        if (ret) {
            VariantInit(ret);
            V_VT(ret) = VT_BOOL;
            V_BOOL(ret) = VARIANT_TRUE;
        }
        return S_OK;
    }

    // method: start
    //  start program by arguments
    HRESULT start(DISPPARAMS* pDispParams, VARIANT* ret) {
        USES_CONVERSION;

        for(int n = 0; n < pDispParams->cArgs; n++) {
            HRESULT hr;
            VARIANT arg;
            VariantInit(&arg);
            hr = VariantChangeType(&arg, &pDispParams->rgvarg[n], 0, VT_BSTR);
            ShellExecute(NULL, _T("open"), OLE2T(arg.bstrVal), NULL, NULL, SW_SHOW);
        }
        if (ret) {
            VariantInit(ret);
            V_VT(ret) = VT_BOOL;
            V_BOOL(ret) = VARIANT_TRUE;
        }
        return S_OK;
    }

    // constructor
    MyObject() : m_cRef(0) {
        myObjectMethodTable.push_back(MY_OBJECT_METHOD_TABLE(1, "say", say));
        myObjectMethodTable.push_back(MY_OBJECT_METHOD_TABLE(2, "start", start));
    }

    STDMETHODIMP QueryInterface(REFIID rid, LPVOID *ppv) {
        *ppv = NULL;
        if (::IsEqualIID(rid, IID_IUnknown)) {
            *ppv = this;
            AddRef();
            return S_OK;
        }
        if (::IsEqualIID(rid, IID_IDispatch)) {
            *ppv = this;
            AddRef();
            return S_OK;
        }
        return E_NOINTERFACE;
    }
    ULONG STDMETHODCALLTYPE AddRef() {
        ULONG ref = InterlockedIncrement(&m_cRef);
        return ref;
    }
    ULONG STDMETHODCALLTYPE Release() {
        ULONG ref = InterlockedDecrement(&m_cRef);
        return ref;
    }
    STDMETHODIMP GetTypeInfoCount(UINT *ptiCount) {
        if (ptiCount) *ptiCount = 0;
        return S_OK;
    }
    STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **pptInfo) {
        if (pptInfo) *pptInfo = NULL;
        return S_OK;
    }
    STDMETHODIMP GetIDsOfNames(REFIID riid, OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID) {
        USES_CONVERSION;

        for(UINT n = 0; n < cNames; n++) {
            std::vector<MY_OBJECT_METHOD_TABLE>::iterator it = myObjectMethodTable.begin();
            for(it = myObjectMethodTable.begin(); it != myObjectMethodTable.end(); it++) {
                if (!strcmp(it->name, OLE2A(rgszNames[n]))) {
                    rgDispID[n] = it->dispid;
                    break;
                }
            }
            if (it == myObjectMethodTable.end()) return E_UNEXPECTED;
        }
        return S_OK;
    }
    STDMETHODIMP Invoke(DISPID dispID, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) {
        std::vector<MY_OBJECT_METHOD_TABLE>::iterator it = myObjectMethodTable.begin();
        for(it = myObjectMethodTable.begin(); it != myObjectMethodTable.end(); it++) {
            if (it->dispid == dispID) {
                return (this->*(it->fn))(pDispParams, pVarResult);
            }
        }
        if (it == myObjectMethodTable.end()) return E_UNEXPECTED;
        return S_OK;
    }
};

// load script and return the code
char* loadScriptAlloc(LPCTSTR pszFileName) {
    HANDLE hFile;
    DWORD dwFileSize, dwReadSize;
    char* pszData = NULL;

    hFile = CreateFile(
            pszFileName,
            GENERIC_READ,
            FILE_SHARE_READ,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL);
    if (hFile == INVALID_HANDLE_VALUE) goto leave;

    dwFileSize = GetFileSize(hFile , NULL);
    if (dwFileSize == (DWORD)-1) goto leave;
    pszData = (char*)calloc(dwFileSize + 1, 1);
    if (!pszData) goto leave;

    if (!ReadFile(
            hFile,
            pszData,
            dwFileSize,
            &dwReadSize, NULL)) goto leave;
leave:
    if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);
    return pszData;
}

int main(int argc, char *argv[]) {
    USES_CONVERSION;

    HRESULT hr = 0;
    IScriptControl* pScriptCtrl = NULL;
    char* pszCode = NULL;
    MyObject* pMyObject = NULL;

    CoInitialize(NULL);

    hr = CoCreateInstance(
            CLSID_ScriptControl,
            NULL,
            CLSCTX_ALL,
            IID_IScriptControl,
            (void**)&pScriptCtrl);
    if (FAILED(hr) || !pScriptCtrl) goto leave;

    hr = pScriptCtrl->put_Language(_bstr_t("JScript"));
    if (FAILED(hr)) goto leave;

    hr = pScriptCtrl->put_Timeout(-1);
    if (FAILED(hr)) goto leave;

    hr = pScriptCtrl->put_AllowUI(VARIANT_FALSE);
    if (FAILED(hr)) goto leave;

    pMyObject = new MyObject();
    hr = pScriptCtrl->AddObject(_bstr_t("MyObject"), pMyObject, VARIANT_TRUE);

    pszCode = loadScriptAlloc(_T("plugin.js"));
    if (!pszCode) goto leave;

    hr = pScriptCtrl->ExecuteStatement(A2BSTR(pszCode));
    free(pszCode);
    pszCode = NULL;

leave:
    if (pszCode) {
        free(pszCode);
        pszCode = NULL;
    }
    if (pScriptCtrl) {
        pScriptCtrl->Reset();
        pScriptCtrl->Release();
        pScriptCtrl = NULL;
    }
    if (pMyObject) {
        pMyObject->Release();
        pMyObject = NULL;
    }
    CoUninitialize();
    return 0;
}
あとはこの「say」メソッドなり「start」メソッドなりを既存アプリケーションとの連携用メソッドとして実装すれば、見事JavaScriptによるプラグイン機能が実現出来ます。
意外と少ないコードで実装出来るので皆さん昔に作ったアプリケーション等を拡張して見られてはどうでしょうか。

Posted at by



2008/03/18


rubyのをpythonに...
ブログにXML-RPC APIで、複数のファイルをアップロードするRubyスクリプト:Goodpic
pythonは楽でいいや。I love python!

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys, os, glob
import base64
import mimetypes
import xmlrpclib

if len(sys.argv) < 6:
  print "usage: url blogid username password path [files ... or directory]"
  sys.exit()

metaWeblog = xmlrpclib.Server(sys.argv[1]).metaWeblog
for arg in sys.argv[6:]:
  if os.path.isdir(arg): arg = os.path.join(arg,'*')
  for f in glob.glob(arg):
    print metaWeblog.newMediaObject(
      sys.argv[2], # blogid
      sys.argv[3], # username
      sys.argv[4], # password
      {
        'name' : "%s/%s" % (sys.argv[5], os.path.basename(f)),
        'type' : mimetypes.guess_type(f)[0] or 'application/octet-stream',
        'bits' : xmlrpclib.Binary(open(f, "rb").read())
      })

使い方は upload.py http://your.blog.server/xmlrpc your-blog-id username password upload-directory [local files or directory]

AWSWORD:python:
Posted at by




以前どこかで、グリモンスクリプトは前に「(function(){」、後ろに「}()」が付与されてるって見たことあるけど // ==UserScript==
// @name           broken source
// @namespace      xxxxxx
// @description    broken source
// @include        http://*
// ==/UserScript==

})();
(function () {
これOKでつか...

イケるんですか...
Posted at by




fukaz55さんの所に置いてあった「Amazon Web Services から情報を取得する blosxom 向けプラグイン」を貰ってきて AWSWORD:ほにゃらら: ※「ほにゃらら」は英数字もしくは「_」
と指定出来るようにした。
これまでの様に ASIN:4844322893 と書けば

となるし
AWSWORD:perl: と書けば
AWSWORD:perl:
となる。
結果は一覧されるうちの先頭で表示され、キャッシュは全件「検索語.xml」で保存される。ファイル名の都合で英数字+「_」が制限になってます。
なおblosxom::templateを使ってないのは、mobrowserと干渉しても構わないようにです。
パッチは以下の通り。
--- awsxom.orig Thu Nov 30 22:12:17 2006
+++ awsxom  Tue Mar 18 09:32:22 2008
@@ -57,17 +57,20 @@
 
    # ASIN/ISBNが書かれていたら置き換える
    # テンプレート指定版
-   s/(?:ASIN|ISBN):([A-Z0-9]{10}):(.*?):/to_html($1,$2)/ge;
+   s/(?:ASIN|ISBN):([A-Z0-9]{10}):(.*?):/to_html_asin($1,$2)/ge;
 
    # テンプレート無指定版
-   s/(?:ASIN|ISBN):([A-Z0-9]{10})/to_html($1,$default_template)/ge;
+   s/(?:ASIN|ISBN):([A-Z0-9]{10})/to_html_asin($1,$default_template)/ge;
+
+   # テンプレート無指定版
+   s/(?:AWSWORD):([a-zA-Z0-9_]*?):/to_html_word($1,$default_template)/ge;
 
    return $_;
 }
 
 # ---------------------------------------------------------------------
 # ASINからAmazonのアフィリエイト用HTMLを作成
-sub to_html {
+sub to_html_asin {
    my ($asin, $template) = @_; # ASINとテンプレ名称
    my $cache = "$cachedir/$asin.xml";
    my $url = "http://webservices.amazon.co.jp/onca/xml?Service=AWSECommerceService&SubscriptionId=$devkey&AssociateTag=$asoid&Operation=ItemLookup&ItemId=$asin&ResponseGroup=Medium,Offers";
@@ -90,8 +93,57 @@
    # テンプレートを展開。エラーの場合はエラー文字列を返す
    my $form;
    if (!defined($detail{"ErrorMsg"})) {
-       $form = &$blosxom::template($blosxom::path, $template, 'html');
-       $form =~ s/\$(\w+)/$detail{$1}/ge;
+       #$form = &$blosxom::template($blosxom::path, $template, 'html');
+        my $fh = new FileHandle;
+        if ($fh->open("< $blosxom::datadir/$template.html")) {
+            $form = join '', <$fh>;
+           $form =~ s/\$(\w+)/$detail{$1}/ge;
+            $fh->close();
+        }
+   }
+   else {
+       $form = "<p>" . $detail{"ErrorMsg"} . "</p>";
+   }
+
+   return $form;
+}
+
+# ---------------------------------------------------------------------
+# ASINからAmazonのアフィリエイト用HTMLを作成
+sub to_html_word {
+   my ($word, $template) = @_; # ASINとテンプレ名称
+   my $cache = "$cachedir/$word.xml";
+   my $url = "http://webservices.amazon.co.jp/onca/xml?Service=AWSECommerceService&SubscriptionId=$devkey&AssociateTag=$asoid&Operation=ItemSearch&Keywords=$word&SearchIndex=Books&ResponseGroup=Medium,Offers";
+   my $outfile = "$cachedir/$word.html";
+
+   # 取り込み直す必要はあるか?
+   if (!(-e $cache) || (-M $cache > ($EXPIRE / 24))) {
+   # AWSから情報を取得してキャッシュファイルに保存
+       # UserAgent初期化
+       my $ua = new LWP::UserAgent;
+       $ua->agent($ua_name);
+       $ua->timeout(60);
+       my $rtn = $ua->mirror($url, $cache);
+   }
+
+   # キャッシュからXMLを読み込んで解析
+   my $content = getFile($cache);
+    $content =~ s!.*?(<Item>.*?</Item>).*!$1!is;
+   my $asin = "";
+    $asin = $1 if ($content =~ /<ASIN>([^<]*)<\/ASIN>/);
+    return "" if !$asin;
+   my %detail = parseXML($content, $asin);
+
+   # テンプレートを展開。エラーの場合はエラー文字列を返す
+   my $form;
+   if (!defined($detail{"ErrorMsg"})) {
+       #$form = &$blosxom::template($blosxom::path, $template, 'html');
+        my $fh = new FileHandle;
+        if ($fh->open("< $blosxom::datadir/$template.html")) {
+            $form = join '', <$fh>;
+           $form =~ s/\$(\w+)/$detail{$1}/ge;
+            $fh->close();
+        }
    }
    else {
        $form = "<p>" . $detail{"ErrorMsg"} . "</p>";
ちなみに、先日図書館で見つけた本で面白いの見つけた。
子供向け絵本らしいが、ちょっと笑った。
Posted at by



2008/03/17


vimではあまり使い道がないだろうけど...
スクリプトは " DO NOT EDIT
scriptencoding utf-8
let s:self = expand("<sfile>")
com! UseDataToken exe join(map(remove(readfile(s:self),5,8),"strpart(v:val,1)"),"|")
"__DATA__
"silent! unlet datatoken
"let datatoken=readfile(expand("<sfile>"))
"cal remove(datatoken,0,search("^\"__DATA__$")-1)
"cal map(datatoken,"strpart(v:val,1)")
こんな感じで使う側はこんな感じ "source datatoken.vim
UseDataToken

silent! unlet d
let d = eval(join(datatoken))
echo d.author
echo d.version
echo d.date

"__DATA__
"{
"'author': 'mattn <mattn.jp@gmail.com>',
"'version': '0.001',
"'date': 'Mon, 17 Mar 2008'
"}
perlの様にファイルハンドルでないのが気持ち悪いか。
追記
タイトル変だったので直した
Posted at by




id:TAKESAKOさんのブックマークから
APIチュートリアル - Lingr Group on Hatena
API Tutorial in Lingr Developer Wiki
を見つけた。さっそくpythonでモジュール作った。(既にありそう...)
セッションの作成、入室、発言、退室をメソッドとして公開しています。
セッションの破棄はデストラクタでやってます。使い方は、__main__を参照。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Lingr API Module
"""
import httplib
import urllib
from xml.dom import minidom

__author__ = 'mattn <mattn.jp@gmail.com>'
__url__ = 'http://mattn.kaoriya.net/'
__version__ = "0.01"

"""
Lingr API Class
"""
class LingrAPI:
  """
  initialize class variables and create session.
  """
  def __init__(self, api_key, nickname):
    self.api_key = api_key
    self.nickname = nickname
    self.session = None
    self.conn = httplib.HTTPConnection("www.lingr.com")
    self.conn.request('POST', '/api/session/create/',
        body = "api_key=%s" % urllib.quote(api_key))
    response = self.conn.getresponse()
    data = response.read()
    doc = minidom.parseString(data)
    if doc.getElementsByTagName('status')[0].childNodes[0].data != 'ok':
      raise Exception({
          'code' : doc.getElementsByTagName('code')[0].childNodes[0].data,
          'message' : doc.getElementsByTagName('message')[0].childNodes[0].data})
    self.session = doc.getElementsByTagName('session')[0].childNodes[0].data

  """
  destroy session.
  """
  def __del__(self):
    if self.session:
      import urllib
      from xml.dom import minidom
      self.conn.request('POST', '/api/session/destroy',
          body = "session=%s" % urllib.quote(self.session))
      response = self.conn.getresponse()
      data = response.read()
      doc = minidom.parseString(data)
      if doc.getElementsByTagName('status')[0].childNodes[0].data != 'ok':
        raise Exception({
            'code' : doc.getElementsByTagName('code')[0].childNodes[0].data,
            'message' : doc.getElementsByTagName('message')[0].childNodes[0].data})

  """
  enter room and return room_id
  """
  def enter_room(self, room_id):
    self.conn.request('POST', '/api/room/enter',
        body = "session=%s&id=%s&nickname=%s"
            % (urllib.quote(self.session), urllib.quote(room_id), urllib.quote(self.nickname)))
    response = self.conn.getresponse()
    data = response.read()
    doc = minidom.parseString(data)
    if doc.getElementsByTagName('status')[0].childNodes[0].data != 'ok':
      raise Exception({
          'code' : doc.getElementsByTagName('code')[0].childNodes[0].data,
          'message' : doc.getElementsByTagName('message')[0].childNodes[0].data})
    return doc.getElementsByTagName('ticket')[0].childNodes[0].data

  """
  say message using ticket.
  """
  def say(self, ticket, message):
    self.conn.request('POST', '/api/room/say',
        body = "session=%s&ticket=%s&message=%s"
            % (urllib.quote(self.session), urllib.quote(ticket), urllib.quote(message)))
    response = self.conn.getresponse()
    data = response.read()
    doc = minidom.parseString(data)
    if doc.getElementsByTagName('status')[0].childNodes[0].data != 'ok':
      raise Exception({
          'code' : doc.getElementsByTagName('code')[0].childNodes[0].data,
          'message' : doc.getElementsByTagName('message')[0].childNodes[0].data})
    return doc.getElementsByTagName('occupant_id')[0].childNodes[0].data

  """
  exit room
  """
  def exit_room(self, ticket):
    self.conn.request('POST', '/api/room/exit',
        body = "session=%s&ticket=%s"
            % (urllib.quote(self.session), urllib.quote(ticket)))
    response = self.conn.getresponse()
    data = response.read()
    doc = minidom.parseString(data)
    if doc.getElementsByTagName('status')[0].childNodes[0].data != 'ok':
      raise Exception({
          'code' : doc.getElementsByTagName('code')[0].childNodes[0].data,
          'message' : doc.getElementsByTagName('message')[0].childNodes[0].data})
    return doc.getElementsByTagName('status')[0].childNodes[0].data

#  def find_room(title):
#    self.conn.request('GET', '/search/rooms',
#        body = "query=%s" % urllib.quote(title))
#    response = self.conn.getresponse()
#    data = response.read()

if __name__ == '__main__':
    import sys
    if len(sys.argv) < 2: sys.exit()
    api_key = sys.argv[1]
    nickname = sys.argv[2]
    room_id = sys.argv[3]
    message = sys.argv[4]

    api = LingrAPI(api_key, nickname)
    ticket = api.enter_room(room_id)
    api.say(ticket, message)
    api.exit_room(ticket)
room_idは部屋の検索から検索して、その部屋のURLの最後の部分がIDになります。
今のところbot的な用途でしか使えない程のメソッドしか出してませんが、暇があればメソッドを追加していくかも。

あと、codereposに置いておくので良かったら弄って下さい。
Posted at by



2008/03/14


capture.flickr.js FirefoxでキャプチャしてFlickrにアップロードするJSActionsスクリプト #2008.03.13 « ZeroMemory

といってもCSSだけ...
--- capture.flickr.js.orig  Thu Mar 13 21:55:18 2008
+++ capture.flickr.js   Fri Mar 14 12:37:01 2008
@@ -7616,7 +7616,7 @@
        document.addEventListener( 'mousedown', this.onmousedown, true );
 
        var s = document.createElement('style');
-       s.innerHTML = '* {cursor: crosshair !important;}';
+       s.innerHTML = '* {cursor: crosshair !important; -moz-user-select: none;}';
        document.body.appendChild(s);
        this.style = s;
    },
ふむ。便利だ。
capture.flickr.js FirefoxでキャプチャしてFlickrにアップロードするJSActionsスクリプト #2008.03.13 « ZeroMemory
Posted at by



2008/03/12


結構見逃してる物多いなぁ。
jQuery: jqAlbumParser Plugin, parses out Flickr, Picasa clientside | The Book and the Cover
カッコイイ。
実装するのもそれ程難しくないしサイトの端っこにでも貼り付けても面白いかもしれない。
動作としては、jqAlbumParserがFlickrのFeedをパースする物で、それを表示する為のプラグインがjqGalViewIIとの事。
よってコードは <style media="all">
@import url("/path/to/css/jqGalViewII.css");
</style>
<script type="text/javascript" src="/path/to/javascript/jquery-latest.js"></script>
<script type="text/javascript" src="/path/to/javascript/jqAlbumParser.js"></script>
<script type="text/javascript" src="/path/to/javascript/jqGalViewII.js"></script>
<script type="text/javascript">
$(document).ready(function(){
    $(".jqAlbumParser").jqAlbumParser({
        pluginExec : function(){$(this).jqGalViewII();}
    }).click();
});
</script>
<a href="http://api.flickr.com/services/feeds/photos_public.gne?ids=YOUR_FLICKR_ID"
    class="jqAlbumParser wa:flickr">my flickr feed</a>
こんな感じ。「$(document).ready」を使わず実行したいならば「.click()」の所を「.trigger('load')」にすれば自動で表示される。
アンカーのクリックアクションで動作させたいならば、何も指定しないか「.trigger('click')」で行けます。
またfeedのURLの最後に「&tags=XXXX」とすれば、そのタグで絞り込める。簡単なソースで結構リッチなアルバムビューワが出来るって凄いなぁ。

以下、私のflickrで実行した例

続きを読む...

Posted at by




id:miyagawa氏のはてなブックマークからyowlという物を見つけました。
yowl - Google Code
yowlとは、Mac OS X上で実装されている通知システムのアプリケーション実装「Growl」をYahooライブラリYUIを使ってWeb実装した物です。
さっそくダウンロードして使ってみましたが、やっぱりYUIは良く出来てますね。
Windows用にシステムトレイ常駐の通知アプリケーションと連携すれば、Webからデスクトップアプリケーションとして何かしらを通知出来るという事になります。
通知時のアクションが決められれば、夢の広がるアプリケーションになるでしょうね。
今日はサンプルとして、以前つくった「はてブでアーーーーッ!!!」をYOWLでやってみたいと思います。

少し色を付けて、JSDeferredとjQueryを使い、del.icio.usとパラレルで動かそうかと思いましたが、あまり面白くないのでやめて、結局JSDeferredのcall/waitのみ使わせて頂いています。

実行結果、ソースは以下から...

続きを読む...

Posted at by




jQueryらくちんでいいや。
jQueryのCycleプラグインで遊んでみました。
まずソース
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript" src="http://www.malsup.com/jquery/cycle/jquery.cycle.all.js"></script>
<style type="text/css"><!--
#devilman {
  width: 272px; height: 352px;
  padding:0; margin:0;
  overflow: hidden;
}
#devilman img {
  width: 240px; height: 320px;
  padding: 15px;
  border: 1px solid #ccc; background-color: #eee;
  top:0; left:0
}
#devilman_serif {
  width: 240px; padding: 15px;
  border: 1px solid #ccc; background-color: #eee;
  top:0; left:0;
  margin-top:10px;
  font-family: Verdana; font-size: 12px;
}
--></style>
<script type="text/javascript"><!--
$(document).ready(function(){
  $('#devilman').cycle({ 
    fx: 'scrollUp', 
    click: '#devilman img', 
    timeout: 0,
    before: function(next, curr) {
      $('#devilman_serif').html('<img src="http://www.ac.cyberhome.ne.jp/~mattn/images/ajax-loader.gif" alt="loading...">');
    },
    after: function(next, curr) {
      curr.title = curr.alt.replace(/<[^>]*>/g,'');
      $('#devilman_serif').html(curr.alt);
    }
  });
});
--></script>
</head>
<body>
<div id="devilman">
    <img class="devilman_slide" src="http://farm2.static.flickr.com/1405/601557689_5d0a1304a8.jpg?v=0" width="240" height="320" alt="誰も知らない。<br />知られちゃいけない。<br />デビルマンが誰なのか。" /> 
    <img class="devilman_slide" src="http://farm2.static.flickr.com/1146/601557811_0591344b9d.jpg?v=0" width="240" height="320" alt="何も言えない、話しちゃいけない。<br />デビルマンが誰なのか。<br />人の世に愛があり、<br />人の世に夢がある。<br />その美しい物を守りたいだけ。" /> 
    <img class="devilman_slide" src="http://farm2.static.flickr.com/1398/601842094_322d292a58.jpg?v=0" width="240" height="320" alt="今日もどこかでデビルマン。<br />今日もどこかでデビルマン。" /> 
</div>
<div id="devilman_serif"></div>
</body>
</html>
画像を3枚使い、clickイベントにてスライドしています。beforeコールバックでajaxローディング画像を、afterコールバックで表示したimgのtitleをメッセージ表示用のdivにコピーしています。 jQueryならこんな簡単に出来ちゃうんすよね。
で、ふと思った。jQueryってFLTKに似てる。もちろんFLTKのコールバックはaddEventListenerじゃないけど、なんか $('#btn').click(function() {alert('ok!');});
で、コールバックが書ける所がカッコイイ。
ちなみにFLTKでウィンドウとボタンを出すだけのアプリケーションならば、こんな感じ。 #include <stdio.h>
#include <fltk/run.h>
#include <fltk/Window.h>
#include <fltk/Button.h>

void button_cb(fltk::Widget *, void *) {
  printf("clicked!\n");
}

int main(int argc, char* argv[]) {
  fltk::Window *window = new fltk::Window(200, 200);

  window->begin();
  fltk::Button *button= new fltk::Button(20, 20, 160, 160, "click me");
  button->callback(button_cb, 0);
  window->end();

  window->show(argc, argv);
  return fltk::run();
}
ポインタ使わず書いたら、もっとそれっぽい。
#include <stdio.h>
#include <fltk/run.h>
#include <fltk/Window.h>
#include <fltk/Button.h>

void button_cb(fltk::Widget *, void *) {
  printf("clicked!\n");
}

int main(int argc, char* argv[]) {
  fltk::Window window(200, 200);

  window.begin();
  fltk::Button button(20, 20, 160, 160, "click me");
  button.callback(button_cb, 0);
  window.end();

  window.show(argc, argv);
  return fltk::run();
}
話を戻して、javascriptの実行結果は↓

続きを読む...

Posted at by



2008/03/11


twitterには結構色んなbotが居て、結構有用だったりします。
有名な所では

plagger

http://twitter.com/plagger
perl/plaggerに詳しい某氏のブックマークが流れているらしい。私も購読している。

hatebu

http://twitter.com/hatebu
はてなブックマークのホットエントリが流れています。私も購読しています。

nicovNEW

http://twitter.com/nicovNEW
ニコニコ動画情報が流れています。

perlnews

http://twitter.com/perlnews
charsberさんのエントリで知った。perlに関するニュース一覧。さっき購読しました。
※詳しくは便利なBOT - TwitterまとめWikiを参照。

で、これらのRSSフィードをフィードリーダで購読する時に思うのが
botのステータスって、本文に対象のリンクが含まれているからRSSのlink要素はtwitter-botのステータスページになってしまって、行きたいリンクには一度ステータスページを踏まなきゃならない。
本文にtinyurlのリンクは書いてあるけど、アンカーになってないからリンク先に行くのが面倒臭い。
って事。しかもtwitterのアンカーって全部ターゲットが「_blank」。フィードリーダからすれば、ステータスページ、対象のリンク、と1つ余計なタブが開いてしまう事になります。
twitterbotautofollowlink.user.jsこんなグリモンで新しいタブを開かずに自動にページ遷移する事も出来るけど followする度に@include足して行くのはかっこ悪い。(ネタですから...)
で、良く考えたら
RSSはアンカーになってないけど、twitterのステータスページってアンカーになってるんだから、フィードリーダがそれをアンカー扱い出来ればいいのでは?
しかも先日作ったGoogle Reader Full Feedがあるじゃないか...
で、先ほどSITEINFOにtwitter.comの情報を追加しておきました。
twitter-bot-feed-link
※リンクがアンカーになってます。
まぁ、本当ならばRSSのlink要素が対象のリンクになってるのがフィードリーダを使う側としては便利なんですけどね。
Posted at by



2008/03/10


追記
都合上、キーを「g」から「z」に変更させて頂いています。
以下読み替えて頂く様お願い致します。

まぁ、お約束って事で。
しかしまぁLDRで全文取得表示するグリモン便利だなぁ。
OperaでLDR Full Feed - 0x集積蔵

id:Constellation さん作の、LDR Full Feed - Userscripts.orgをOperaに移植してみた。
移植早ぇぇぇぇぇ!!!

って事で、os0xさんのOpera版をベースにGoogle Reader版を作ってみました。
そんなに大きな変更してません。要はos0xさんのを参考に昨日のグリモンをOperaに対応させただけ。

またまた操作は同じく、[G]のアイコンが付いてたら「g」を押下で全文取得。
とりあえず、dankogai氏のサイトでチェックして問題なさげ。(またか!)

ライセンスはLDR Full Feedに委ねます。さらにOpera固有部に関してはos0xさんのLDR Full Feed Operaに委ねます。

ダウンロード:googlereaderfullfeed.js

追記
2008/02/28
Posted at by




結構難しかった...
オリジナル作者はLDRizeやMinibufferを作られたsnj14さん。
初めてminibufferbookmarkcommandを使った時は「スゲー」と感動しました。その後ソースがCodeReposで管理される用になりプラガブルな仕組みに修正させて頂き、gooブックマークや、niftyクリップ、pookmark等のプラグインも動くようになりました。
ただFirefoxでは動くけど、Operaでは動かなかった。
Firefoxだけでしか使えないってのが擬かしい程、使い勝手はめちゃめちゃ良くて
「minibufferBookmarkcommandのボタン一発ブックマークが気軽すぎてタグ付けとかしなくなる」
とおっしゃる方もいる位。ポップアップも出ないし別画面に飛ばされる訳でもないから、記事を読んでる最中に「ぶくま!」と思ったら「b」一発。
タグやコメントが打ちたくなったら「B」。常用しだすと手放せなくなります。
で、この快感をOperaユーザにも伝えたい。そう思いました。
Operaでは通常の作りをしていてはドメインを越えた通信(JSON以外)は出来ないのですが、postMessageというAPIを使うことでコンテンツ間のメッセージングが行えるようになります。
これを使用して、GM_xmlhttpRequestもどきを実装しています。ただし困ったのがこのpostMessageに送り出す文字列(HTML)を取得する為にはオブジェクトをメインコンテンツに追加する必要がありかつ追加するという事はGET限定、つまりPOSTを実行させる為にはドメインを越えられる別のPOST実装が必要になるって事に。
で、結局やったのが動的にiframeを生成して、その中にformを作りポストするという方法。
いろいろやっている内に、DOM追加で動くGET版とiframeを使うPOST版を纏めたGM_xmlhttpRequestみたいな物がなんとなく出来上がりました。
(完全ではありません)

これで行ける!と思ったのですが今度はdel.icio.usのタグを取得する方法で困った。このpostMessageに送り出すにはDOMContentLoadedをフックするのだけれど、del.icio.usのAPIからXML形式のタグ一覧を読み込んだ時にはDOMContentLoadedが走らない。
悩んだ挙句、一度「http://del.icio.us/」にアクセスし、ユーザ名称を見つけ、「http://feeds.delicious.com/feeds/json/tags/XXXX」にアクセスし、JSONを取得するという方法で実装しました。
ようやく
が使えるminibufferbookmarkcommandが出来上がりました。ふぅ
Operaユーザの方で、SBMをお使いの方は一度試して見て下さい。
minibufferbookmarkcommand.js
なお作成にあたっては、Opera版のLDR Full Feedを作成されたos0xさんのコードをふんだんに参考にさせて頂いております。感謝。
Posted at by




追記
都合上、キーを「g」から「z」に変更させて頂いています。
以下読み替えて頂く様お願い致します。
LDRで全文取得表示するグリモン便利だなぁ。
LDR Full Feed 0.0.6
gをぺちって押してLDRで全部全文読んじゃおう。
「g」押すだけなんて!!!
でもLDR使ってない!!!

って事で、Google Readerにサクサクっと移植してみました。
そんなに大きな変更してません。SITEINFOも同じ場所を使っています。
オリジナル作者様、もし同じSITEINFOを見る事に問題があれば、ご連絡下さい。
即効でグリモン毎、削除させて頂きます。


操作は同じく、[G]のアイコンが付いてたら「g」を押下で全文取得。
とりあえず、dankogai氏のサイトでチェックして問題なさげ。

ライセンスはLDR Full Feedに委ねます。

ダウンロード:googlereaderfullfeed.user.js

googlereaderfullfeed
dankogai氏のサイトの全文です。dankogai氏問題があればご連絡下さい。
キャプチャを削除させて頂きます。
Posted at by



2008/03/07


昨日のcurlの場合と対してやってる事は変わらない。
perlならばcpanの「Net::Google::GData」とか使った方がいいかもしれない。
ただ、まだ「Net::Google::GData」は使ったこと無い。
でも↓くらいのほうが見通し良い。

昨日のcurlの場合と同様に、GoogleLoginに使用する「auth」を取得してサービスコード「cp」に一覧を要求している。
#!/usr/bin/perl

use strict;
use LWP::UserAgent;
use HTTP::Request::Common qw(GET POST);
use URI::Escape qw(uri_escape);
use XML::LibXML;
use Data::Dumper;

my $gdata_user='your-account@gmail.com';
my $gdata_pass='your-password';

my $url = 'https://www.google.com/accounts/ClientLogin';
my %postdata = (
    Email => $gdata_user,
    Passwd => $gdata_pass,
    accountType => 'GOOGLE',
    source => 'Google-Contact-Lister',
    service => 'cp',
);
my $req = POST($url, [%postdata]);
my $ua = LWP::UserAgent->new;
my $res = $ua->request($req);

my $google_auth = (split(/\n/, $res->content))[2];
$google_auth =~ s!^Auth=!!;

$url = 'http://www.google.com/m8/feeds/contacts/' .
    uri_escape($gdata_user) . '/base';

$req = GET($url, [%postdata]);
$req->header(
    Authorization => "GoogleLogin auth=$google_auth",
);
$res = $ua->request($req);
my $x = XML::LibXML->new;
my $doc = $x->parse_string($res->content)->documentElement;
my @nodes = $doc->getElementsByTagName('entry');
for my $node (@nodes) {
    my @title = $node->getElementsByTagName('title')->[0]->getChildNodes();
    my $gd = $node->getElementsByTagName('gd:email')->[0];
    print $title[0]->toString() . "," . $gd->getAttribute('address') . "\n"
        if @title && $gd;
}
実行結果は、めちゃめちゃ非公開なものなので、出せません><
CSV形式で名前とメアドが一覧されます。

AWSWORD:perl:
Posted at by




追記1
おもいっきり間違ってる。
後で直します。
追記2
直した。

vimも負けません。
そろそろ FizzBuzz に飽きた - にぽたん研究所
Lingua::JA::Numbers無いなら作ります。
もちろんHiraganaだけですが...
scriptencoding utf-8

silent! unlet s:ndg
let s:ndg = [
  \ {0: ''},
  \ {0: 'じゅう'},
  \ {0: 'ひゃく', 3: 'ぴゃく', 6: 'ぴゃく', 8: 'ぴゃく'},
  \ {0: 'せん', 3: 'ぜん'},
\ ]

silent! unlet s:dig
let s:dig = [
  \ {0: ''},
  \ {0: 'まん'},
  \ {0: 'おく'},
  \ {0: 'ちょう'},
  \ {0: 'けい'},
  \ {0: 'がい'},
  \ {0: 'じょ'},
  \ {0: 'じょう'},
  \ {0: 'こう'},
  \ {0: 'かん'},
  \ {0: 'せい'},
  \ {0: 'さい'},
  \ {0: 'ごく'},
  \ {0: 'こうがしゃ'},
  \ {0: 'あそうぎ'},
  \ {0: 'なゆた'},
  \ {0: 'ふかしぎ'},
  \ {0: 'むりょうたいすう'}
\ ]

silent! unlet s:num
let s:num = [
  \ {0: 'ぜろ'},
  \ {0: 'いち'},
  \ {0: 'に'},
  \ {0: 'さん'},
  \ {0: 'よん'},
  \ {0: 'ご'},
  \ {0: 'ろく', 3: 'ろっ'},
  \ {0: 'なな'},
  \ {0: 'はち', 3: 'はっ', 4: 'はっ'},
  \ {0: 'きゅう'}
\ ]

" return japanese numeric string from 'num'
function! s:num2ja(num)
  let str = "" . a:num
  let len = len(str)
  if str == 0
    return s:num[0][0]
  endif
  if len >= 17*4+1
    return s:dig[17][0]
  endif
  let n = 0
  let ret = ""
  while n < len
    if str[n] != 0 && (str[n] != 1 || (len-n)%4 == 1)
      let ret .= has_key(s:num[str[n]], len-n) ?
        \ s:num[str[n]][len-n] : s:num[str[n]][0]
    endif
    if str[n] != 0
      let ret .= has_key(s:ndg[(len-n-1)%4], str[n]) ?
        \ s:ndg[(len-n-1)%4][str[n]] : s:ndg[(len-n-1)%4][0]
      let ret .= has_key(s:dig[(len-n-1)/4], str[n]) ?
        \ s:dig[(len-n-1)/4][str[n]] : s:dig[(len-n-1)/4][0]
    endif
    "echo ret
    let n = n + 1
  endwhile
  return ret
endfunction

" generate array from 'start' to 'end'
function! s:gen_array(start, end)
  let ret = []
  let n = a:start
  while n <= a:end
    call add(ret, n)
    let n = n + 1
  endwhile
  return ret
endfunction

for n in s:gen_array(1, 40)
  echo (!(n % 3) || n =~ '3') ? s:num2ja(n) : n
endfor
期待する結果:

:so NabeAtzz.vim
1
2
さん
4
5
ろく
7
8
きゅう
10
11
じゅうに
じゅうさん
14
じゅうご
16
17
じゅうはち
19
20
にじゅういち
22
にじゅうさん
にじゅうよん
25
26
にじゅうなな
28
29
さんじゅう
さんじゅういち
さんじゅうに
さんじゅうさん
さんじゅうよん
さんじゅうご
さんじゅうろく
さんじゅうなな
さんじゅうはち
さんじゅうきゅう
40

dankogai++ nipotan++ bram++
Posted at by



2008/03/06


ちょっと目から鱗な物みつけた。
Google Code FAQ - Using cURL to interact with Google data services
curlを使ってコマンドラインからGDataにアクセスする方法。
濃いなぁ...苦笑。手順を追って説明してくれています。色々書かれていますが以下ではPicasaWebAlbumに画像をアップロードするまでの簡単な手順を示して行きます。
まず https://www.google.com/accounts/ClientLogin
にEmail、Passwd、accountType、source、serviceをパラメータ指定してGDataへのAuthトークンを取得しに行きます。
ここで返って来るのは3行程のデータで
SID=XXXXXX
LSID=YYYYYY
Auth=ZZZZZZ
といった物が返ってきます。
認証に必要なのは最後の「Auth」です。以降のコマンドライン操作ではこの Authorization: GoogleLogin auth=ZZZZZZ
というヘッダを加えてやれば、GDataのやり取りが出来る様になります。
まず、Authを取得する為に以下の様なコマンドを発行します。
curl -s https://www.google.com/accounts/ClientLogin \
    -d Email=xxxxx@your-google-account.com \
    -d Passwd=YoUr-GoOgLe-PaSsWoRd \
    -d accountType=GOOGLE \
    -d source=Google-Picssa-Upload \
    -d service=lh2 | grep ^Auth= | sed 's/^Auth=//'
これで上記「Auth=ZZZZZZ」でいうZZZZZZの部分が取得出来ます。lh2は「Google Code FAQ - What is the service name in ClientLogin for each Data API?」にも書いてある通りPicasaWebAlbumのサービスコードになります。
そしてPicasaへ画像をアップするには以下の様なコマンドを発行します。
curl -s http://picasaweb.google.com/data/feed/api/user/default \
    -X POST \
    --data-binary "@my_picture.jpg" \
    -H "Content-Type: image/jpg" \
    -H "Slug: my_picture.jpg" \
    -H "Authorization: GoogleLogin auth=ZZZZZZ"
Slugヘッダはファイル名を指します。
後はこれを汎用的にシェルスクリプトへ...
#!/bin/sh

# include 'GDATA_USER' and 'GDATA_PASS'.
. ~/.gdata

GDATA_AUTH=`
curl -s https://www.google.com/accounts/ClientLogin \
    -d Email=$GDATA_USER \
    -d Passwd=$GDATA_PASS \
    -d accountType=GOOGLE \
    -d source=Google-Picssa-Upload \
    -d service=lh2 | grep ^Auth= | sed 's/^Auth=//'
`
FILE=`basename $1`

curl -s http://picasaweb.google.com/data/feed/api/user/default \
    -X POST \
    --data-binary "@$1" \
    -H "Content-Type: image/jpg" \
    -H "Slug: $FILE" \
    -H "Authorization: GoogleLogin auth=$GDATA_AUTH" > /dev/null
こんな感じでしょうか...
実行には「$HOME/.gdata」に GDATA_USER=xxxxx@your-google-account.com
GDATA_PASS=YoUr-GoOgLe-PaSsWoRd
を書いておく必要があります。
パスワードが書かれていますからパーミッションに注意
これで引数に指定されたJPGファイルをPicasaWebAlbumのドロップボックスにアップロードされる様になっています。
もう少し凝れば、タグを付けたりアルバムを作ったりと出来そうです。またPicasaWebAlbumに限らずGDataを扱う物であれば、ちょっとした事にcurlを使えそうです。

色々やって見たいですが、今日はこの辺でおひらき。

なおC言語上の実装例ですが、CodeReposに上げてあるBlogWriterのコードAtomPP.cppで、Bloggerへのログインとポストしている部分も参考になるかもしれません。
「getBloggerAuth」あたり
コードめちゃめちゃ汚いですが...

Posted at by



2008/03/05


RPC-XML-Parser-LibXML をつくってみた - TokuLog 改め だまってコードを書けよハゲ
http://svn.coderepos.org/share/lang/perl/RPC-XML-Parser-Lite/

RPC::XML::Parser というのがあり、これは expat ベース。RPC::XML::Parser::XS というのがあるのだが、これは libxml を直接つかった XS なのだ。

さらにRPC::XML::Parser::LibXML も要コンパイルなのだ。

というわけで、ためしに XML::Parser::Lite::Tree と XML::Parser:Lite::Tree::XPath をつかったものを作ってみた。機能的には一緒 & インターフェースもいっしょにしてみた。

ただ、perlへたっぴなので誰か添削ぷりーず

※要は、id:tokuhirom のパクリなのだ。

Posted at by




一つの時代が終わるのかな。
リチャード・ストールマンが Emacs のメンテナを引退 - YAMDAS現更新履歴
Looking for a new Emacs maintainer or team
vimではBram氏がメンテナであり、Bram氏がGoogleに移籍してからも尚、ワンマンな体制をとり続けている。
パッチを含む全てのリリースからリリースアナウンスまで全てをBram氏が行う。
パッチは皆vim-devに送るけれど、Bram氏がOKを出さない限り取り込まれない。
これまで、ほぼ例外なくそうだった。
Bram氏は引退について考えた事、あるんだろうか。
他のプロジェクトの様にBram氏の右腕とか、作らないんだろうか...

vimってBram氏引退したらどうなるんだろ...
今まで突飛な機能でも良いと思った機能は取り込んでくれる様なリーダー、マルチバイトに関する物は率先して取り組んでくれてる様なリーダーが、もし他の人に変わったら...とちょっとだけ心配になった。
Posted at by