2008/12/22


scaffoldで客寄せするのも悪くない...私はそうは思うんです。
scaffoldがあるから何秒でwebアプリケーションができました風の自慢はやめた方がいいと思う - val it : α → α = fun

この観点からいうと、scaffoldというのはつまりHQ9+である。

http://www.jmuk.org/diary/2008/12/20/1
確かに釣りな記事を書いた事には間違いないです。が、私が思うscaffoldの認識と少し違うかなと思ったので...

scaffoldはそもそも足場という意味の単語で、アプリケーションフレームワーク上では何かのベースとなる部品を提供する機能を一般的に指しているのだと思う。確かに「××分で出来る」ってのはbuzzword的に広まっており、「rails 分で出来る」なんてググると、わんさか出てきたりします。それだけインパクトがあり、需要があってこそなんだと思います。
mukaiさんの仰っておられるのは、「scaffoldで出来上がったデモがその後のユーザにとって本当に意味のある物になるのか...無駄じゃないのか」という事だと私は読み取ったのですが、巷のIDEの様に幾らか雛を作ってくれる物は初級者にとって敷居を下げてくれ、マニュアルやフレームワーク自身のコード解析からフレームワークの使い方を学ぶ手間と比べると、改造ベースからコーディングを始められる良さってのはあると信じています。
これは使い手側の意識と、先人が思う考察の違いの問題であって、例えばブログを書くのにWYSIWYGなツールを使う事は、HTMLを学べないから良くないとか、HTMLを知らなくてもHTMLを書けるWYSIWYGエディタの自慢をする事は良くないとか、そんな事はユーザにとってどうでもいい事の様な気がするんです。HTMLなんて知りたくないけどWYSIWYGは便利だよって人もいるだろうし、Google App Engineのマスタになりたいなんて思わないけどGoogle App Engineを使ってみたいって人はいると思う。
ユーザが開発者で、Google App Engineを学びたい人であって、Google App Engine Oilを知らなくて、何から手っ取り早くGoogle App Engine Oilで何が出来るかを知るには、多少特化したエッセンスを用いて表すのもアリなんじゃないかと。

例えば「10分で出来る、かんたん餃子レシピ」なんて物があって「その後本当に餃子の皮から作りたい人が、果たしてこのレシピを有用と見るだろうか」なんて考えるのはナンセンスな気がするんです。
調べる人は自分で調べるだろうし、「かんたん餃子レシピ」の役目は「餃子を自分で作ってみたいけど、皮から作るなんて難しい」と思ってる人への道しるべになれたのならそれで十分だと思う。そして本当に10分で美味しい餃子が出来たのなら、私はそのレシピを評価してあげて良いと思う。
Google App Engine Oilは、素のGoogle App Engineでは痒い所に手が届かない部分に色々と機能を追加していってくれていますし、良い意味でも悪い意味でもキャッチーです。
WSGIってどう書けば良いの?モデルを作ってそのモデルを編集する画面ってどう作ればいいの?って人は、まずサンプル触るのが良いと思うんですよね。
「そのフレームワークを学ぶ」が目的じゃなくて、「○○みたいなの作るにはどうしたら良いの?」が目的な人多いと思うんですよね。

これはあくまで私の持論であって、「自慢がいけない」のか「scaffoldがいけない」のかの違いも本筋でもない気がしています。

私は餃子を作りたいと思った時に「餃子のプロが見せる超絶技巧100」よりは「10分で出来る、かんたん餃子レシピ」を見たい人もいると思うんですよね。

ちなみに、お店で出すメニューに「10分で出来る、かんたん餃子レシピ」で作った餃子を出して自慢する...なんて事はもちろん、私も(その店にとって)良くない事だと思っていますよ。:-)
Posted at by



2008/12/18


Perlerと呼ばれる程のスキルは無いのですが...
Perlプログラマーの皆さん - Perl入門~サンプルコードによるPerl入門~

Perlをやっている人のブログをRSSリーダーに登録しておくと最新の事情がわかったりします。

http://d.hatena.ne.jp/perlcodesample/20081217/1229522517
Plaggerで上記リンクからOPML作るYAML書きました。
perler-in-japan.yaml
plugins:
  - module: Subscription::XPath
    config:
      url: http://d.hatena.ne.jp/perlcodesample/20081217/1229522517
      xpath: //div[@class="body"]/div[@class="section"]/blockquote/p/a

  - module: Publish::OPML
    config:
      filename: perler-in-japan.opml
出来上がったOPMLも置いておきます。2008/12/17 14:30:00時点のOPMLです。
Plagger Subscriptions - Perler in Japan
よろしければどうぞ。
Posted at by



2008/12/17


いつも面倒臭いなぁと思いながらブラウザでログインしてバージョン書いたりしてたんですが、勢い余ってスクリプトを書いてしまいました。
使い方は # vim-release.pl -sv=<スクリプトのバージョン> -vv=<vimのバージョン> -msg=<メッセージ> <ファイル>
です。-vvを省略すると7.0が、-msgを省略すると入力プロンプトが表示されます。ユーザおよびパスワードはConfig::Pitで管理します。
実際には、このスクリプトの-svを省略する為の補助が欲しい所ですが、前のバージョンから+1では不味いし、スクリプトから調べるのは無理があるし...で引数にしました。
おそらく私くらいしか使わないかと思いますが、よろしければどうぞ。
gist: 36989 — GitHub
#!perl
use strict;
use warnings;
use Config::Pit;
use Getopt::Long;
use WWW::Mechanize;
use Perl6::Say;

my $conf = pit_get("vim.com", require => {
  "username" => "your username on vim.org", # NOTE: do not edit this line
  "password" => "your password on vim.org", # NOTE: do not edit this line
});

my %args = ( id => '', sv => '', vv => '7.0', msg => '' );
die "invalid args"
  unless GetOptions(\%args, 'id=i', 'sv=s', 'vv=f', 'msg=s');
unless ($args{msg}) {
  print "message: ";
  $args{msg} = <STDIN>;
}

my $file = shift;
die "script version not specified" unless $args{id};
die "message not specified" unless $args{msg};
die "file not specified" unless $file;

my $mech = WWW::Mechanize->new;
$mech->get('http://www.vim.org/login.php');
$mech->submit_form(
    form_name => 'login',
    fields    => {
        userName => $conf->{username},
        password => $conf->{password},
    },
);

$mech->get("http://www.vim.org/scripts/add_script_version.php?script_id=$args{id}");
$mech->form_name('script');
$mech->field(script_file => $file);
$mech->select('vim_version', $args{vv});
$mech->field(script_version => $args{sv});
$mech->field(version_comment => $args{msg});
my $res = $mech->click('add_script');
say $res->is_success ? "uploaded" : $res->status_line;
Gist.vim version 1.1 への更新で上手く動いてそげです。
Posted at by




最近Githubをよく使っているのですが、どうしてもGithubといえばプロジェクト管理なイメージがあり、プロジェクトを持ってない人から見ると少し遠い存在にみえがちです。ですが、ちょっとしたスクリプトを書きたい人、しかもリモートにもリポジトリを置きたい人いますよね。
そんな時に便利なのがGistです。
「Gistというと、単なるコードスニペットサービスでしょ?」と思われるかもしれませんが、Gistの凄いところはちょっとしたスクリプトをソース管理出来る所です。
以下、簡単なperlスクリプトをソース管理しながら完成まで仕上げる過程を例を持って示してみます。

ただし、ここで言っておきたい事が一つ。Gistを便利に使う為のツール、Gistyを入れるべきです。

GistyはGistにポストすると同時に所定の場所にgit cloneしてくれ、初期ポストからコーディング開始までをスムーズに促してくれます。

ファイル名を決める

まぁ、やりたい事が見つかっているならば、既にファイル名は決まっているでしょうね。
% vi cookpad.pl
% cat cookpad.pl
#!perl
use strict;
use warnings;
出来てなくても良いです。とりあえずpostしましょう。
なお、Gistyでポストする前には、cloneするディレクトリを環境変数GISTY_DIRに設定しておきましょう。
% gisty post cookpad.pl
恥ずかしいならばprivate postでもいいでしょう。
% gisty private_post cookpad.pl
Initialized empty Git repository in /home/mattn/gisty/aea6b5797ba2b83a6dc8/.git/
Enter passphrase for key '/home/mattn/.ssh/id_rsa':
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.

% ls /home/mattn/gisty
118f0658700dce0c48df

%
このポストが成功した時点で、環境変数GISTY_DIRで設定したディレクトリに「aea6b5797ba2b83a6dc8」というディレクトリが出来ています。作ったファイルは実はもう要りません。出来たディレクトリに移動して作業を開始します。
なお、先日ご紹介した「Big Sky :: SSHポートが通らなくてもgithub.comにpushする方法」に付け足して"~/.ssh/config"に Host gist.github.com
    User git
    Hostname ssh.github.com
    Port 443
    IdentityFile /home/mattn/.ssh/id_rsa
    #IdentityFile /home/mattn/.ssh/id_rsa.ppk
    TCPKeepAlive yes
    IdentitiesOnly yes
としておけば、SSHポートが通らなくてもGistをclone/push出来ます。

コーディングする

ま、ここは人によりいろいろですね。私の場合は
#!perl
use strict;
use warnings;
use utf8;
use URI;
use Encode;
use Perl6::Say;
use Web::Scraper;

if ( $^O eq "MSWin32" ) {
    binmode STDOUT, ":encoding(cp932)";
    @ARGV = map { Encode::decode( "cp932", $_ ) } @ARGV;
}
my $menu = shift || '味噌汁';

my $recipes = scraper {
    process 'div.recipe-text', 'recipes[]' => scraper {
        process 'a.recipe-title', title => 'TEXT',
        process 'a.recipe-title', url => '@href',
    };
    result 'recipes';
}->scrape( URI->new("http://cookpad.com/レシピ/$menu") );

say $_->{title}, "\n\t", $_->{url} for ( @{$recipes} );
こんな感じに仕上げました。
その都度、何度か # git commit -a
してcommitしています。

リモートにも反映させる

# git push origin master
Enter passphrase for key '/home/mattn/.ssh/id_rsa':
Counting objects: 5, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 617 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@gist.github.com:aea6b5797ba2b83a6dc8.git
   af1ff17..24c4fd9  master -> master
private postじゃない場合なら、誰かがforkして改良してくれるかもしれませんね。

Github/Gist、Gistyって便利

コーディングの時間を除くと、Gistへの登録やGitでの作業に費やした時間はほぼ十数秒しかありませんでした。
個人のスクリプトをgitでソース管理してくれるGithub、そしてGistを便利にしてくれるGistyすばらしいですね。

皆さんもちょっとしたスクリプトの作成をGist/Gistyから始めてみませんか。
Posted at by



2008/12/16


GAEOにScaffoldのジェネレータが付いた。どれだけ速くアプリケーションを作れるか。

アプリケーションを作る

# gaeo.py gaeotter
The "gaeotter" project has been created.

# cd gaeotter

Scaffoldを作る

# gaeogen.py scaffold posts index new show user:StringProperty(required=True) comment:StringProperty(required=True)
Creating Model posts ...
Creating .../gaeotter/application/model/posts.py ...
Creating .../gaeotter/application/templates/posts ...
Creating .../gaeotter/application/templates/posts/index.html ...
Creating .../gaeotter/application/templates/posts/new.html ...
Creating .../gaeotter/application/templates/posts/show.html ...
Creating Controller posts ...
Creating .../gaeotter/application/controller/posts.py ...

テンプレートを少しだけ編集する

# vim application/templates/posts/index.html
# cat !$
<h1>PostsController#index</h1>
<a href="/posts/new">New</a>
<ul>
{% for rec in result %}
    <li><a href="/posts/show?key={{ rec.key }}">{{ rec.comment|escape }}</a> by {{ rec.user|escape }}</li>
{% endfor %}
</ul>

# vim application/templates/posts/show.html
# cat !$
<h1>PostsController#show</h1>
<p>user: {{ user|escape }}</p>
<p>comment: {{ comment|escape }}</p>


起動する

# dev_appserver.py gaeotter
INFO     2008-12-16 05:03:03,546 appcfg.py] Server: appengine.google.com
INFO     2008-12-16 05:03:03,937 appcfg.py] Checking for updates to the SDK.
INFO     2008-12-16 05:03:04,437 appcfg.py] The SDK is up to date.
INFO     2008-12-16 05:03:05,062 dev_appserver_main.py] Running application gaeo
tter on port 8080: http://localhost:8080
# firefox http://localhost:8080/posts/
gaeo-example001

gaeo-example002

gaeo-example003


結論

Google App Engine Oilすばらしい。
Posted at by



2008/12/05


久々Plaggerネタ。めんどくさいので説明なしで...
スクリプト
hatena-news.pl use strict;
use warnings;
use utf8;
use Web::Scraper;
use URI;

my $uri = URI->new( "http://news.hatelabo.jp/" );
my $entry_scanner = scraper {
    process 'h1.article', summary => 'TEXT';
    process 'div.section', body => 'RAW';
};

my $scanner = scraper {
    process '//td[.//span[text()="主なニュース"]]//ul/li',
        'entries[]' => scraper {
            process 'a',
                title => 'TEXT',
                link => '@href',
                info => sub {
                  $entry_scanner->scrape(
                      URI->new_abs( $_->attr('href'), $uri )
                  );
                }
        };
   result 'entries';
};

my $feed = {
    title => 'はてなニュース',
    link  => $uri->as_string,
};

for my $entry (@{ $scanner->scrape( $uri ) }) {
    push @{$feed->{entries}}, {
        title   => $entry->{title},
        link    => $entry->{link},
        summary => $entry->{info}->{summary},
        body    => $entry->{info}->{body},
    };
}

use YAML;
binmode STDOUT, ":utf8";
print Dump $feed;

hatena-news.yaml global:
  log:
    level: error
plugins:
  - module: Subscription::Config
    config:
      feed:
        - script:///path/to/hatena-news.pl
  - module: CustomFeed::Script
  - module: Publish::Feed
    config:
      dir: /path/to/hatena-news
      filename: hatena-news.rss
      format: RSS
※Windowsで動かす人は環境変数PATHEXTを以下の様にしておく必要あり
set PATHEXT=%PATHEXT%;.PL

あと、出力先はご自由にPublish::なんちゃらで...
ま、その内フィード出来るだろけど。
Posted at by



2008/12/03


毎回良い物作るなぁ。
汎用ダウンローダっぽいのが欲しい - 冬通りに消え行く制服ガールは、夢物語にリアルを求めない。 - subtech http://subtech.g.hatena.ne.jp/cho45/20081202/1228220280
私がやったのはopen3の実装をwin32/open3に変えただけの簡単なお仕事です。
ただ、今非win32な環境が無いので元の動作が確認出来てません。動作に違いがあるのかもしれません。
両方試して動作が違っていればご報告下さい。
mattn's middown at master — GitHub http://github.com/mattn/middown/tree/master
安定すれば作者のcho45さんにpull requestしたいと思います。

ところで今回知ったのですが、rubyってshebangが"#perl"でもちゃんと処理してくれるんですね。
Posted at by



2008/11/21


追記2
動いた。どうやらjQuery lightboxと併用するとCPU100%になったり、lightboxのオーバーレイが変になったりするみたい。
調べた所、lightboxのdomReadyよりも先にTypePad Connectのjavascriptが読み込まれるとまずい様なので $(function () {
    $.getScript("http://profile.typepad.com/services/embed/tpc/{your-id}/embed.js", function() {
        $.getScript("http://static.typepad.com/.shared/js/profile/blogside.js");
    });
});
この様にlightboxのdomReadyよりも後に読み込むように修正した。とりあえず動いている。
コメント歓迎です!

ただIEだとエラー出るなぁ...
unknown-yahoo
って事でIE以外の人、コメントよろしく!

さらに追記。Google ChromeやSafariでも重くなる。よってFirefoxとOperaだけ!
追記1
なぜだか、コメントが0件場合にCPUが100%になる現象が見られたので、一時的に外しました。
日を改めて調査し、出来れば導入したいと思います。

blosxomなので、flavourにそのまま貼り付け。
コードの生成はこちらから
Connect a blog - TypePad

Choose a blog to have TypePad Connect installed on

http://www.typepad.com/connect/site/blogs/create
↓の方にコメント欄ぽいのがあるので、よかったらコメント付けて見て下さい。

関連URL: YappoLogs: TypePad Connect Betaつけた
Posted at by



2008/11/20


追記

svn/trunkに取り込まれました。


ちょっとしたパッチですが...
まずエビデンス
remedie-ie6

そしてパッチ
Index: root/static/js/remedie.js
===================================================================
--- root/static/js/remedie.js   (revision 207)
+++ root/static/js/remedie.js   (working copy)
@@ -12,7 +12,7 @@
   current_id: null,
 
   initialize: function() {
-    if (!jQuery.browser.safari && !jQuery.browser.mozilla) {
+    if (!jQuery.browser.safari && !jQuery.browser.mozilla && !jQuery.browser.msie) {
       alert("Your browser " + navigator.userAgent + " is not supported.");
       return;
     }
@@ -170,7 +170,7 @@
           } else {
             alert(r.error);
           }
-        },
+        }
       });
     }
 
@@ -217,7 +217,7 @@
         dataType: 'json',
         success: function(r) {
           item.props.embed = { code: r.code };
-        },
+        }
       });
     }
 
@@ -377,20 +377,20 @@
         } else {
           alert(r.error);
         }
-      },
+      }
     });
   },
 
   newChannelDialog: function() {
     $.blockUI({
-      message: $("#new-channel-dialog"),
+      message: $("#new-channel-dialog")
     });
     return false;
   },
 
   importDialog: function() {
     $.blockUI({
-      message: $("#import-opml-dialog"),
+      message: $("#import-opml-dialog")
     });
     return false;
   },
@@ -443,7 +443,7 @@
         } else {
           alert(r.error);
         }
-      },
+      }
     });
     return false;
   },
@@ -484,7 +484,7 @@
           'div', { id: 'channel-items', className: "clear" }, null
         );
 
-        r.items.forEach(function(item) {
+        $.each(r.items, function(index, item) {
           remedie.items[item.id] = item;
           $("#channel-items").createAppend(
            'div', { className: 'channel-item channel-item-selectable', id: 'channel-item-' + item.id  }, [
@@ -504,7 +504,7 @@
                ],
                'h3', { id: 'channel-item-title-' + item.id,
                        className: item.is_unwatched ? 'channel-item-unwatched' : '' }, item.name,
-               'p', { className: 'item-infobox-description' }, item.props.description
+               'div', { className: 'item-infobox-description' }, item.props.description
              ],
              'div', { className: "clear" }, null
            ]
@@ -629,7 +629,7 @@
           $.event.trigger('remedieChannelUpdated', channel); // Fake updated Event to cancel animation
           alert(r.error);
         }
-      },
+      }
     });
   },
 
@@ -653,7 +653,7 @@
         } else {
           alert(r.error);
         }
-      },
+      }
     });
   },
 
@@ -665,7 +665,7 @@
       dataType: 'json',
       success: function(r) {
         $("#collection").children().remove();
-        r.channels.forEach(function(channel) {
+        $.each(r.channels, function(index, channel) {
           remedie.channels[channel.id] = channel;
           remedie.renderChannelList(channel, $("#collection"));
           remedie.redrawUnwatchedCount(channel);
@@ -769,5 +769,5 @@
       message.children("a.command-unblock").click($.unblockUI);
       $.blockUI({ message: message });
       return false;
-  },
+  }
 };
Index: Makefile.PL
===================================================================
--- Makefile.PL (revision 207)
+++ Makefile.PL (working copy)
@@ -21,6 +21,7 @@
 requires 'String::CamelCase';
 requires 'URI';
 requires 'XML::RSS::LibXML';
+requires 'Image::Info';
 
 build_requires 'Test::More';
 use_test_base;
単なるカンマ取りと、一部タグをpからdivに変えないと動かなかったのを修正したくらい。あとImage::Infoが無いと言われたのでMakefile.PLのrequiresに足しただけ。

今の所、私んところのIE7でもご機嫌良く動いてる。
Posted at by



2008/11/13


Gist GitHubが提供するコードスニペットサービス(という言い方で良いのかな?)、「Gist」をvimから扱えるvimscript「Gist.vim」を書いた。
内部はcurlコマンドを使っており
  • 一覧
  • 閲覧
  • ポスト
  • プライベートポスト
が出来る様になっています。導入にはcurlコマンドとgitコマンドが必要です(コマンドライン版gistコマンドやrubyは必要ありません)。ただし、グローバル変数に"github_user"と"github_token"さえ設定していればgitコマンドも必要ありません。
使い方は、適当にバッファを開いて :Gist
とすればポストされ、公開URLがコマンドライン上に表示されます。
またビジュアル選択して :'<,'>Gist
で部分的にポストする事も出来ます。なお引数として :Gist -p
もしくは--private
を指定するとprivateモードでポストします。 GistのIDが分かっている場合には :Gist XXXXX
とID指定でgistを開く事も出来ます。
さらに :Gist -la もしくは--listall
で全ユーザの最新gist一覧 :Gist -l mattn もしくは--list mattn
でユーザ指定一覧、といった使い方も出来ます。

現在、github上で開発を進めておりvim.orgでも公開しています。
よかったら使ってみて下さい。

Posted at by




id:tokuhiromが良い物作ってくれたので、それを使ったブログエンジン書いてみた。
MENTA というウェブアプリケーションフレームワークをかいてみた - TokuLog 改めB日記

「CGI 用のウェブアプリケーションフレームワークにはどういうものが最適か」という問いに対する自分なりの解答。

http://d.hatena.ne.jp/tokuhirom/20081111/1226418572

名前は、「MENTOS(メントス)」。
mentos-weblog-engine
実質コードは以下の量くらい。

sub read_entry {
    my $file = shift;
    my $pubdate = strftime("%y-%m-%d %H:%M:%S", localtime((stat $file)[9])),
    my $content = read_file($file);
    $file =~ s!.*?([^/]+)\.txt$!$1!;
    $content =~ /^([^\n]+)\n\n(.*)/ms;
    return {
        id => $file,
        title => $1,
        pubdate => $pubdate,
        description => $2,
    }
}

# あなたのプログラム
sub do_index {
    my $id = param('id') || '';
    my $data_dir = config()->{application}->{data_dir};

    if ($id =~ /^(\d+)$/) {
        render('entry.html', read_entry("${data_dir}/${id}.txt"));
    } else {
        my @entries;
        for my $file (glob("${data_dir}/*.txt")) {
            push @entries, read_entry($file);
        }
        render('entries.html', \@entries);
    }
}

sub do_edit {
    my $id = param('id') || '';
    my $data_dir = config()->{application}->{data_dir};
    my $msg = '';
    my $entry = {};

    if ($ENV{'REQUEST_METHOD'} eq 'POST') {
        $entry->{id} = param('id') || '';
        $entry->{title} = param('title') || '';
        $entry->{description} = param('description') || '';
        $entry->{title} =~ s!\r!!g;
        $entry->{description} =~ s!\r!!g;
        my $password = param('password') || '';
        my $admin_password = config()->{application}->{password};
        if ($entry->{id} =~ /^(\d+)$/ && $password eq $admin_password) {
            $entry->{id} = time unless $entry->{id};
            utf8::decode($entry->{title});
            utf8::decode($entry->{description});
            write_file("${data_dir}/${id}.txt", $entry->{title}."\n\n".$entry->{description});
            redirect(config()->{application}->{blog_url});
            return;
        } else {
            $msg = 'パスワードが違います';
        }
    } else {
        $entry = read_entry("${data_dir}/${id}.txt") if -f "${data_dir}/${id}.txt";
    }

    render('edit.html', $entry, $msg);
}
MENTAを使ってどれだけ小さなコード量でブログエンジンが出来上がるかを試して見たかっただけなので、ブログと言いながらカテゴリやコメント、トラックバック等はありません。一応編集機能は持ち合わせています。
コードはcodereposのこの辺に置いときますので、適当に弄って遊んで下さい。
暇があれば、id:yappoのYacafiや、id:kazuhoのNanoAでも作ってみたいなぁ。
Posted at by



2008/11/10


さらにちょこっと弄ってset-branch/commitが使える様にした。
Ditz の Mercurial プラグイン - ursmの日記

例によって Mercurial のはないみたいだったので、Git のものをちょちょいと手直しして作ってみました。

http://d.hatena.ne.jp/ursm/20080729/1217345141
これまたgit.rbからパクっただけですが... --- mercurial.rb.orig   2008-11-10 12:30:03.015625000 +0900
+++ mercurial.rb    2008-11-10 12:30:04.921875000 +0900
@@ -2,6 +2,7 @@
  
 module Ditz
   class Issue
+    field :hg_branch, :ask => false
     def hg_commits
       return @hg_commits if @hg_commits
       output = `hg log --template '{date|rfc822date}\t{author}\t{node|short}\t{desc|firstline}\n' #{pathname}`
@@ -45,5 +46,51 @@
 EOS
     end
   end
+  class Operator
+    operation :set_branch, "Set the hg feature branch of an issue", :issue, :maybe_string
+    def set_branch project, config, issue, maybe_string
+      puts "Issue #{issue.name} currently " + if issue.hg_branch
+        "assigned to hg branch #{issue.hg_branch.inspect}."
+      else
+        "not assigned to any hg branch."
 end
 
+      branch = maybe_string || ask("Mercurial feature branch name:")
+      return unless branch
+  
+      if branch == issue.hg_branch
+        raise Error, "issue #{issue.name} already assigned to branch #{issue.hg_branch.inspect}"
+      end
+  
+      puts "Assigning to branch #{branch.inspect}."
+      issue.hg_branch = branch
+    end
+  
+    operation :commit, "Runs hg-commit and auto-fills the issue name in the commit message", :issue do
+      opt :all, "commit all changed files", :short => "-a", :default => false
+      opt :verbose, "show diff between HEAD and what would be committed", \
+        :short => "-v", :default => false
+      opt :message, "Use the given <s> as the commit message.", \
+        :short => "-m", :type => :string
+    end
+  
+    def commit project, config, opts, issue
+  
+      args = {
+        :verbose => "--verbose",
+        :all => "--all",
+      }.map { |k, v| opts[k] ? v : "" }.join(" ")
+  
+      comment = "# #{issue.name}: #{issue.title}"
+      tag = "Ditz-issue: #{issue.id}"
+      message = if opts[:message]
+        "#{opts[:message]}\n\n#{tag}"
+      else
+        "#{comment}\n#{tag}"
+      end
+  
+      message = message.gsub("\"", "\\\"")
+      exec "hg commit #{args} --message=\"#{message}\""
+    end
+  end
+end
ちなみにmercurialとditzを使った開発方法はおおよそ以下の様な感じ。
# cd /path/to/my/repos/example
まず初期設定
# ditz init
# echo '- mercurial' >> .ditz-plugins
# cat > .ditz-config
--- !ditz.rubyforge.org,2008-03-06/config
mercurial_commit_url_prefix: .
^D
# ditz reconfigure
issueを追加
# ditz add
Title: 'foo' is not found in README.
Description (ctrl-d, ., or /stop to stop, /edit to edit, /reset to reset):
> need to add 'foo'
> .
Is this a (b)ugfix, a (f)eature, or a (t)ask? t
Issue creator (enter for "mattn <mattn.jp@gmail.com>"):
Comments (ctrl-d, ., or /stop to stop, /edit to edit, /reset to reset):
> .
Added issue example-1.
You may have to inform your SCM that the following files have been added:
  .ditz/issue-5486bdc5eeb7c4f6c2fc784a4a7c41d949d0791e.yaml

ソースを修正する
# echo 'foo' >> README
# ditz close example-1
Closing issue example-1: 'foo' is not found in README..
Choose a disposition:
  1) fixed
  2) won't fix
  3) reorganized
Disposition (1--3): 1
Comments (ctrl-d, ., or /stop to stop, /edit to edit, /reset to reset):
> added 'foo'
> .
Closed issue test2-2 with disposition fixed.
You may have to inform your SCM that the following files have been modified:
  .ditz/issue-5486bdc5eeb7c4f6c2fc784a4a7c41d949d0791e.yaml

コミットする
# ditz commit
summary付でコミットされている
# hg log
changeset:   6:720dc3bf4ef9
tag:         tip
user:        mattn <mattn.jp@gmail.com>
date:        Mon Nov 10 12:43:21 2008 +0900
summary:     # example-1: 'foo' is not found in README.
ditzの場合は、.ditzフォルダや.ditz-configもリポジトリに格納するのが通常の使い方みたいですね。

ところでditzってバッチっぽく動く様になればいろんなツールが出始めると思うのに勿体無い気がするなぁ。
Posted at by




最近のインターネットは便利になったもので、なんと美輪明宏のチンコがあるのかないのかを返してくれるWebAPIまである。インターネットリソースの大きさは今後もきっと拡大して行き、いずれはきっとGoogle Alertや、はてなアンテナ、はたまたEngadget Japanese等でのリーク流出等で実際の「あり/なし」が判明、本当に正しい結果が返って来る事になると思われる。

だがしかし、上記JSONサーバにはRSS/Feedが存在せず、実は「美輪明宏にはチンコがない」とユーザが初めて気付く為には、結果をメール等で通知する様なデーモンプロセスを稼働させてこのサービスを監視(ポーリング)しJSONをパースした結果としてユーザに通知するか、ユーザにとって使用頻度の高い乗用アプリケーションへチェック機能を付けユーザの操作(トリガ)でチェックを行う方法が選択肢となってしまう。たしかにポーリングは簡単に、かつ昼夜を問わず最新の情報を人手無しに監視し続ける事が出来る。
しかしながら、このサーバに負荷を掛けることは今後のインターネット発達が望まれる中、希少な有志の力を自ら塞ぎ込む形と成り得ない。
ここはトリガ形式を考えるのが適切である。

では、トリガ形式としてどんな常用アプリケーションが適切か。私が考えた結果がテキストエディタである。テキストエディタはユーザであればほぼ万人が使っている。今回私は、このテキストエディタの中でもギーク性が高いと言われるvimを選んだ。

まずは、何をもって「美輪明宏のチンコの有無」を確認するか。
テキストエディタであれば、入力補完がユーザのニーズに答えられる物になるだろう。
たとえば
美輪明宏にチンコは
まで入力し、<tab>を押下して「ある」もしくは「ない」が補完されたとすれば、それはかなり使い勝手のある物になると思われる。

運が良いことに、vimでは入力補完にフックメソッドを組み込む事が出来るため、今回私は、以下の様なソースを書いた。
func! s:CheckTimpo()
  " ここにデータ取得処理を入れる
endfunc
iabbr <silent> 美輪明宏にチンコは 美輪明宏にチンコは<C-R>=<SID>CheckTimpo()<CR>
入力I/Fに対するメソッド呼出部が完成した。
あとはJSONをどう解釈するか。ここで私にあるひらめきが。
たしかvim7のDictionaryは、JSON展開形式に似てたはず
そして以下のようなコードを書いた。 func! s:CheckTimpo()
  let ret = system("curl -s http://dzfl.jp/mojo/")
  let true = 'ある'
  let false = 'ない'
  exec "let json = " . ret
  return json['miwa']
endfunc
iabbr <silent> 美輪明宏にチンコは 美輪明宏にチンコは<C-R>=<SID>CheckTimpo()<CR>
以下にこのソースの解説を行う。
JSONサーバからは以下の形式でデータが戻される。
{ "miwa": true }
vim7ではDictionaryが導入されており、そのシンタックスは { key: value }
となる。つまりtrue/falseといった予約語を定義してやればJSON形式が扱える事になるのだ。
上記ソースではtrue/falseの定義を行った上でDictionaryへ値展開し、代入している。これによりvim7ではjsonという変数に対して json["miwa"]
とアクセス出来るのである。true/falseはそれぞれ「ある」/「ない」と定義している為、もし美輪明宏にチンコがあれば
json["miwa"] == "ある"
また、美輪明宏にチンコがなければ
json["miwa"] == "ない"
となるのだ。
この値をスクリプト内メソッドCheckTimpoの戻り値として返し、入力補完を行えるようになる。
以上の処理手順により、vim7を使っているならば何時でも
美輪明宏にチンコは
とまで入力し、補完トリガと成り得るようなキー(例えばスペース)を入力すれば
美輪明宏にチンコはある
と入力補完されるのである。

今回は、vimとJSONでマッシュアップを行ったが、これはあくまで一例に過ぎない。これを応用すれば「某国会議員がカツラなのか」、「モー娘のあの娘はタバコを吸っているかどうか」が、テキストエディタから補完出来るのである。

皆さんも、色んなマッシュアップを試して見てほしいと切に感じている。

以下、今回作成したソース「miwa.vim」を公開しておく。

ダウンロード:
注意:vimの入力補完は、全構文を途切れなく入力する必要がある。
Posted at by



2008/11/06


UN*X版だとssh-agentの実行結果が SSH_AUTH_SOCK=/tmp/ssh-suhGif2116/agent.2116; export SSH_AUTH_SOCK;
SSH_AGENT_PID=5672; export SSH_AGENT_PID;
echo Agent pid 5672;
みたいになっててログイン時に eval `ssh-agent`
とするんですがWindowsだと、そう簡単には行かない。 @echo off
if not "%SSH_AGENT_PID%" == "" goto end
for /f "eol=; tokens=1,2 delims==;" %%1 in ('ssh-agent.exe') do (
 if "%%1" == "SSH_AUTH_SOCK" set SSH_AUTH_SOCK=%%2
 if "%%1" == "SSH_AGENT_PID" set SSH_AGENT_PID=%%2
)
ssh-add
:end
開発を始める前にコマンドプロンプトで1回これを動かすとパスフレーズを聞かれ、1回入力すれば以降は聞かれない様になる(sshはmsysGitに含まれる物を使ってる)。 さらにWindowsサポートツールに入ってる"setx.exe"を使えば、ユーザ環境変数への反映も出来る為、新しく起動したコマンドプロンプトでsshコマンドがSSH_AGENT_PID/SSH_AUTH_SOCKを見てくれるようになってもっとウマーとなるはず(↑のバッチでsetしている所でさらにsetxもする必要があるかな)。

とりあえずこれで楽になった。ウマー
Posted at by



2008/10/29


追記 最新版はgithubで作ってます。
mattn's nicodown at master — GitHub
http://github.com/mattn/nicodown/tree/master


適当だけど書いてみた。
タイトル取って来る所はlibxml使うの面倒臭かったのでXMLパーサ使わずベタで(実態参照文字あると変になるので気を付けて)。Windowsの場合だけWin32 APIでシフトJISにファイル名を変換しています。
荒いコードなので色々直し所がありますが、サンプルって事で。
//#define CURL_STATICLIB
#include <curl/curl.h>

#define HEX_DIGITS "0123456789ABCDEF"
#define IS_QUOTED(x) (*x == '%' && strchr(HEX_DIGITS, *(x+1)) && strchr(HEX_DIGITS, *(x+2)))

static char* response_data = NULL;  /* response data from server. */
static size_t response_size = 0;    /* response size of data */

static void
curl_handle_init() {
    response_data = NULL;
    response_size = 0;
}

static void
curl_handle_term() {
    if (response_data) free(response_data);
}

static size_t
curl_handle_returned_data(char* ptr, size_t size, size_t nmemb, void* stream) {
    if (!response_data)
        response_data = (char*)malloc(size*nmemb);
    else
        response_data = (char*)realloc(response_data, response_size+size*nmemb);
    if (response_data) {
        memcpy(response_data+response_size, ptr, size*nmemb);
        response_size += size*nmemb;
    }
    return size*nmemb;
}

int
main(int argc, char* argv[]) {
    CURLcode res;
    CURL* curl;
    char error[256];
    char name[256];
    char data[1024];
    char* buf = NULL;
    char* ptr = NULL;
    char* tmp = NULL;
    FILE* fp = NULL;
    int status = 0;

    // usage
    if (argc != 4) {
        fputs("usage: nicodown [usermail] [password] [video_id]", stderr);
        goto leave;
    }

    // default filename
    sprintf(name, "%s.flv", argv[3]);

    curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
    curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error);
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_handle_returned_data);
    curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookies.jar");
    curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookies.txt");

    // login
    sprintf(data, "mail=%s&password=%s&next_url=/watch/%s", argv[1], argv[2], argv[3]);
    curl_handle_init();
    curl_easy_setopt(curl, CURLOPT_URL, "https://secure.nicovideo.jp/secure/login?site=niconico");
    curl_easy_setopt(curl, CURLOPT_POST, 1);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
    res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        fprintf(stderr, error);
        goto leave;
    }
    buf = malloc(response_size + 1);
    strcpy(buf, response_data);
    if (strstr(buf, "id=\"login_bar\"")) {
        printf("%s\n", buf);
        free(buf);
        fprintf(stderr, "failed to login\n");
        goto leave;
    }
    free(buf);
    curl_handle_term();

    // get video url, and get filename
    sprintf(data, "http://www.nicovideo.jp/api/getthumbinfo?v=%s", argv[3]);
    curl_handle_init();
    curl_easy_setopt(curl, CURLOPT_URL, data);
    curl_easy_setopt(curl, CURLOPT_POST, 0);
    res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        fprintf(stderr, error);
        goto leave;
    }
    buf = malloc(response_size + 1);
    strcpy(buf, response_data);
    ptr = strstr(buf, "<title>");
    if (ptr) {
        ptr += 7;
        tmp = strstr(ptr, "</title>");
        if (*tmp) {
            *tmp = 0;
            strcpy(name, ptr);
        }
#ifdef _WIN32
        {
            UINT codePage;
            size_t wcssize;
            wchar_t* wcsstr;
            size_t mbssize;
            char* mbsstr;

            codePage = CP_UTF8;
            wcssize = MultiByteToWideChar(codePage, 0, name, -1,  NULL, 0);
            wcsstr = (wchar_t*)malloc(sizeof(wchar_t) * (wcssize + 1));
            wcssize = MultiByteToWideChar(codePage, 0, ptr, -1, wcsstr, wcssize + 1);
            wcsstr[wcssize] = 0;
            codePage = GetACP();
            mbssize = WideCharToMultiByte(codePage, 0, (LPCWSTR)wcsstr,-1,NULL,0,NULL,NULL);
            mbsstr = (char*)malloc(mbssize+1);
            mbssize = WideCharToMultiByte(codePage, 0, (LPCWSTR)wcsstr, -1, mbsstr, mbssize, NULL, NULL);
            mbsstr[mbssize] = 0;
            sprintf(name, "%s.flv", mbsstr);
            free(mbsstr);
            free(wcsstr);
        }
#endif
    }
    free(buf);
    printf("downloading %s\n", name);
    curl_handle_term();

    // get video url
    sprintf(data, "http://www.nicovideo.jp/api/getflv?v=%s", argv[3]);
    curl_handle_init();
    curl_easy_setopt(curl, CURLOPT_URL, data);
    curl_easy_setopt(curl, CURLOPT_POST, 0);
    res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        fprintf(stderr, error);
        goto leave;
    }
    buf = malloc(response_size + 1);
    strcpy(buf, response_data);
    ptr = strstr(response_data, "url=");
    if (!ptr) {
        free(buf);
        fprintf(stderr, "failed to get video info\n");
        goto leave;
    }
    tmp = strstr(ptr, "&");
    if (tmp) *tmp = 0;
    tmp = ptr;
    while(*tmp) {
        if (IS_QUOTED(tmp)) {
            char num = 0;
            sscanf(tmp+1, "%02x", &num);
            *tmp = num;
            strcpy(tmp + 1, tmp + 3);
        }
        tmp++;
    }
    strcpy(data, ptr + 4);
    printf("URL: %s\n", data);
    free(buf);
    curl_handle_term();

    // download video
    fp = fopen(name, "wb");
    if (!fp) {
        fprintf(stderr, "failed to open file\n");
        goto leave;
    }
    curl_handle_init();
    curl_easy_setopt(curl, CURLOPT_URL, data);
    curl_easy_setopt(curl, CURLOPT_POST, 0);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
    res = curl_easy_perform(curl);
    fclose(fp);
    if (res != CURLE_OK) {
        fprintf(stderr, error);
        goto leave;
    }

leave:
    curl_handle_term();
    curl_easy_cleanup(curl);

    return 0;
}
確認はWindowsでしかしてませんが、きっとUN*Xでも動くはず。perlやpythonやruby使わない派の方は雛形として持ってって下さい。

UNIXプログラミングの道具箱: プロフェッショナルが明かす研ぎ澄まされたツール群の使いこなし UNIXプログラミングの道具箱: プロフェッショナルが明かす研ぎ澄まされたツール群の使いこなし
工藤 智行
技術評論社 単行本 / ¥60 (2004年06月01日)
 
発送可能時間:

Posted at by




皆さん既に知ってたら御免なさい。ずっと知らなかったのでgithubには手を付けてませんでした。
githubは、cloneするとき # git clone git://github.com/yappo/konnitiwa.git
の代わりに # git clone http://github.com/yappo/konnitiwa.git
とgitをhttpにするとclone出来るのは知っていたのですが、pushはsshを使うのでport 22が開いてないとpush出来ない物と信じ込んでいました。
が、今日解決しました。以下私がWindowsで行った手順
Windowsじゃない人は最初の部分はすっ飛ばして下さい。

Git for Windowsを入れる

まずmsys Gitというのを入れます。
msysgit - Google Code
http://code.google.com/p/msysgit/downloads/list
注意点としては、PATH環境変数の設定具合によってはmsys Gitに含まれるコマンドライン郡の方が先に認識され、思う動きにならない可能性があります。例えば私の場合svn/trunkなvimを使っていますがmsys Gitにもvimが含まれている為、commit時にエラーが出たりします。

SSHキーを生成する

msys Gitに含まれるssh-keygenで生成します。 # ssh-keygen -C "xxx@example.com" -t rsa
以下省略

SSHキーをgithubに登録する

アカウントページにid_rsa.pubの値を貼り付けます。
https://github.com/account

github-ssh-pubkey
Titleは適当に"my windows abazure"と名付けました。

github.comへのssh接続を変更する

github.comへのSSH接続にはホスト名"ssh.github.com"、ポート"443"に接続する様に設定します。
※このssh.github.comが味噌です。
~/.ssh/config
Host github.com
    User git
    Hostname ssh.github.com
    Port 443
    IdentityFile c:/docume~1/mattn/.ssh/id_rsa
秘密鍵のパスをお間違え無く。

後はpushしまくれ

本当にpushしまくるのは良くないでしょうが...
yappo's konnitiwa at master — GitHub
こんにちわ
http://github.com/yappo/konnitiwa/tree/master
をforkし # git clone git@github.com:mattn/konnitiwa.git
Enter passphrase for key 'c:/docume~1/mattn/.ssh/id_rsa':
remote: Counting objects: 9, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 9 (delta 0), reused 6 (delta 0)
Receiving objects: 100% (9/9), done.
とclone出来ますのでREADMEを編集後に # git commit -a -m "added oops"
Created commit 109bbff: added oops
 1 files changed, 1 insertions(+), 0 deletions(-)
# git push Enter passphrase for key 'c:/docume~1/mattn/.ssh/id_rsa':
Counting objects: 5, done.
Writing objects: 100% (3/3), 264 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@github.com:mattn/konnitiwa.git
   66a8481..109bbff  master -> master
でpush出来ます。port 443なので防火壁内の人でもpush出来るかと思います。既にみんな知ってる話だと恥ずかしいですが...

それと昨日書いたニコニコ動画ダウンロードプログラムをgithubで作っていく事にしました。
mattn's nicodown at master — GitHub
http://github.com/mattn/nicodown/tree/master
codereposでも良かったのですが、なんなくgithubを使ってみたくなったので...

よいgithubライフを。

集合知プログラミング 集合知プログラミング
Toby Segaran, 當山 仁健, 鴨澤 眞夫
オライリージャパン 大型本 / ¥3,190 (2008年07月25日)
 
発送可能時間:

Posted at by



2008/10/28


miyagawaさんがPlaggerのFetchNicoVideoからダウンローダとして抜き出してくれました。
Tatsuhiko Miyagawa / WWW-NicoVideo-Download-0.01 - search.cpan.org
http://search.cpan.org/~miyagawa/WWW-NicoVideo-Download-0.01/
中身はMooseを使ったモダンなコード。eg/fetch-video.plにそのまま使えそうなサンプルまで入っています。
サンプルではTerm::ProgressBarで進捗表示までされて至れり尽くせり。今日はちょっとだけ修正してファイル名をタイトルから名付ける様にしてみました。
といってもWWW::NicoVideo::Downloadではloginが単体で呼び出せられる様になっていますし、user_agentが得られるようになっているのでそれを同じくmiyagawaさん作のWeb::Scraperに渡しただけです。
#!/usr/bin/perl
use strict;
use warnings;
use Encode qw(encode decode_utf8);
use URI;
use Web::Scraper;
use WWW::NicoVideo::Download;
use Term::ProgressBar;

my($email, $password, $video_id) = @ARGV;

my($term, $fh);

my $client = WWW::NicoVideo::Download->new( email => $email, password => $password );
$client->login( $video_id );

my $helper = scraper { process '#subinfo img', title => '@alt' };
$helper->user_agent( $client->user_agent );

my $title = $helper->scrape(
    URI->new("http://www.nicovideo.jp/watch/$video_id")
)->{title} || $video_id;
$title = encode("cp932", decode_utf8($title)) if $^O eq 'MSWin32';

$client->download($video_id, \&cb);

sub cb {
    my($data, $res, $proto) = @_;

    unless ($term && $fh) {
        my $ext = (split '/', $res->header('Content-Type'))[-1] || "flv";
        my $filename = "$title.$ext";
        $filename =~ s![\\/|:<>"?*]!_!g;
        open $fh, ">", $filename or die $!;
        $term = Term::ProgressBar->new( $res->header('Content-Length') );
    }

    $term->update( $term->last_update + length $data );
    print $fh $data;
}
うむ。便利便利。

そういえばWEB+DB PRESS vol47買った。codereposの記事も面白かった。

WEB+DB PRESS Vol.47 WEB+DB PRESS Vol.47
高橋 徹, 遠藤 康裕, はまちや2, 正野 勇嗣, 前坂 徹, やまだ あきら, 笠谷 真也, 大沢 和宏, 縣 俊貴, ミック, 田中 洋一郎, 有賀 一輝, 石黒 尚久, 石室 元典, 長野 雅広, 池邉 智洋, 和田 正則, 下岡 秀幸, 伊藤 直也, nanto_vi, 武者 晶紀, 大塚 知洋, 山本 陽平, 高林 哲, 小飼 弾, WEB+DB PRESS編集部
技術評論社 大型本 / ¥93 (2008年10月23日)
 
発送可能時間:


追記
otsuneさんから、API叩いた方が仕様変更に強いとコメント頂きました。XML::Simple使ってタイトル取る様に修正しました。
#!/usr/bin/perl
use strict;
use warnings;
use Encode qw(encode decode_utf8);
use XML::Simple;
use WWW::NicoVideo::Download;
use Term::ProgressBar;

my($email, $password, $video_id) = @ARGV;

my($term, $fh);

my $client = WWW::NicoVideo::Download->new( email => $email, password => $password );
$client->login( $video_id );

my $res = $client->user_agent->get("http://www.nicovideo.jp/api/getthumbinfo?v=$video_id");
my $title = $video_id;
if ($res->is_success) {
  my $xs = XML::Simple->new;
  my $ref = $xs->XMLin($res->decoded_content);
  $title = $ref->{thumb}->{title} || $video_id;
  $title = encode("cp932", decode_utf8($title)) if $^O eq 'MSWin32';
}

$client->download($video_id, \&cb);

sub cb {
    my($data, $res, $proto) = @_;

    unless ($term && $fh) {
        my $ext = (split '/', $res->header('Content-Type'))[-1] || "flv";
        my $filename = "$title.$ext";
        $filename =~ s![\\/|:<>"?*]!_!g;
        open $fh, ">", $filename or die $!;
        $term = Term::ProgressBar->new( $res->header('Content-Length') );
    }

    $term->update( $term->last_update + length $data );
    print $fh $data;
}
Posted at by




今のところ使い道見つからないけど、面白い。
Chris Grau / Export-Lexical - search.cpan.org

Export::Lexical - Lexically scoped subroutine imports

http://search.cpan.org/dist/Export-Lexical/
SYNOPSISをちょと変えて use strict;
use warnings;
package Foo;

use Export::Lexical;
sub foo :ExportLexical {
    print "foo@_\n" or 1;
}
sub bar :ExportLexical {
    print "bar@_\n" or 1;
}
1;
を使う以下のスクリプト use strict;
use warnings;
use Foo;

no Foo 'foo';

eval { foo(1); } or warn "foo1 is disabled.";
eval { bar(1); } or warn "bar1 is disabled.";

{
    use Foo 'foo';
    no Foo 'bar';

    eval { foo(2); } or warn "foo2 is disabled.";
    eval { bar(2); } or warn "bar2 is disabled.";
}

eval { foo(3); } or warn "foo3 is disabled.";
eval { bar(3); } or warn "bar3 is disabled.";
実行すると foo1 is disabled. at hoge.pl line 7.
bar1
foo2
bar2 is disabled. at hoge.pl line 15.
foo3 is disabled. at hoge.pl line 18.
bar3
こんな動きをする。レキシカルスコープでnoが使える。ただnoとは言えどメソッド自体は定義されてるからstrictでも通るし、eval無しで実行してもエラーにはならない。

面白い。でも使い道が見つからない。
Posted at by



2008/10/27


以前、「はてなブックマークをPlaggerで同期する際の注意点」という記事で書いたXML::Feed::Atomが複数のsubjectを返せない件で、送ったパッチが取り込まれました。厳密にはちょっと変えられてますが。
はてなブックマークのRSSフィードにはblockquote引用文が含まれており、Atomを使った方が良いという記事でしたが難点としてPlaggerで使用しているXML::Feed::Atomには複数のsubjectつまりタグを返せる仕組みがなかった為、別のソーシャルブックマークへポストした際にタグが削られてしまう事になっていました。
簡単な例で言うと use strict;
use warnings;
use XML::Feed;
my $feed = XML::Feed->parse(URI->new('http://b.hatena.ne.jp/mattn/atomfeed'))
    or die XML::Feed->errstr;
for my $entry ($feed->entries) {
  print $entry->link."\n";
  print "[$_]" for $entry->category;
  print $entry->summary->{body};
  print "\n";
}
というコードに対して、以前までのXML::Feed::Atomだと http://b.hatena.ne.jp/mattn/20081024#bookmark-10541285
[coderepos]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10541123
[coderepos]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10538485
[wsgi]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10534904
[googleappengine]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10531400
[]てかRSS意識しすぎw そんなに気になるか。
http://b.hatena.ne.jp/mattn/20081024#bookmark-7152814
[webfont]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10530774
[vimperator]Include this patch f*ck でok
http://b.hatena.ne.jp/mattn/20081023#bookmark-10527748
[perl]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10529111
[chrome]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10522215
[catalyst]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10520297
[dojo]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10516769
[igoogle]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10513534
[ruby]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512893
[merb]merbでscaffold
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512804
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512802
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-9671938
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512759
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-9696628
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512388
[perl]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10498444
[lighttpd]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10504059
[]prototype.jsかぁ…。
http://b.hatena.ne.jp/mattn/20081022#bookmark-10502783
[]
http://b.hatena.ne.jp/mattn/20081021#bookmark-10489204
[vimperator]rcが先す
http://b.hatena.ne.jp/mattn/20081021#bookmark-10488856
[softbank]
http://b.hatena.ne.jp/mattn/20081021#bookmark-10484808
[blosxom]
http://b.hatena.ne.jp/mattn/20081021#bookmark-10488095
[]
http://b.hatena.ne.jp/mattn/20081020#bookmark-7686515
[テスト]TeSt
http://b.hatena.ne.jp/mattn/20081019#bookmark-10465795
[]
http://b.hatena.ne.jp/mattn/20081019#bookmark-10460236
[perl]
こんな形にタグが1つ以上出力されず、しかも空の配列として得られたりしていましたが、XML::Feed 0.22以降だと http://b.hatena.ne.jp/mattn/20081024#bookmark-10541285
[coderepos][oops]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10541123
[coderepos]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10538485
[wsgi]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10534904
[googleappengine][appengine]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10531400
てかRSS意識しすぎw そんなに気になるか。
http://b.hatena.ne.jp/mattn/20081024#bookmark-7152814
[webfont]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10530774
[vimperator]Include this patch f*ck でok
http://b.hatena.ne.jp/mattn/20081023#bookmark-10527748
[perl]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10529111
[chrome]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10522215
[catalyst]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10520297
[dojo]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10516769
[igoogle][google]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10513534
[ruby][rails]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512893
[merb]merbでscaffold
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512804
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512802
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-9671938
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512759
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-9696628
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512388
[perl][twitter][wassr]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10498444
[lighttpd]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10504059
prototype.jsかぁ…。
http://b.hatena.ne.jp/mattn/20081022#bookmark-10502783

http://b.hatena.ne.jp/mattn/20081021#bookmark-10489204
[vimperator]rcが先す
http://b.hatena.ne.jp/mattn/20081021#bookmark-10488856
[softbank]
http://b.hatena.ne.jp/mattn/20081021#bookmark-10484808
[blosxom]
http://b.hatena.ne.jp/mattn/20081021#bookmark-10488095

http://b.hatena.ne.jp/mattn/20081020#bookmark-7686515
[テスト]TeSt
http://b.hatena.ne.jp/mattn/20081019#bookmark-10465795

http://b.hatena.ne.jp/mattn/20081019#bookmark-10460236
[perl]
こんな感じの出力になります。 今回の修正により手放しで、はてなブックマークAtomフィードをdelicios等にポスト出来る様になります。

めでたしめでたし
Posted at by



2008/10/16


追記

色付き表示(-R)を使った場合に対応出来てなかった様です。msmhrtさんが修正済みのパッチを書いてくれているのでそちらを使った方が良いです。

私は日常コマンドプロンプト(cygwinは使わない)で暮らすという間違ったWindowsの使い方をしているのですが、最近のソフトウェア配布物に含まれるソースやREADME.jaは殆どがutf-8で書かれており、特にPerlやPython等と言ったソースからドキュメントを見せる様な代物だと、現状utf-8が見れないWindows版のlessでは苦しかったりします。
本当ならば色んな文字コードに対応するのが良いかと思うのですが、最近Shift_JISやEUC-JPで書かれているコードも無いだろうし、もしあったとしてもそれは特例で C:¥> nkf -s example.c | less
すれば言いだけですし...問題はデフォルトでutf8なコードが見れるページャ、かつ見ようと思えばShift_JISなファイルも見れるページャが欲しかったんです。
lessは文字コードの扱いとしてutf-8のみ特別な扱い方をしているのですが、それに用いられている utf-8 <=> ucs な関数があります。Windowsのwchar_tは2バイトですがサロゲートペアさえなければ見れるはず。
Downloading less
http://www.greenwoodsoftware.com/less/download.html
からソースを落として以下のパッチを当ててビルド diff -u less-418.org/Makefile.wnm less-418/Makefile.wnm
--- less-418.org/Makefile.wnm   2007-06-16 07:06:23.000000000 +0900
+++ less-418/Makefile.wnm   2008-10-16 14:13:28.562500000 +0900
@@ -6,7 +6,7 @@
 CC = cl
 
 # Normal flags
-CFLAGS = /nologo /ML /W3 /GX /O2 /I "." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /c
+CFLAGS = /nologo /MT /W3 /EHsc /O2 /I "." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D"_CRT_SECURE_NO_WARNINGS" /c
 LDFLAGS = /subsystem:console /incremental:no /machine:I386
 
 # Debugging flags
@@ -14,7 +14,7 @@
 #LDFLAGS = /subsystem:console /incremental:yes /debug /machine:I386
 
 LD = link
-LIBS = user32.lib
+LIBS = user32.lib /nodefaultlib:libc.lib
 
 #### End of system configuration section. ####
 
diff -u less-418.org/screen.c less-418/screen.c
--- less-418.org/screen.c   2008-01-01 09:50:42.000000000 +0900
+++ less-418/screen.c   2008-10-16 18:55:27.562500000 +0900
@@ -2489,7 +2489,18 @@
    int len;
 {
 #if MSDOS_COMPILER==WIN32C
-   WriteConsole(con_out, text, len, NULL, NULL);
+   extern int utf_mode;
+   if (utf_mode) {
+       int n, m;
+       char* limit = text + len;
+       wchar_t *wtext = (wchar_t*) malloc((len * 2 + 1) * sizeof(wchar_t));
+       for (n = 0, m = 0; *text && n < len; n++)
+           wtext[m++] =  (wchar_t) step_char(&text, TRUE, limit);
+       WriteConsoleW(con_out, wtext, m, NULL, NULL);
+       free(wtext);
+   } else {
+       WriteConsole(con_out, text, len, NULL, NULL);
+   }
 #else
    char c = text[len];
    text[len] = '\0';
環境変数LESSCHARSETに"utf-8"を設定して置けばデフォルトでutf-8なファイルが見れる様になり、環境変数PAGERにlessを設定しておけばperldocで化ける事も無くなります。
less-win32-utf8
最近はマシンスペックが良くなって、毎回変換しても大して劣化しなくて良いですね。
Posted at by



2008/10/15


基本部はこちらを参照。
ニコニコ動画のサムネイル画像を取得する Perl スクリプト - Yet Another Hackadelic
http://d.hatena.ne.jp/ZIGOROu/20081014/1223991205
思いっきり個人用ぽいスクリプトになってしまいました...。
Plaggerでアレを適当なフォルダにガサーーーと落として、そのフォルダ上で走らせる。 ガサーーーのレシピはこんな感じ。
plugins:
  - module: Subscription::Config
    config:
      feed:
         - http://www.nicovideo.jp/mylist/7688389

  - module: Filter::FetchNicoVideo
    config:
      mail: xxxxx@example.com
      password: your-password
      dir: /path/to/download/
      download_comment: 1
      id_as_filename: 0
      # for windows
      #filename_encode: shift-jis
スクリプトはガサーーーの中に含まれるXMLをパースしてゴニョゴニョしている。
#!/usr/bin/perl

use strict;
use warnings;

use Encode;
use Template;
use XML::Simple;
use LWP::UserAgent;

my $ua = LWP::UserAgent->new( keep_alive => 4 );
my $xs = XML::Simple->new;

sub save {
    my ($content, $filename) = @_;
    open my $fh, '>', $filename;
    binmode $fh;
    print $fh $content;
    close $fh;
}

sub build {
    my $file = $_;

    # 動画IDと、ソートに用いるサーバ時刻を取得する
    my $ref = $xs->XMLin($file);
    my $video_id = substr($ref->{view_counter}->{id}, 2);
    my $time = $ref->{thread}->{server_time};

    # 動画ファイル名を得る(s/xml/flv/)
    $file =~ s/\.xml$//;
    $file = decode('cp932', $file) if $^O eq 'MSWin32';
    my $movie_file = "$file.flv";

    # ref: http://d.hatena.ne.jp/ZIGOROu/20081014/1223991205
    my $server_id = $video_id % 2 + 1;
    my $thumb_url = "http://tn-skr$server_id.smilevideo.jp/smile?i=$video_id";
    my $thumb_file = "sm$video_id.jpg";
    my $video_url = "http://www.nicovideo.jp/watch/sm$video_id";

    # サムネイルをダウンロード
    unless (-e $thumb_file) {
        my $res = $ua->get($thumb_url);
        &save($res->content, $thumb_file);
    }

    # " - "でファイル名を分割して先頭を題名に、
    my @info = split(/ - /, $file);
    return {
        title   => $info[0] || $file,
        speaker => $info[1] || $file,
        thumb   => $thumb_file,
        file    => $movie_file,
        time    => $time,
        url     => $video_url,
    };
}

# XMLファイル一覧から動画情報を組み立て、サーバ時刻でソートする
my @movies = sort {$a->{time} <=> $b->{time}}
    map(build($_), glob('*.xml'));

# Templateを使用してHTML出力
binmode STDOUT, ':encoding(utf-8)';
my $tt = Template->new({ UNICODE => 1, ENCODING => 'utf-8' });
$tt->process(\*DATA, {movies => \@movies});

__DATA__
<html>
<head>
<title>第1回 Coderepos Con</title>
<style type="text/css">
body { font-family: meiryo; background-color: black; color: white }
a { color: #333399 }
.file { color: orange; }
.movie { font-size: 20px; font-weight: bold; padding: 0.5em; }
.movie img { margin-right: 0.5em; border: 1px solid blue; }
</style>
</head>
<body>
<h1>第1回 Coderepos Con</h1>
<hr />
<div id="content">
[% FOREACH movie IN movies %]
<div class="movie">
    <img src="[% movie.thumb %]" style="float:left;" />
    Name: <a class="file" href="[% movie.file %]">[% movie.title %]</a><br />
    Speaker: [% movie.speaker %]<br />
    URL: <a href="[% movie.url %]">[% movie.url %]</a><br />
    <br clear="all"/>
</div>
[% END %]
</div>
</body>
</html>
" - "でタイトルとか分けてる部分は、この動画ファイルの名づけ方に依存しているので使う人は好き勝手に変えて下さい。
ちなみに動かすとこんなHTMLが出来上がる。
nico-playlist
いやぁ久々ソースにコメント書いたね!(えっ
Posted at by



2008/10/14


mingw32でビルド C:¥TEMP> wget -O getclip.c "http://sources.redhat.com/cgi-bin/cvsweb.cgi/~checkout~/cygutils/src/clip/getclip.c?rev=1.2&content-type=text/plain&cvsroot=cygwin-apps"
C:¥TEMP> wget -O putclip.c "http://sources.redhat.com/cgi-bin/cvsweb.cgi/~checkout~/cygutils/src/clip/putclip.c?rev=1.2&content-type=text/plain&cvsroot=cygwin-apps"
C:¥TEMP> type common.h
#include <windows.h>
#include <stdio.h>
#include <popt.h>
#include <fcntl.h>

C:¥TEMP> gcc -o getclip.exe -I. getclip.c -lpopt
C:¥TEMP> gcc -o putclip.exe -I. putclip.c -lpopt
できあがり。
C:¥TEMP> ls *.exe | putclip
とか C:¥TEMP> getclip | xargs rm とか使えて便利。poptは「Gnuwin32 Popt」あたりから。
Posted at by



2008/10/10


ようやく動いた。
Apache2/mod_rubyでtDiaryを動かそうと思ったけど、エラーが出て動かなかった。どうやらrhtmlをERBでパースする所で出てるみたい。
今日はこの不可解な問題と向き合ったお話。
まずはmod_rubyをインストールするに当たりバイナリを取ってきたけどDLLのエントリポイントが見つからないとエラーが出たのでビルドする。
svn co http://svn.modruby.net/repos/mod_ruby/trunk mod_ruby
で最新を取って来て、以下のパッチを当てる。
Index: bucket.c
===================================================================
--- bucket.c    (revision 141)
+++ bucket.c    (working copy)
@@ -28,6 +28,10 @@
 #include "mod_ruby.h"
 #include "apachelib.h"
 
+#ifdef _WIN32
+# undef read
+#endif
+
 #ifdef APACHE2
 
 VALUE rb_cApacheBucket;
そして以下のMakefile.w32をmod_ruby直下に置く
APACHE_ROOT=C:\Program Files\Apache Software Foundation\Apache2.2
RUBY_ROOT=C:\Ruby
SRCS = \
    apache_cookie.c \
    apache_multipart_buffer.c \
    apache_request.c \
    apachelib.c \
    array_header.c \
    bucket.c \
    connection.c \
    cookie.c \
    error.c \
    mod_ruby.c \
    multival.c \
    paramtable.c \
    request.c \
    ruby_config.c \
    ruby_shared_stub.c \
    server.c \
    table.c \
    upload.c \
    uri.c

OBJS = $(SRCS:.c=.obj)
CFLAGS=/I"$(APACHE_ROOT)\include" /I"$(RUBY_ROOT)\lib\ruby\1.8\i386-mswin32" /DWIN32 /nologo
LDFLAGS=/LIBPATH:"$(APACHE_ROOT)\lib" /LIBPATH:"$(RUBY_ROOT)\lib"
LIBS= libapr-1.lib libaprutil-1.lib libhttpd.lib msvcrt-ruby18.lib

.SUFFIXES: .c .obj

all : mod_ruby.so

.c.obj:
    cl /c $(CFLAGS) /Fo$@ $<

mod_ruby.so : $(OBJS)
    link /nologo /DLL /OUT:$@ /EXPORT:ruby_module $(OBJS) $(LDFLAGS) $(LIBS)

clean :
    -@del /Q *.obj *.so
パス等は環境に合わせて修正して下さい。
コマンドラインからnmakeでビルド(mingw32の場合はもう一工夫いります)。出来上がったmod_ruby.soをApacheのmodulesディレクトリに配置。httpd.confは適当に設定。 次にtDiary。最新版を svn co https://tdiary.svn.sourceforge.net/svnroot/tdiary/trunk/core tdiary
で取得して設定を済ませる。デフォルトの.htaccessはCGI版になっているので Options +ExecCGI

# if you run tDiary with symbolic link, use settings below.
#Options +FollowSymLinks

# if making anchor style as 'YYYYMMDD.html', add some settings below.
# SEE header of html_anchor.rb plugin.

#AddHandler cgi-script .rb
AddHandler ruby-script .rb
DirectoryIndex index.rb
AddType application/xml .rdf

<Files "*.rhtml*">
    deny from all
</Files>

<Files "tdiary.*">
    deny from all
</Files>

<Files update.rb>
    AuthName      tDiary
    AuthType      Basic
    AuthUserFile  /home/foo/.htpasswd
    Require user  foo
    SetHandler ruby-object
    RubyHandler Apache::RubyRun.instance
</Files>

<Files index.rb>
    SetHandler ruby-object
    RubyHandler Apache::RubyRun.instance
</Files>
以上の様に修正する。さて実行...とまでは良かったが冒頭で書いたエラーが発生。色々調べている内にERBのresult(binding)メソッドでエラーが発生している事が分かった。
しかもコマンドラインから実行すると現象は発生しないけど、Apacheから動かすとエラーが発生する。さらに調べた所、rhtmlファイル内にあるタブ文字(0x09)が影響している事が分かり、tDiaryのskelディレクトリ内にある全てのrhtmlファイルに対してタブ文字からスペースに置き換える作用を行うと正しく動作する。
これでようやくApache2/mod_rubyで動くtDiaryが完成した。
tdiary-apache2-modruby-win32
ただ、どうしてもタブ文字でおかしくなる原因が分からない。RubyKanjiCodeを指定しても駄目。お手上げです。

誰か原因知りませんか?
Posted at by




Perl Quiz: qw - TokuLog 改めB日記 http://d.hatena.ne.jp/tokuhirom/20081010/1223607626
こうかな?
perl -MTest::More -e 'plan tests => 1;is(join(",",qw/a b/x3),"a,b,a,b,a,b")'
Posted at by



2008/10/08


よくvimrcにパスワードが書いてあるからcodereposで公開したくても出来ない!と目にするので、Pitで設定を管理出来るスクリプト書いた。
インストールは通常通りpluginフォルダに、起動には
  • perlインタフェース付きのvim
  • perlのConfig::Pitモジュール
が必要になります。
前者は頑張ってビルド、後者はcpanからインストールします。
使い方は :PitLoad プロファイル名
で引数で指定されたプロファイルを読み込み、pitの設定内容をvimのグローバルスコープに読み込みます。文字列/数値/配列/ハッシュが扱え、それぞれvimのstring/number/List/Dictionaryに割り当てられます。
デフォルトではvimrcというプロファイル名が使用され、"g:pitconfig_default"で変更する事も出来ます。

また
:PitReload
で現在のプロファイルを再読み込みします。

プロファイルの保存は :PitSave
と実行します。
:PitSave プロファイル名
として別のプロファイル名で保存する事も出来ます。現在のプロファイルに対して格納したい変数があれば :PitAdd 変数名
と実行すれば、以降PitSaveで保存される対象となります。同様に
:PitDel 変数名
で削除です。
なお、Config::Pitに付属するppitや、ruby版に付属するpitでも設定を編集出来ます。コマンドラインから # ppit set vimrc
とする事でテキストエディタが起動します。(環境変数EDITORの設定をお忘れなく)

よかったら使って見て下さい。
pitconfig.vim
さぁこれで「パスワードが保存されてるからcodereposでvimrcを公開出来ない」とは言わせませんよ。
Posted at by



2008/10/07


http://d.hatena.ne.jp/gfx/20081007/1223340787

こう? #!perl -w
use strict;
use Test::More 'no_plan';
sub f{
    # 何かする
    package ::Foo;
}
f();
is ref(bless({}, 'Foo')), 'Foo';
__END__
Posted at by



2008/09/26


tokuhiromさんやkazeburoさんの言うように私の所もFastが速かった。
perl の memcached libraries の速度検証 - TokuLog 改めB日記

ワタシの環境だと、Cache::Memcached::Fast の方が速いようだ。

個人的には、 mixi のような大きなサービスでの採用実績があり、速度的にも速い C::M::Fast が現時点ではいい選択肢だとおもう。C::M::libmemcached は、まだまだ開発発展途上な感じなので、もうちょい落ち着くまでは仕事では使えない印象だし。

http://d.hatena.ne.jp/tokuhirom/20080926/1222408445

結果は以下の通り。
Module Information:
 + Cache::Memcached => 1.24
 + Cache::Memcached::Fast => 0.12
 + Cache::Memcached::libmemcached => 0.02008
 + Memcached::libmemcached => 0.2101

Library Information:
 + libmemcached => 0.24

Server Information:
 + localhost:11211 => 1.2.1

Options:
 + Memcached server: localhost:11211
 + Include no block mode (where applicable)? :NO

Prepping clients...

==== Benchmark "Simple get() (scalar)" ====
                  Rate perl_memcached   libmemcached memcached_fast
perl_memcached  2319/s             --           -83%           -86%
libmemcached   13387/s           477%             --           -18%
memcached_fast 16244/s           601%            21%             --
==== Benchmark "Simple get_multi() (scalar)" ====
                 Rate perl_memcached   libmemcached memcached_fast
perl_memcached  664/s             --           -81%           -91%
libmemcached   3509/s           428%             --           -52%
memcached_fast 7306/s          1000%           108%             --
==== Benchmark "Serialization with get()" ====
                  Rate perl_memcached   libmemcached memcached_fast
perl_memcached  2104/s             --           -77%           -81%
libmemcached    8964/s           326%             --           -19%
memcached_fast 11114/s           428%            24%             --
==== Benchmark "Simple get() (w/compression)" ====
                 Rate perl_memcached   libmemcached memcached_fast
perl_memcached  972/s             --           -43%           -43%
libmemcached   1706/s            75%             --            -1%
memcached_fast 1720/s            77%             1%             --
==== Benchmark "Simple set() (scalar)" ====
                  Rate perl_memcached   libmemcached memcached_fast
perl_memcached  5316/s             --           -55%           -71%
libmemcached   11851/s           123%             --           -36%
memcached_fast 18389/s           246%            55%             --
==== Benchmark "Simple set() (w/seriale)" ====
                 Rate perl_memcached   libmemcached memcached_fast
perl_memcached 3190/s             --           -49%           -52%
libmemcached   6201/s            94%             --            -7%
memcached_fast 6668/s           109%             8%             --
==== Benchmark "Simple set() (w/compress)" ====
                Rate perl_memcached   libmemcached memcached_fast
perl_memcached 315/s             --            -2%            -8%
libmemcached   322/s             2%             --            -6%
memcached_fast 342/s             8%             6%             --
Cache::Memcached::Fastのwin32版は、まだ汚いポーティングなので、後日codereposにあげる予定。
Posted at by



2008/09/25


オリジナルはperl版でGTKを使ったUIになっています。
memcachedclient-perl
これを、python(pygtk)、ruby(ruby-gnome2)、lua(lua-gtk)、C(GTK)、java(Swing)に移植してみた。
オリジナルから小さいコードなので、簡単なものですが...
興味のある方は、この辺を覗いて下さい。
Posted at by



2008/09/19


手順だけ。
cd C:\temp\
wget http://search.cpan.org/CPAN/authors/id/T/TI/TIMB/Memcached-libmemcached-0.2101.tar.gz
zcat Memcached-libmemcached-0.2101.tar.gz | tar xv
cd Memcached-libmemcached-0.2101
mkdir src_inst\include\libmemcached
mkdir src_inst\lib
svn co http://svn.coderepos.org/share/lang/c/libmemcached-win32/libmemcached-latest
cd libmemcached-latest\libmemcached
mingw32-make -f makefile.w32
cd ..\..
copy libmemcached-latest\libmemcached\*.h src_inst\include\libmemcached\.
copy libmemcached-latest\libmemcached\memcached.a src_inst\lib\.
copy src_inst\include\libmemcached\*.h src\libmemcached\libmemcached\.
perl Makefile.PL
set OPT="INC=-IC:\temp\Memcached-libmemcached-0.2101\src_inst\include" "LMCD_BUILT_LIB=C:\temp\Memcached-libmemcached-0.2101\src_inst\lib\memcached.a" "LDFROM=$(OBJECT) C:\temp\Memcached-libmemcached-0.2101\src_inst\lib\memcached.a"
nmake %OPT%
あとは libmemcached-latest\libmemcached\memcached.dll
をパスの通る場所に置いておけばWindowsで use strict;
use Perl6::Say;
use Memcached::libmemcached qw(
    memcached_create
    memcached_server_add
    memcached_set
    memcached_get
);

my $key = "foo";
my $value = "bar";
my $memc = memcached_create();
memcached_server_add($memc, '127.0.0.1');
memcached_set($memc, $key, $value);
say memcached_get($memc, $key);
こんなソースが通る様になります。Cache::Memcached::libmemcachedも動きます。
さらに言うなら、rubyのmemcachedも動きました。 require 'memcached'
$cache = Memcached.new('127.0.0.1:11211')
$cache.set('foo', 'bar')
print $cache.get('foo')
こちらはCOMPATIBILITYファイルを弄ったり、最新バージョンで無くなったWHEELな処理をカットしないといけませんが...
さらにさらにpython-libmemcachedも動きます。
import cmemcached

c = cmemcached.Client(['127.0.0.1:11211'])
c.set("foo", "bar")
print c.get("hoge")
こちらもcmemcached.pyxでWHEELな処理をカットしないといけませんが...
とりあえず、動くって事です。
ちなみにオフィシャルの反応はまだなし...
Posted at by



2008/09/18


色々やってたら、出来た。これで が動く様になるのではないか!
地味な作業だったけど、以外と効果は大きいかも。今日は時間無いので明日以降にソース綺麗にしてオフィシャルにパッチを送ってみようかと思ってます。
ちなみにUNIXドメインソケットなmemcachedは動きません。(win32ですからね...)

mingw32でコンパイルすると、こんなソースがコンパイル実行出来ました。
#include <winsock2.h>
#include <memcached.h>
#include <stdio.h>

int main(void) {
    memcached_return rc;
    memcached_st *memc;
    char* value;
    int value_length = 0;
    int flags = 0;

    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) {
        return -1;
    }
    memc = memcached_create(NULL);;
    rc = memcached_server_add(memc, "127.0.0.1", 11211);
    printf("server add: %s\n", memcached_strerror(memc, rc));

    rc = memcached_set(memc, "test", 4, "example", 8, 0, 0);
    printf("set: %s\n", memcached_strerror(memc, rc));

    value = memcached_get(memc, "hoge", 4, &value_length, &flags, &rc);
    printf("get: %s\n", memcached_strerror(memc, rc));
    printf("test = %s\n", value);

    memcached_free(memc);
    return 0;
}
汚いソースを見たいという方はこの辺を...
Posted at by



2008/09/17


Google App Engineにファイルを転送しようとするとき、ファイルの数が多いと途中で失敗したり時間が掛かったりと結構嫌な思いをする事があったのですが、今日公開されたGAE SDK 1.3にてstaticファイルをZIPでまとめる事が出来る様になりました。
Google App Engine Blog: SDK 1.1.3 Now Available for download

Support for zipimport and a new module, zipserve, which serves static files from a zip archive. These allow you to work past the 1000-file app deployment limit.

The development console now includes a memcache viewer (you can use this by accessing http://localhost:8080/_ah/admin while your app is running on the SDK).

URLFetch now allows users to disable automatically following HTTP redirects.

We now allow composite indexes with repeated properties.

http://googleappengine.blogspot.com/2008/09/sdk-113-now-available-for-download.html
使い方も簡単。まず以下の様な構成のzipファイルを用意します。 Archive:  zippage.zip
  Length     Date   Time    Name
 --------    ----   ----    ----
      289  08/09/17 13:03   index.html
      203  08/09/17 12:53   test.html
     4233  08/01/30 15:55   logo.png
 --------                   -------
     4725                   3 files
そしてアプリケーションハンドラの設定を以下の様に設定します。
import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.ext import zipserve

def main():
  application = webapp.WSGIApplication(
    [
      ('/zippage/(.*)', zipserve.make_zip_handler('zippage.zip')),
    ])
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
  main()
もちろんハンドラを分ければ、あるURLではpythonのハンドラ、あるURLではZIP内ファイルという事も出来ます。なお、動いている物はこの辺で確認出来ます。
他にも今回のバージョンアップにて開発者用コンソールにMemcache Viewerが使える様になりました。
gae-memcache-viewer
直接値を編集する事も出来ますが、現状マルチバイト文字でエラーが出ているようです。
他、URLFetchでURLの自動フォローを出来ないようになったり等の修正も行われています。

話は変わって、tokuhiromさん作のjavascript v8エンジンで動くCGIインタフェースllv8callですが、tokuhiromさんが入れ込んでる機能をwin32に移植して行っていますがlibmemcachedのwin32版がMSVCでビルド出来なかったので、自前でmemcacehdとお喋りする様にしました。興味がある方はこの辺のソースをご覧下さい。

Posted at by



2008/09/11


ちょっと訳あって、WScriptからjQueryを呼び出す必要があり(嘘です)作ってみました。

これを使うと $.each([1,2,3], function(index, item) {
    print("foo" + item);
});
$.ajax({
    type: "GET",
    url: "http://www.google.co.jp/",
    async: false,
    success: function(data) {
        print(data);
    }
});
こんなソースが実行出来ます。
$.eachなんかは目茶目茶便利なので、使わない手はありません。とりあえず、$.ajaxでgoogle.co.jpのソースが取得出来るくらいは動きます。
以下全体ソース。
// vim:fdm=marker fdl=0 fdc=0 fdo+=jump,search:
// vim:fdt=substitute(getline(v\:foldstart),'\\(.\*\\){\\{3}','\\1',''):
// {{{
(function(target) {
    target.window = {
        document : {
            defaultView : {}
        },
        navigator : {
            userAgent : "Windows Scripting Host"
        },
        location : {},
        XMLHttpRequest : function() {
            // copied from http://la.ma.la/misc/js/ie_xmlhttp.js
            var self = this;
            var props = "readyState,responseText,responseXML,status,statusText".split(",");
            this.readyState  = 0;
            this.__request__ = new ActiveXObject("Microsoft.XMLHTTP");
            this.__request__.onreadystatechange = function(){
                for(var i=0;i<props.length;i++){
                    try{
                        self[props[i]] = self.__request__[props[i]]
                    }catch(e){
                    }
                }
                self.onreadystatechange()
                if(self.readyState == 4) self.onload();
            }
            this.onreadystatechange = function(){};
        },
        setInterval : function(func, interval) {
            func(); // quickly f*ckin hack
        },
        clearInterval : function(timer) {
        },
        setTimeout : function(func, interval) {
            func(); // quickly f*ckin hack
        },
        clearTimeout : function(timer) {
        }
    };
    var methods = "open','abort','send','setRequestHeader','getResponseHeader','getAllResponseHeaders".split("','");
    var make_method = function(name){
        window.XMLHttpRequest.prototype[name] = function(){
            var params = new Array(arguments.length);
            for(var i=0;i<params.length;i++) params[i] = "_"+i;
            return Function(
                params.join(","),
                ["return this.__request__.",name,"(",params.join(","),")"].join("")
            ).apply(this,arguments);
        }
    };
    for (var i=0;i<methods.length;i++) make_method(methods[i]);
    for (var n in window) target[n] = window[n];
})(this);

function print(msg) {
    WScript.StdOut.WriteLine(String(msg));
}

function require(source, target) {
    target = target||this;
    var fso = new ActiveXObject('Scripting.FileSystemObject');
    var stm = fso.OpenTextFile(source, 1, false, -2);
    var text = stm.ReadAll();
    stm.Close();
    eval(text, target);
    for (var n in windowif (typeof target[n] === 'undefined') target[n] = window[n];
}
// }}}

require("jquery-latest.js");

$.each([1,2,3], function(index, item) {
    print("foo" + item);
});

$.ajax({
    type: "GET",
    url: "http://www.google.co.jp/",
    async: false,
    success: function(data) {
        print(data);
    }
});
setTimeoutとかsetIntervalはf*ckingはhackなので$.get(非同期)は動きません。直す余地ありです。
あと、XMLHttpRequestのbindはmalaさんのをパクってます。
Posted at by




tokuhiromさん作のfcgi-v8をCGIインタプリタとして、cho45さん作のblosxom.rhinoを改造してblosxom.v8というのを作ってみた。
v8にはunevalが無かったのでdankogaiさん作のuneval.jsを使わせて頂いた。
実行画面はblosxom.rhinoの書き換えなので、パクリ気味ですが...
blosxom-v8
fcgi-v8でファイル日付の取得が実装出来ていないので、今は日付は表示されていません。
blosxom.v8
よかったら遊んでみて下さい。
Posted at by



2008/09/10


ひさびさPlagger弄り。
Lingua::JA::Galを使ってフィードテキストをギャル語に変換します。
注意する点はHTMLなフィードをプレーンテキストに置き換えてしまう点です。
これでPublish::MixiDiary使ってギャルなマイミク装ってみませんか。
コードはこの辺に。

Lingua::JA::Gal++
Posted at by



2008/09/05


shell.ccから改造したから分からなくなったんだよね。
v8::Objectに内部保持するnativeな値(例えばハンドルとか)を保持するには FILE* fp = fopen(...);
v8::Local<v8::Object> obj = v8::Object::New();
obj->Set(v8::String::New("value"), v8::External::New((void*)fp));
という様に、v8::Externalでメンバを追加してやれば良い。
ただ分からなくなってた原因としては、shell.ccが持ってるPrintという関数が v8::Handle<v8::Value> Print(const v8::Arguments& args) {
  bool first = true;
  for (int i = 0; i < args.Length(); i++) {
    v8::HandleScope handle_scope;
    if (first) {
      first = false;
    } else {
      printf(" ");
    }
    v8::String::AsciiValue str(args[i]);
    printf("%s", *str);
  }
  printf("\n");
  return v8::Undefined();
}
となってて、何でも文字列化してくれる様に思えてた。で実際 var obj = test();
print(obj.value); // ここでクラッシュ
こんな事やると落ちてしまうんだけど、良く考えたらExternalって言ってるんだから値の出しようもない。確かに以下の様なコードをshell.ccに含ませ global->Set(v8::String::New("hoge"), v8::External::New(NULL));
javascriptから"print(hoge)"で例外を発生させた際のデバッグ出力で確かめたら ==== Stack trace ============================================

(No security context)
    1: DefaultString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#)
(No security context)
    2: ToString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#)
    6: arguments adaptor frame: 1->0
Security context: 00E006A1 <FixedArray[41]>#2#
    7: /* anonymous */(this=00C10361 <JS Global Object>#3#)

==== Details ================================================

(No security context)
[1]: DefaultString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#) {
  // stack-allocated locals
  var v = 01000135 <undefined>
  var s = 01000135 <undefined>
  // expression stack (top to bottom)
  [04] : 01000361 <String[8]: toString>
  [03] : 00E0068D <Proxy>#1#
  [02] : 00E0068D <Proxy>#1#
--------- s o u r c e   c o d e ---------
function DefaultString(x) {?  if ((typeof(x.toString) === 'function')) {?    var s = x.toString();?    if (%IsPrimitive(s)) return s;?  }?  if ((typeof(x.valueOf) === 'function')) {?    var v = x.valueOf();?    if (%IsPrimitive(v)) return v;?  }?  throw %MakeTypeError('cannot_convert_to_primitive', []);?}
-----------------------------------------
}

(No security context)
[2]: ToString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#) {
  // expression stack (top to bottom)
  [02] : 01002845 <String[13]: DefaultString>
  [01] : 00C104BD <JS Object>#0#
  [00] : 0100232D <String[8]: ToString>
--------- s o u r c e   c o d e ---------
function ToString(x) {?  if ((typeof(x) === 'string')) return x;?  if ((typeof(x) === 'number')) return %NumberToString(x);?  if ((typeof(x) === 'boolean')) return x ? 'true' : 'false';?  if ((typeof(x) === 'undefined')) return 'undefined';?  return ((x === null)) ? 'null' : %ToString(%DefaultString(x));?}
-----------------------------------------
とNULL参照している事が分かる。v8からすると"訳分かんない物をToStringして表示しようとしただけさ。"と言ったところか。
つまりはv8::Externalの使い方が悪いわけではなく、v8::Externalを表示しようとしてたPrint関数がマズイのだ。さらに元々Externalなんか扱ってなかったshell.ccにExternalを埋め込んだ犯人が悪い。そう...私。
言ってしまえばExternal組み込んだんだから、Printもちゃんと修正しろよって事ですね。
if (argv[0]->IsExternal()) {
    printf("[native code]");
    continue;
}
よくブラウザなんかで見かける"[native code]"という表示になる様にしました。
この修正は「Google の JavaScript エンジン v8 で FastCGI する - TokuLog 改めB日記」での成果物"fcgi-v8"にも組み込んであります。
ただ、Extenalに保持する様な値というのは、ハンドルとかメモリ確保した領域だとか、状態を保持する様な物のはずなので、実際には確保したポインタ等のリストをグローバルな領域にを持っておき、終了時に解放するかPersistentなObjectTemplateでインスタンスを作り、WeakReferenceCallbackにて解放するのがベターという事になる。
Externalを使う側からすると、そこに何が入っててもおかしい訳でもなく、v8として取り決めもない(引数の型はvoidポインタ)。よって複数の種類を格納したい場合は種類も判別出来る物、例えば typedef enum _OBJECT_TYPE {
    OBJECT_TYPE_FILE_POINTER, // FILE*
    OBJECT_TYPE_WINDOW_HANDLE // ウィンドウハンドル
} OBJECT_TYPE;

typedef struct _OBJECT_VALUE {
    OBJECT_TYPE type;
    void* object;
} OBJECT_VALUE;

// 設定
OBJECT_VALUE* val = new OBJECT_VALUE;
val->type = OBJECT_TYPE_FILE_POINTER;
val->object = fopen("test.dat", "rb");
obj->Set(v8::String::New("value"), v8::External::New((void*)val));

// 取得
v8::Local<v8::Value> valueobj = obj->Get(v8::String::New("value"));
if (!valueobj->IsExternal()) return v8::ThrowException(v8::String::New("Exception: invalid object"));
OBJECT_VALUE* val = static_cast< OBJECT_VALUE* >(v8::Handle<v8::External>::Cast(valueobj)->Value());
if (val->type == OBJECT_TYPE_FILE_POINTER) {
    // FILE*な処理
} else
if (val->type == OBJECT_TYPE_WINDOW_HANDLE) {
    // ウィンドウハンドルな処理
}
こんな構造と設定および取得をすべき、という事になる。ここまで気付くのに結構時間を使ってしまった。

さてこのPrint関数ですが、↑のリンクにある"fcgi-v8"には「Big Sky :: Google Chromeが使っているjavascript v8エンジンにTwitterにアクセス出来るクラスを作ってポストする。」でご紹介したtwitter-v8とは違う方法でPrintを実装してあります。
twitter-v8の方はコンソールに出力する物なのでsetlocaleとワイドキャラクタ出力(%S)でOKですが、fcgi-v8の場合はUCSからUTF-8への変換を組み込んであります。一応v8のプロジェクトサイトでIssue 17 : 「Add Strign::WriteUtf8」としてリクエストされているので、もしかすると今後このコードは必要なくなるかもしれませんね。
v8エンジンは高速な上、今まで私が見た中で一番組み込みやすい代物になっているかと思います。皆さんもv8使った面白いもの作ってみませんか。

twitter-v8、fcgi-v8それぞれ参加者をお待ちしております。
Posted at by



2008/09/03


v8エンジン盛り上がってますね。
さて、今日はv8エンジンのshell.ccを弄って、シェル内部にTwitterオブジェクトを作ってみました。とはいっても、仕組みは簡単。FunctionTemplateから得たPrototypeTemplateにメソッドを追加、さらにInstanceTemplateにプロパティusernameとpasswordを足しているだけです。
  v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New();
  v8::Local<v8::Template> p = t->PrototypeTemplate();
  p->Set("friendsTimeline", v8::FunctionTemplate::New(TwitterFriendsTimeline));
  p->Set("updateStatus", v8::FunctionTemplate::New(TwitterUpdateStatus));
  v8::Local<v8::ObjectTemplate> i = t->InstanceTemplate();
  i->Set(v8::String::New("username"), v8::String::New(""));
  i->Set(v8::String::New("password"), v8::String::New(""));

  global->Set(v8::String::New("Twitter"), t);
このTwitterFriendsTimelineには以下の様なコードを...
v8::Handle<v8::Value> TwitterFriendsTimeline(const v8::Arguments& args) {
  v8::Handle<v8::Value> ret = v8::Undefined();
  v8::Local<v8::Object> This = args.This();
  v8::HandleScope handle_scope;
  v8::TryCatch try_catch;

  v8::String::AsciiValue username(This->Get(v8::String::New("username")));
  v8::String::AsciiValue password(This->Get(v8::String::New("password")));

  CURL* curl = NULL;
  CURLcode res = CURLE_OK;
  char *auth;

  response_data = NULL;
  response_size = 0;
  curl = curl_easy_init();
  if (!curl) return v8::ThrowException(v8::String::New("Error: unknown"));

  int n = strlen(*username) + strlen(*password);
  auth = (char*) malloc(n + 2);
  memset(auth, 0, n + 2);
  strcpy(auth, *username);
  strcat(auth, ":");
  strcat(auth, *password);

  curl_easy_setopt(curl, CURLOPT_URL, "http://twitter.com/statuses/friends_timeline.json");
  curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handle_returned_data);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
  res = curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  free(auth);

  if (res == CURLE_OK) {
    char* json = NULL;
    json = (char*) malloc(response_size+1);
    memset(json, 0, response_size+1);
    memcpy(json, (char*) response_data, response_size);
    v8::Handle<v8::String> source = v8::String::New(json);
    free(json);

    v8::Handle<v8::Script> script = v8::Script::Compile(source);
    if (script.IsEmpty()) {
        ret = v8::ThrowException(v8::String::New("Error: unknown"));
        goto leave;
    }
    v8::Handle<v8::Value> result = script->Run();
    if (script.IsEmpty()) {
        ret = v8::ThrowException(v8::String::New("Error: unknown"));
        goto leave;
    }
    if (!result->IsArray()) {
        ret = v8::ThrowException(v8::String::New("Error: unknown"));
        goto leave;
    }
    ret = result;
  }

leave:
  if (response_data) free(response_data);
  response_data = NULL;
  return ret;
}
さらにTwitterUpdateStatusには v8::Handle<v8::Value> TwitterUpdateStatus(const v8::Arguments& args) {
  v8::Handle<v8::Value> ret = v8::Undefined();
  v8::Local<v8::Object> This = args.This();
  v8::HandleScope handle_scope;
  v8::TryCatch try_catch;

  if (args.Length() != 1) return v8::ThrowException(v8::String::New("Error: usage(message)"));

  v8::String::AsciiValue username(This->Get(v8::String::New("username")));
  v8::String::AsciiValue password(This->Get(v8::String::New("password")));

  v8::Handle<v8::Object> global = v8::Context::GetCurrent()->Global();
  v8::Local<v8::Function> func = v8::Function::Cast(*global->Get(v8::String::New("encodeURIComponent")));
  v8::Local<v8::Value> funcargs[1];
  funcargs[0] = args[0]->ToString();
  v8::Local<v8::Value> result_val = func->Call(global, 1, funcargs);
  v8::String::AsciiValue escaped(result_val->ToString());

  char* status = url_encode_alloc(*escaped);

  CURL* curl = NULL;
  CURLcode res = CURLE_OK;
  char *auth;
  char url[2048];

  response_data = NULL;
  response_size = 0;
  curl = curl_easy_init();
  if (!curl) return v8::ThrowException(v8::String::New("Error: unknown"));

  memset(url, 0, sizeof(url));
  strncpy(url, "http://twitter.com/statuses/update.xml", sizeof(url)-1);
  strncat(url, "?status=", sizeof(url)-1);;
  strncat(url, status, sizeof(url)-1);
  free(status);

  int n = strlen(*username) + strlen(*password);
  auth = (char*) malloc(n + 2);
  memset(auth, 0, n + 2);
  strcpy(auth, *username);
  strcat(auth, ":");
  strcat(auth, *password);

  curl_easy_setopt(curl, CURLOPT_URL, url);
  curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handle_returned_data);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
  curl_easy_setopt(curl, CURLOPT_POST, 1);
  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
  res = curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  free(auth);

  if (res != CURLE_OK) {
    ret = v8::ThrowException(v8::String::New("Error: unknown"));
  }
  if (response_data) free(response_data);
  response_data = NULL;
  return ret;
}
これだけ準備出来れば、あとは var twitter = new Twitter()
twitter.username = "my-username"
twitter.password = "my-password"
var statuses = twitter.friendsTimeline();
for (var n = 0; n < statuses.length; n++) {
    print(statuses[n].user.screen_name, ":", statuses[n].text);
}
twitter.updateStatus("v8エンジン12気筒");
こんなスクリプト書いて # shell twitter.js とかやればfollowerのステータスが出た後でメッセージがポストされます。
簡単ですね。
ちなみに既存のPrintではAsciiValueを使いますが、AsciiValueではUTF-8が8bit落ちするのでsetlocaleを使ってワイドキャラでprintfする様修正しています。
コードはcodereposのこの辺に置いときます。汚いソースですが、よろしければ参考まで...
Posted at by




Tumblr - Post to Tumblr : vim online

Script that allows you to post regular type posts to Tumblr.

といっても3行
*** tumblr.vim.orig Wed Sep 03 14:49:50 2008
--- tumblr.vim  Wed Sep 03 14:47:15 2008
***************
*** 50,55 ****
--- 50,58 ----
      url = "http://www.tumblr.com/api/write"
      email = vim.eval("g:tumblr_email")
      password = vim.eval("g:tumblr_password")
+     enc = vim.eval('&encoding')
+     title = title.decode(enc).encode('utf-8')
+     body = body.decode(enc).encode('utf-8')
      data = urllib.urlencode({"email" : email, "password" : password, "title" : title, "body" : body})
      res = urllib.urlopen(url, data)
  

このパッチを当てたtumblr.vimをpluginフォルダに置き
let g:tumblr_email='your@example.com'
let g:tumblr_password='tumblr-password'
と設定し新しいバッファ :TumblrNew
とすると Title:
-- Post follows this line --
と表示されるので、Title部と破線以下に入力し、 :TumblrPost
と実行します。一応rangeにも対応出来ているらしいです。
Textしかポスト出来ませんが、改造すればQuoteも行けるんじゃないですかね。ただvimで編集中のテキストが引用元になるから何がQuoteか分かりませんが...
Posted at by




scons入れてwin32でビルドしてみたよ。ベンチマークも取ってみた。
環境はWindows XP、P4 3GHz CPU、1G memで。

まずmozillaのjsエンジン(ちょっと昔)
JavaScript-C 1.7.0 2007-10-03 (js.exe)
Richards: 87
DeltaBlue: 84
Crypto: 44
RayTrace: 78
EarleyBoyer: 87
----
Score: 74

次にrhino
Rhino 1.6 release 7 (win32 nativeビルド版)
Richards: 6
DeltaBlue: 5
Crypto: 4
RayTrace: 11
EarleyBoyer: 12
----
Score: 7

Rhino 1.7 release 7 2008 03 06(java版 js-14.jar)
Richards: 30
DeltaBlue: 30
Crypto: 24
RayTrace: 47
EarleyBoyer: 53
----
Score: 35

そしてv8エンジン
V8 version 0.2.5 (shell.exe)
Richards: 1020
DeltaBlue: 981
Crypto: 622
RayTrace: 580
EarleyBoyer: 1070
----
Score: 827
はぇーーーーーーーーーーーーーーー!(゚д゚;)

ベンチマークはbenckmarksフォルダにあるrun.jsで起動します。

追記
取り直したらこんな結果もでた
Richards: 1391
DeltaBlue: 1224
Crypto: 940
RayTrace: 774
EarleyBoyer: 1360
----
Score: 1110
もっと詳細が知りたい方は以下を参照。
Google V8 についてしらべてみました - TokuLog 改めB日記
Posted at by



2008/09/02


追記
teramakoさんがもっと良いの 作ってくれました。
きっとそっち使う方が幸せな、ともだちんこになれます。
ですので、私のはubiquityNeta.jsにリネームする事にします。


こう使うのですね?わかります。
(function() {
    if (typeof gUbiquity === 'undefined'return;
    liberator.commands.addUserCommand(["ubiquity"], "Ubiquity",
        function(arg, special){
            var anchor = document.getElementById("content");
            if (window.location == "chrome://browser/content/browser.xul")
                anchor = anchor.selectedBrowser;
            gUbiquity.openWindow(anchor);
            gUbiquity.__textBox.value = arg||'';
            var context = gUbiquity.__makeContext();
            var previewBlock = document.getElementById("cmd-preview").contentDocument.getElementById("preview");
            gUbiquity.__cmdManager.updateInput(arg||'', context, previewBlock);
            if (special) {
                gUbiquity.__cmdManager.execute(context);
                gUbiquity.closeWindow();
            }
        },
    { });
})();
使い方は
:ubiquity map osaka
でプレビュー表示
:ubiquity! map osaka
で直実行です。
ubiquity-vimperator
ソースはcodereposに置いておきます。
ubiquityNeta.js
ubiquityとvimperatorは、ともだちんこですね。わかります。

参考:
ゆびきちが相当面白い!! - ネットランダム

指基地なのか、指既知なのかは置いておいて、ユビキタス度アップ間違いなしのFirefoxアドオンである。

今後、Greasemonkeyにとって変わる可能性すらある。

っていうか、Vimperatorユーザ涙目!

http://d.hatena.ne.jp/fk_2000/20080901/p2
Posted at by



2008/08/29


適当ですが...
show-delicious-comment-if-hatena-bookmark-comment-is-disabled.user.js
これで今日も眠れますね。
しかし名前長いな。
Posted at by




Atompub使うと簡単。
use strict;
use warnings;

use Atompub::Client;
use XML::Atom::Entry;
use utf8;

my $username = 'xxxxxxxxxxxxxxxxxx';
my $password = 'xxxxxxxxxxxxxxxxxx';
my $uri = "http://d.hatena.ne.jp/$username/atom/draft";

my $client = Atompub::Client->new;

$client->username($username);
$client->password($password);

# 下書きを一覧
my @entries = $client->getFeed($uri);
for my $entry (@entries) {
    next if not $entry->id;
    warn $entry->link->href."\n".$entry->title."\n\n";
}

# 新規下書きエントリ
my $entry = XML::Atom::Entry->new;
$entry->title('下書きテスト');
$entry->content('はてダで下書き');
my $entry_uri = $client->createEntry($uri, $entry);
warn $client->errstr||'\n';

# 下書きを上書き
$entry->content('はてダで下書きを上書き');
$client->updateEntry($entry_uri, $entry);

# 下書きを削除
$client->deleteEntry($entry_uri);

# 公開用に新規下書きエントリ
$entry = XML::Atom::Entry->new;
$entry->title('下書き公開テスト');
$entry->content('はてダの下書きを公開してみるテスト');
$entry_uri = $client->createEntry($uri, $entry);
warn $client->errstr||'\n';

# 下書きを公開
$client->ua->default_header('X-HATENA-PUBLISH' => 1);
$client->updateEntry($entry_uri, $entry);
ゴミのエントリは、適当に消して下さい。
ところで1つも下書きエントリがない状態で、getFeedするとルートのlinkが取れてしまうのだけど、これは仕様だろうか。
Posted at by



2008/08/27


ま、ほぼ差異のないソースですが...
windowsでしか動作確認出来てないので、動かなかったら直して下さい。
よかったらもってって下さい。
Posted at by



2008/08/22


APIs for Finding Location

Google launched two APIs for finding the location of a user from a web application. The most simple way, which is also the least precise, is to derive some information about location from the IP. Google AJAX API has an object named google.loader.ClientLocation that includes "metro-level granularity properties": country, region, city, low resolution latitude and longitude. This could be useful if you want to customize a web page for a specific country or to prefill information about location. You probably noticed that google.com automatically redirects to the appropriate international domain and that when you create a Google account your country is already selected.

http://googlesystem.blogspot.com/2008/08/apis-for-finding-location.html

Developer's Guide - Google AJAX APIs - Google Code

When an application makes used of the AJAX API loader, the loader attempts to geo locate the client based on it's IP address. If this process succeeds, the client's location, scoped to the metro level, is made available in the google.loader.ClientLocation property. If the process fails to find a match, this property is set to null.

http://code.google.com/apis/ajax/documentation/#ClientLocation http://code.google.com/apis/gears/api_geolocation.html
試しにやってみたら"google.loader.ClientLocation.address.region"で"「大阪府」って出た。
あと"ClientLocation.latitude"と"ClientLocation.longitude"で緯度経度が得られるので、setCenterで中心位置を変更出来る。
google.load("maps", "2");
google.load("search", "1");
google.setOnLoadCallback(function() {
    var map = new google.maps.Map2(document.getElementById("map"));
    map.setCenter(new google.maps.LatLng(
        google.loader.ClientLocation.latitude,
        google.loader.ClientLocation.longitude), 10);
});
実行結果は以下

続きを読む...

Posted at by




Booで。
class _X:
  s as string

  def constructor():
    s = "ひだまり"

  static def op_LessThan(x as _X, v as string):
    print x.s, v
    return x
 
  static def op_Division(x as _X, v as object):
    if v.GetType() == typeof(int):
      x.s += "スケッチ"
    else:
      x.s += "365"
    return x

X = _X()
_ = 1

X / _ / X < "来週も見てくださいね!"
実行は
C:¥temp> booi yuno.boo
ひだまりスケッチ365 来週も見てくださいね!
でインタプリタ起動するか
C:¥temp> booc yuno.boo
Boo Compiler version 0.8.2.2960 (CLR v2.0.50727.832)

C:¥temp> yuno.exe
ひだまりスケッチ365 来週も見てくださいね!
でコンパイル実行。
boo初めて使ってみたけど、これいいかも。
monoでの実装とかあるのかな?

ちなみにpythonに似てるけど、静的型付けです。あとオペレータの判断順番がpythonと違うので、pythonのまま移植すると「ひだまり365スケッチ 来週も見てくださいね!」になるので要注意

Posted at by



2008/08/19


HTML::TreeBuilderは便利だけど、データ構造がロジックになってしまうのが難点。
Perlでブックオフの店舗を検索し、結果をハッシュの配列に格納する - As a Futurist...

HTMLを解析する練習です。Perlの配列とかハッシュの扱いも少し分かりました。 以下のブックオフの検索をPerlでやっただけです。

http://blog.riywo.com/2008/03/31/164327
ってことでWeb::Scraperで。

#!/usr/bin/perl -w

use strict;
use warnings;
use URI;
use Web::Scraper;
use YAML;

my $str = shift || '新宿';
my $uri = URI->new( 'http://www.bookoff.co.jp/shop/shop.php' );
$uri->query_form(
    action => 'search',
    station => $str,
    shop_name => $str,
);

warn Dump scraper {
    process '//tr[td]', 'res[]' => scraper {
        process '//td[1]', name => 'TEXT',
        process '//td[2]', time => 'TEXT',
        process '//td[3]', tel => 'TEXT',
        process '//td[4]', place => 'TEXT',
    };
    result qw/res /;
}->scrape( $uri );
Web::Scraper便利。
process書かずに
name => '//td[1]/text()'
とか書けるシンタックスシュガーあったらなぁ...とか思った。
Posted at by




既に試した方もいらっしゃるようですね。
visual_studio.vimを入れてみた - ampmmnの日記
TaskList : タスクリストの項目をquickfixに表示する。動作未確認。
Output : アウトプットの内容をquickfixに表示。動作未確認。
Solutions : これはVisualStudioを複数走らせている時に、どれと通信するかを選択するものらしい。自分の環境で実行したら、何かエラーメッセージが出力された。でも機能はしているようだ。
Projects : どのプロジェクトをアクティブ(スタートアップ)にするかを選択する。動作未確認。
http://d.hatena.ne.jp/ampmmn/20080809/1218230946
visual_studio.vim,python,unicodeencodeerror - gnarl、技術メモ
visual_studio.vimを導入してみたものの同梱pythonスクリプトのstr(name)のあたりでunicodeencodeerrorが出て困りましたね。
...
projectsメニューの一部がなぜか文字化けしますが
http://d.hatena.ne.jp/gnarl/20071012/1192181729
さまざまな言語のコメントON/OFFに対応した便利プラグイン - Seasons.NET
ただし、日本語版は、日本語エンコードに不具合があるので、アウトプット文字列が表示できないです。
http://d.hatena.ne.jp/Seasons/20070613/1181629449

もったいないので、日本語版Visual Studioでも動作する様に修正してみました。
ポイントは、DDE通信を行ってウィンドウを取得した後、ウィンドウキャプションで目的のサブウィンドウを探している所。
英語版で想定されているので、"Output"は"出力"、"Find Result 1/Find Result 2"は"検索結果 1/検索結果 2"に、"Build"は"ビルド"に修正する必要があります。
せっかくなので、vimに表示されるメニューも修正しました。見栄えはこんな感じ。
visual_studio_vim日本語版
一応、全てのメニューが動作する様にしてあります。
ちなみに、プロジェクト一覧の取得では str(dict)
を使ってvimのDictionaryに引き渡している為、ダブルクォートでないと"\xXX\xXX"なマルチバイトを解釈出来ないvimでは正しく表示されていませんでした。
今回の修正では、simplejsonを使い simplejson.dumps(obj, ensure_ascii=False).decode('utf-8').encode('mbcs')
を使うことでマルチバイトなvimのDictionary形式への変換を行っています。
ソース差分は以下の通り。
diff -cr plugin.orig/visual_studio.py plugin/visual_studio.py
*** plugin.orig/visual_studio.py    Fri Sep 28 20:51:49 2007
--- plugin/visual_studio.py Mon Aug 18 22:59:17 2008
***************
*** 1,3 ****
--- 1,4 ----
+ # -*- coding: ms932 -*-
  '''
  Companion file to visual_studio.vim
  Version 1.2
***************
*** 7,12 ****
--- 8,14 ----
  '''
  
  import os, re, sys, time, pywintypes, win32com.client
+ import simplejson
  
  import logging
  _logging_enabled = False
***************
*** 42,48 ****
          return
      # ExecuteCommand is not synchronous so we have to wait
      _dte_wait_for_build (dte)
!     dte_output (vs_pid, fn_quickfix, 'Output')
      _vim_status ('Compile file complete')
      _vim_activate ()
  
--- 44,50 ----
          return
      # ExecuteCommand is not synchronous so we have to wait
      _dte_wait_for_build (dte)
!     dte_output (vs_pid, fn_quickfix, '出力')
      _vim_status ('Compile file complete')
      _vim_activate ()
  
***************
*** 77,83 ****
          _dte_exception (e)
          _vim_activate ()
          return
!     dte_output (vs_pid, fn_quickfix, 'Output')
      _vim_status ('Build solution complete')
      _vim_activate ()
  
--- 79,85 ----
          _dte_exception (e)
          _vim_activate ()
          return
!     dte_output (vs_pid, fn_quickfix, '出力')
      _vim_status ('Build solution complete')
      _vim_activate ()
  
***************
*** 116,122 ****
          _dte_exception (e)
          _vim_activate ()
          return
!     dte_output (vs_pid, fn_quickfix, 'Output')
      _vim_status ('Build project complete')
      _vim_activate ()
  
--- 118,124 ----
          _dte_exception (e)
          _vim_activate ()
          return
!     dte_output (vs_pid, fn_quickfix, '出力')
      _vim_status ('Build project complete')
      _vim_activate ()
  
***************
*** 147,153 ****
      if not dte: return
      task_list = None
      for window in dte.Windows:
!         if str(window.Caption).startswith('Task List'):
              task_list = window
      if not task_list:
          _vim_msg ('Error: Task List window not active')
--- 149,155 ----
      if not dte: return
      task_list = None
      for window in dte.Windows:
!         if window.Caption.encode('mbcs').startswith('タスク一覧'):
              task_list = window
      if not task_list:
          _vim_msg ('Error: Task List window not active')
***************
*** 155,165 ****
      TL = task_list.Object
      for i in range (1, TL.TaskItems.Count+1):
          TLItem = TL.TaskItems.Item(i)
!         try: filename = TLItem.FileName
          except: filename = '<no-filename>'
          try: line = TLItem.Line
          except: line = '<no-line>'
!         try: description = TLItem.Description
          except: description = '<no-description>'
          print >>fp_task_list, '%s(%s) : %s' % (filename, line, description)
      fp_task_list.close ()
--- 157,167 ----
      TL = task_list.Object
      for i in range (1, TL.TaskItems.Count+1):
          TLItem = TL.TaskItems.Item(i)
!         try: filename = TLItem.FileName.encode('mbcs')
          except: filename = '<no-filename>'
          try: line = TLItem.Line
          except: line = '<no-line>'
!         try: description = TLItem.Description.encode('mbcs')
          except: description = '<no-description>'
          print >>fp_task_list, '%s(%s) : %s' % (filename, line, description)
      fp_task_list.close ()
***************
*** 170,176 ****
  
  def dte_output (vs_pid, fn_output, window_caption, notify=None):
      logging.info ('== dte_output %s' % vars())
!     if window_caption not in ['Find Results 1', 'Find Results 2', 'Output']:
          _vim_msg ('Error: unrecognized window (%s)' % window_caption)
          return
      dte = _get_dte(vs_pid)
--- 172,178 ----
  
  def dte_output (vs_pid, fn_output, window_caption, notify=None):
      logging.info ('== dte_output %s' % vars())
!     if window_caption not in ['検索結果 1', '検索結果 2', '出力']:
          _vim_msg ('Error: unrecognized window (%s)' % window_caption)
          return
      dte = _get_dte(vs_pid)
***************
*** 179,191 ****
      if not window:
          _vim_msg ('Error: window not active (%s)' % window_caption)
          return
!     if window_caption == 'Output':
!         owp = window.Object.OutputWindowPanes.Item ('Build')
          sel = owp.TextDocument.Selection
      else:
          sel = window.Selection
      sel.SelectAll()
!     lst_text = str(sel.Text).splitlines()
      lst_text = _fix_filenames (os.path.dirname(dte.Solution.FullName), lst_text)
      sel.Collapse()
      fp_output = file (fn_output, 'w')
--- 181,193 ----
      if not window:
          _vim_msg ('Error: window not active (%s)' % window_caption)
          return
!     if window_caption == '出力':
!         owp = window.Object.OutputWindowPanes.Item ('ビルド')
          sel = owp.TextDocument.Selection
      else:
          sel = window.Selection
      sel.SelectAll()
!     lst_text = str(sel.Text.encode('mbcs')).splitlines()
      lst_text = _fix_filenames (os.path.dirname(dte.Solution.FullName), lst_text)
      sel.Collapse()
      fp_output = file (fn_output, 'w')
***************
*** 280,286 ****
              startup_project_index = index
          lst_result.append (_dte_project_tree(project))
          index += 1
!     _vim_command ('let s:visual_studio_lst_project = %s' % lst_result)
      _vim_command ('let s:visual_studio_startup_project_index = %s' % startup_project_index)
  
  def _dte_project_tree (project):
--- 282,288 ----
              startup_project_index = index
          lst_result.append (_dte_project_tree(project))
          index += 1
!     _vim_command ('let s:visual_studio_lst_project = %s' % simplejson.dumps(lst_result, ensure_ascii=False).decode('utf-8').encode('mbcs'))
      _vim_command ('let s:visual_studio_startup_project_index = %s' % startup_project_index)
  
  def _dte_project_tree (project):
***************
*** 290,296 ****
      name = _com_property (project, 'Name')
      if not name:
          return []
!     name = str(name)
      properties = _com_property(project, 'Properties')
      if properties:
          try: full_path = str(properties['FullPath'])
--- 292,298 ----
      name = _com_property (project, 'Name')
      if not name:
          return []
!     name = name.encode('utf-8')
      properties = _com_property(project, 'Properties')
      if properties:
          try: full_path = str(properties['FullPath'])
***************
*** 441,447 ****
  def _dte_output_activate (vs_pid):
      dte = _get_dte(vs_pid)
      if not dte: return
!     window = _dte_get_window(dte, 'Output')
      if not window: return
      window.Activate()
  
--- 443,449 ----
  def _dte_output_activate (vs_pid):
      dte = _get_dte(vs_pid)
      if not dte: return
!     window = _dte_get_window(dte, '出力')
      if not window: return
      window.Activate()
  
diff -cr plugin.orig/visual_studio.vim plugin/visual_studio.vim
*** plugin.orig/visual_studio.vim   Thu Sep 27 05:57:47 2007
--- plugin/visual_studio.vim    Tue Aug 19 09:07:25 2008
***************
*** 226,232 ****
  
  function! DTEOutput()
      let &errorfile = g:visual_studio_output
!     call <Sid>DTEExec ('dte_output', &errorfile, 'Output')
  endfunction
  
  "----------------------------------------------------------------------
--- 226,232 ----
  
  function! DTEOutput()
      let &errorfile = g:visual_studio_output
!     call <Sid>DTEExec ('dte_output', &errorfile, '出力')
  endfunction
  
  "----------------------------------------------------------------------
***************
*** 234,243 ****
  function! DTEFindResults(which)
      if a:which == 1
          let &errorfile = g:visual_studio_find_results_1
!         let window_caption = 'Find Results 1'
      else
          let &errorfile = g:visual_studio_find_results_2
!         let window_caption = 'Find Results 2'
      endif
      call <Sid>DTEExec ('dte_output', &errorfile, window_caption)
  endfunction
--- 234,243 ----
  function! DTEFindResults(which)
      if a:which == 1
          let &errorfile = g:visual_studio_find_results_1
!         let window_caption = '検索結果 1'
      else
          let &errorfile = g:visual_studio_find_results_2
!         let window_caption = '検索結果 2'
      endif
      call <Sid>DTEExec ('dte_output', &errorfile, window_caption)
  endfunction
***************
*** 299,305 ****
      endif
      if gui_menu
          echo 'Found '.len(s:visual_studio_lst_dte).' VisualStudio instance(s)'
!         popup! VisualStudio.Solutions
      else
          call <Sid>DTESolutionTextMenu()
      endif
--- 299,305 ----
      endif
      if gui_menu
          echo 'Found '.len(s:visual_studio_lst_dte).' VisualStudio instance(s)'
!         popup! VisualStudio.ソリューション(&S)
      else
          call <Sid>DTESolutionTextMenu()
      endif
***************
*** 307,313 ****
  
  function! <Sid>DTESolutionGuiMenuCreate()
      try
!         aunmenu VisualStudio.Solutions
      catch
      endtry
      let menu_num = 0
--- 307,313 ----
  
  function! <Sid>DTESolutionGuiMenuCreate()
      try
!         aunmenu VisualStudio.ソリューション(&S)
      catch
      endtry
      let menu_num = 0
***************
*** 320,331 ****
          else
              let leader = '\ \ \ &'.menu_num
          endif
!         exe 'amenu <silent> .810 &VisualStudio.&Solutions.'.leader.'\ '.dte_sln.' :call <Sid>DTESolutionMenuChoice('.menu_num.')<cr>'
      endfor
      if len(s:visual_studio_lst_dte) > 0
!         amenu <silent> &VisualStudio.&Solutions.-separator- :
      endif
!     amenu <silent> &VisualStudio.&Solutions.&Refresh :call DTEGetSolutions(1)<cr>
  endfunction
  
  function! <Sid>DTESolutionTextMenu()
--- 320,331 ----
          else
              let leader = '\ \ \ &'.menu_num
          endif
!         exe 'amenu <silent> .810 &VisualStudio.ソリューション(&S).'.leader.'\ '.dte_sln.' :call <Sid>DTESolutionMenuChoice('.menu_num.')<cr>'
      endfor
      if len(s:visual_studio_lst_dte) > 0
!         amenu <silent> &VisualStudio.ソリューション(&S).-separator- :
      endif
!     amenu <silent> &VisualStudio.ソリューション(&S).更新(&R) :call DTEGetSolutions(1)<cr>
  endfunction
  
  function! <Sid>DTESolutionTextMenu()
***************
*** 393,399 ****
      endif
      if gui_menu
          echo 'Found '.len(s:visual_studio_lst_project).' project(s)'
!         popup! VisualStudio.Projects
      else
          call <Sid>DTEProjectTextMenu()
      endif
--- 393,399 ----
      endif
      if gui_menu
          echo 'Found '.len(s:visual_studio_lst_project).' project(s)'
!         popup! VisualStudio.プロジェクト(&J)
      else
          call <Sid>DTEProjectTextMenu()
      endif
***************
*** 401,407 ****
  
  function! <Sid>DTEProjectGuiMenuCreate()
      try
!         aunmenu VisualStudio.Projects
      catch
      endtry
      let menu_num = 0
--- 401,407 ----
  
  function! <Sid>DTEProjectGuiMenuCreate()
      try
!         aunmenu VisualStudio.プロジェクト(&J)
      catch
      endtry
      let menu_num = 0
***************
*** 414,422 ****
          else
              let leader = '\ \ \ &'.menu_num
          endif
!         let project_name_menu = '&VisualStudio.Pro&jects.'.leader.'\ '.escape(project_name, '\. ')
!         exe 'amenu <silent> .820 '.project_name_menu.'.&Build\ Project :call <Sid>DTEProjectMenuBuildChoice('.menu_num.')<cr>'
!         exe 'amenu <silent> .820 '.project_name_menu.'.Set\ Start&up\ Project :call <Sid>DTEProjectMenuStartupChoice('.menu_num.')<cr>'
          exe 'amenu <silent> .820 '.project_name_menu.'.-separator- :<cr>'
          if type(project_children) == type([])
              for child in project_children
--- 414,422 ----
          else
              let leader = '\ \ \ &'.menu_num
          endif
!         let project_name_menu = '&VisualStudio.プロジェクト(&J).'.leader.'\ '.escape(project_name, '\. ')
!         exe 'amenu <silent> .820 '.project_name_menu.'.プロジェクトをビルド(&B) :call <Sid>DTEProjectMenuBuildChoice('.menu_num.')<cr>'
!         exe 'amenu <silent> .820 '.project_name_menu.'.スタートアッププロジェクトに設定(&U) :call <Sid>DTEProjectMenuStartupChoice('.menu_num.')<cr>'
          exe 'amenu <silent> .820 '.project_name_menu.'.-separator- :<cr>'
          if type(project_children) == type([])
              for child in project_children
***************
*** 425,433 ****
          endif
      endfor
      if len(s:visual_studio_lst_project) > 0
!         amenu <silent> .820 &VisualStudio.Pro&jects.-separator- :
      endif
!     amenu <silent> .820 &VisualStudio.Pro&jects.&Refresh :call DTEGetProjects(1, 1)<cr>
  endfunction
  
  function! <Sid>DTEProjectGuiSubMenuCreate(menu, name, value)
--- 425,433 ----
          endif
      endfor
      if len(s:visual_studio_lst_project) > 0
!         amenu <silent> .820 &VisualStudio.プロジェクト(&J).-separator- :
      endif
!     amenu <silent> .820 &VisualStudio.プロジェクト(&J).更新(&R) :call DTEGetProjects(1, 1)<cr>
  endfunction
  
  function! <Sid>DTEProjectGuiSubMenuCreate(menu, name, value)
***************
*** 510,526 ****
  " Menu setup
  
  if has('gui') && ( ! exists('g:visual_studio_menu') || g:visual_studio_menu != 0 )
!     amenu <silent> &VisualStudio.&Get\ File :call DTEGetFile()<cr>
!     amenu <silent> &VisualStudio.&Put\ File :call DTEPutFile()<cr>
      amenu <silent> &VisualStudio.-separator1- :
!     amenu <silent> &VisualStudio.&Task\ List :call DTETaskList()<cr>
!     amenu <silent> &VisualStudio.&Output :call DTEOutput()<cr>
!     amenu <silent> &VisualStudio.&Find\ Results\ 1 :call DTEFindResults(1)<cr>
!     amenu <silent> &VisualStudio.Find\ Results\ &2 :call DTEFindResults(2)<cr>
      amenu <silent> &VisualStudio.-separator2- :
!     amenu <silent> &VisualStudio.&Build\ Solution :call DTEBuildSolution()<cr>
!     amenu <silent> &VisualStudio.Build\ Start&up\ Project :call DTEBuildStartupProject()<cr>
!     amenu <silent> &VisualStudio.&Compile\ File :call DTECompileFile()<cr>
      amenu <silent> &VisualStudio.-separator3- :
      call <Sid>DTESolutionGuiMenuCreate()
      call <Sid>DTEProjectGuiMenuCreate()
--- 510,526 ----
  " Menu setup
  
  if has('gui') && ( ! exists('g:visual_studio_menu') || g:visual_studio_menu != 0 )
!     amenu <silent> &VisualStudio.ファイルを取得(&G) :call DTEGetFile()<cr>
!     amenu <silent> &VisualStudio.ファイルを反映(&P) :call DTEPutFile()<cr>
      amenu <silent> &VisualStudio.-separator1- :
!     amenu <silent> &VisualStudio.タスクリスト(&T) :call DTETaskList()<cr>
!     amenu <silent> &VisualStudio.出力(&O) :call DTEOutput()<cr>
!     amenu <silent> &VisualStudio.検索結果\ 1(&F) :call DTEFindResults(1)<cr>
!     amenu <silent> &VisualStudio.検索結果\ 2(&2) :call DTEFindResults(2)<cr>
      amenu <silent> &VisualStudio.-separator2- :
!     amenu <silent> &VisualStudio.ソリューションをビルド(&B) :call DTEBuildSolution()<cr>
!     amenu <silent> &VisualStudio.スタートアッププロジェクトをビルド(&U) :call DTEBuildStartupProject()<cr>
!     amenu <silent> &VisualStudio.ファイルをコンパイル(&C) :call DTECompileFile()<cr>
      amenu <silent> &VisualStudio.-separator3- :
      call <Sid>DTESolutionGuiMenuCreate()
      call <Sid>DTEProjectGuiMenuCreate()
Visual StudioつまりWindows限定なのでファイルの文字コードはms932としました。
動作確認はVisual Studio 2005でしか行っていません。
visual_studio_ja-20080819.zip
よろしければどうぞ。
Posted at by



2008/08/06


以前試した時は出来なかったんですが、今日試したら動く様になっていました。
growl-for-windows - Google Code

A port of the Mac app Growl for use on Windows machines

http://code.google.com/p/growl-for-windows/
バージョンが1.1から1.2に上がったからかな?
Growl for Windows 1.2
以下のコードで動きました。
まずperl
use strict;
use warnings;
use Encode;
use Net::Growl;
register(host => 'localhost',
         application=>"My Perl App",
         password=>'Realy Secure', );
notify(
       application=>"My Perl App",
       title=>'warning',
       description=>decode_utf8('あめんぼ赤いなアイウエオ'),
       priority=>2,
       sticky=>0,
       password=>'Realy Secure',
);
そしてpython
#!/usr/bin/python
#-*- coding:utf-8 -*-
import Growl
g = Growl.GrowlNotifier(
    applicationName='My Python App',
    notifications=["PyGrowl"],
    defaultNotifications=[0],
    hostname="localhost",
    password="Realy Secure")
g.register()
g.notify(
    icon=open('unk.gif').read(),
    noteType="PyGrowl",
    title='wanings',
    description=u"あめんぼ赤いなアイウエオ",
    sticky=False)
あれ?1.1の時にもdecode_utf8とかu""とか試したんですけどねぇ...。やっぱり1.2になったから?

それと今日、whineというWindowsで動くGrowlアプリケーションを、某カレー通の方に教えて頂きました。ありがとうございました。
Whine
Growl for Windowsではフォント等、ディスプレイのカスタマイズが出来ないのですがwhineでは出来るので、こちらを使おうかと思います。
猫派の私ですが、これはお勧めです。
Posted at by




家では貧弱なマシンを使っているので、極力重い処理は避けたいと色んな設定を入れています。その一つにperlのファイルを開いた際に"ftplugin/perl.vim"がINCを調べるのに実行する perl -e 'print join(q/,/,@INC)'
というコマンドの抑制。ただ単にperlのモジュールパスを調べるいるだけなんですが、ファイルを開くだけでperlを実行されるのも...という方には以下の設定をvimrcに追加すると実行されなくなります。
let g:perlpath="XXX"
このXXXには上記コマンドの実行結果を設定します。
Windows系ならば perl -e "print join(q/,/,@INC)"
で得られる文字列となります。ActivePerlだとおそらく let g:perlpath = "c:/perl/site/lib,C:/perl/lib"
と設定すれば良いかと思います。

ま、最近の人はリッチなマシン使ってる人多いから、メリット無いかな。
ちなみにUNIX系でperlのバージョン上げたりするとモジュールパスが変りますので再度この設定をし直す必要があります。ご注意を。
Posted at by



2008/08/05


途中まで書いて助けを求めたんですが、結局自分で書きました。
CodeRepos::Share - Trac

mattn誰か後は頼んだ... ゴブッ(吐血)

http://coderepos.org/share/changeset/17104
このリンク先のソースは不完全なソースです。

使い方は
:mixiecho
でエコーの一覧表示
:mixiecho ○○遺跡に落書きした!
でポストです。

これでtwitterの様にmixiを使って、マイミク外される事うけあいですね。
mixiecho.js
よかったらどうぞ。
i love vimperator!
※ teramakoさんからi love vimperatorの画像頂きました。
Posted at by




perl版もあったんですね。
Perl App Engine状況報告、Protocol BufferのPerl対応 | エンタープライズ | マイコミジャーナル

Perl App Engineに関連したコードベースにはおもに次の3つがある。

  • Perl App Engine
  • Perl XS module Sys::Protect
  • Protocol Buffers for Perl
http://journal.mycom.co.jp/news/2008/07/29/042/index.html

protobuf-perl - Google Code

Protocol Buffers for Perl.

http://code.google.com/p/protobuf-perl/
さっそく遊んでみました。
まず、protobuf-perlはオリジナルの改造として作られており、スケルトンクラス生成ツール「protoc」をビルドする所からの導入となります。
通常はprotobufディレクトリで # make -j2 protoc
とすれば出来上がります。ただVisual Studioの場合はlibprotocプロジェクトのソース一覧からperlのgeneratorソースである「perl_generator.cc」が外れてしまっているので、追加してからビルドする必要があります。
出来上がれば、「libprotobuf.dll」と「libprotoc.dll」、「protoc.exe」をパスの通った位置にコピーします。
"main.cc"のソースを見て頂ければ分かりますが、protocol buffersのgeneratorはソースが分離されており、main実行時にプラグインを読み込む様な形で登録されています。 int main(int argc, char* argv[]) {

  google::protobuf::compiler::CommandLineInterface cli;

  // Proto2 C++
  google::protobuf::compiler::cpp::CppGenerator cpp_generator;
  cli.RegisterGenerator("--cpp_out", &cpp_generator,
                        "Generate C++ header and source.");

  // Proto2 Java
  google::protobuf::compiler::java::JavaGenerator java_generator;
  cli.RegisterGenerator("--java_out", &java_generator,
                        "Generate Java source file.");


  // Proto2 Python
  google::protobuf::compiler::python::Generator py_generator;
  cli.RegisterGenerator("--python_out", &py_generator,
                        "Generate Python source file.");


  // Proto2 Perl
  google::protobuf::compiler::perl::Generator perl_generator;
  cli.RegisterGenerator("--perl_out", &perl_generator,
                        "Generate Perl source file.");

  return cli.Run(argc, argv);
}
別の言語でgeneratorを作る場合、"XXX_generator.h"と"XXX_generator.cc"を作り、"main.cc"でincludeとRegisterGeneratorを実行すれば動き出す仕組みになっています。キレイな作りですね。

さて出来上がった"protoc"でperlのラッパクラスを作ります。
先日ご紹介した際に使った"person.proto"を使います。ここで注意しなければならないのが、"--perl_out"オプションを使用する際の引数で指定するファイル名がパッケージ名として使われてしまう点です。
perlパッケージ名称がそれっぽくなる様に以下の様に実行します。
# protoc --perl_out=. Person.proto UNIX系の方は"person.proto"から"Person.proto"にリネームしてから実行して下さい。Windowsならばそのまま実行出来ます。
出来上がった"ProtoBuf::Person"は以下の様になりました。
## Boilerplate:
# Auto-generated code from the protocol buffer compiler.  DO NOT EDIT!

use strict;
use warnings;
#use 5.6.1;
use Protobuf;
package ProtoBuf::Person;


use constant TRUE => 1;
use constant FALSE => 0;
## Top-level enums:

## Top-level extensions:

## All nested enums:
## Message descriptors:

our $_PERSON = Protobuf::Descriptor->new(
  name => 'Person',
  full_name => 'protocol.Person',
  containing_type => undef,
  fields => [
    Protobuf::FieldDescriptor->new(
      name => 'name', index => 0, number => 1,
      type => 9, cpp_type => 9, label => 2,
      default_value => "",
      message_type => undef, enum_type => undef, containing_type => undef,
      is_extension => FALSE, extension_scope => undef),
    Protobuf::FieldDescriptor->new(
      name => 'age', index => 1, number => 2,
      type => 5, cpp_type => 1, label => 2,
      default_value => 0,
      message_type => undef, enum_type => undef, containing_type => undef,
      is_extension => FALSE, extension_scope => undef),
  ],
  extensions => [
  ],
  nested_types => [],  # TODO(bradfitz): Implement.
  enum_types => [
  ],
  options => Protobuf::MessageOptions->new(
  ),
);

## Imports:

## Fix foreign fields:

## Messages:
Protobuf::Message->GenerateClass(__PACKAGE__ . '::Person', $_PERSON);

## Fix foreign fields in extensions:
## Services:
実装はMooseを使っており、アクセサに対する制御も行っています。
次にサンプルコード
use strict;
use warnings;
no warnings qw(deprecated);
use Protobuf::Decoder;
use ProtoBuf::Person;

my $person = ProtoBuf::Person::Person->new;
$person->set_name('mattn');
$person->set_age(18);

open my $out, '>', 'person_perl.txt';
print $out $person->serialize_to_string();
close $out;

my $other = ProtoBuf::Person::Person->new;
open my $in, '<', 'person_perl.txt';
$other->parse_from_string( do { local $/;<$in> } );
close $in;

printf "name:%s\nage:%d\n", $other->name(), $other->age();
前回と同様にファイルに書き出し、ファイルから読み込んでparse_from_stringで実体化します。結果は前回と同様です。

cppは別として、pythonの場合はpickleが、javaの場合はSerializableがあり、Google Protocol Buffersの使い所が難しい気もしましたが、色々な言語がサポートされてくると言語をまたいだデータ共有という形で使えそうな気がして来ました。特にmemcachedを使ったダイナミックインスタンスエクスポートとか面白いかもしれませんね。
またperl版がMooseと絡んでいる所がとても通ぽくて良いですね。:-)
どうせなら、PerlIOでシリアライズ出来たらな...と思ってしまいましたが、もしかしたら今後サポートされるかもしれませんね。

他にもprotocol buffersはいろんな言語でインプリメンツされているので、皆さんもお好きな言語で遊んでみてはいかがでしょうか。
Posted at by



2008/08/04


vimではコマンドモード時に、<c-x>で現在のディレクトリを補完するようにしているので、これがvimperatorでもやれると便利と思った。
twitter.jsでは「%URL%」とか「%TITLE%」で現在のURLが置き換えられる様になってるけど、他のコマンドでも使いたい場合もある。
例えば、そろそろ誰かが「mixiecho」ってコマンド作ったとして :mixiecho ちょっwwwコレ http://example.jp/ とかやりたい時には、またtwitter.jsと同じ実装しなきゃいけない。
ま、こんなので困るのは私だけかも。
(function() {
    liberator.mappings.addUserMap([liberator.modes.COMMAND_LINE], ['<c-x>'],
        "insert current URL to command line",
        function () {
            var cmdline = liberator.CommandLine();
            var curcmd = ':' + cmdline.getCommand();
            if (!curcmd.match(/ $/)) curcmd += ' ';
            cmdline.open(curcmd, liberator.buffer.URL);
        }
    );
})();
コマンドラインのコントロール名は決まっているので、そこにkeyEventをdispatchEventしても良かったけど、まっいいや。
Posted at by



2008/07/31


twissr.js
※右クリックして名前を付けて保存です。
※ソース閲覧禁止です。
Posted at by



2008/07/30


kuさんが面白い物見つけてくれました。
mixi for iPhoneから発掘されたmixi日記投稿用API « ku

iPhoneからぜんぜん日記を書く手段がなかったらmixiから、mixi for iPhoneという日記を書いたりするiPhoneアプリが公開されました!

新しいアプリに新しいAPI、日記が投稿できるアプリなら日記投稿用のAPIというわけでmixiのあしあとAPI発掘と同じように掘り起こして見つけました。

ほかのAPIと同様、認証はWSSEでatomPubで日記を書くことができるようになっていました。エンドポイントはhttp://mixi.jp/atom/diary/member_id=mixiIDです。

http://ido.nu/kuma/2008/07/30/digging-mixi-for-iphone-application-and-new-api-for-posting-a-diary-with-a-photo/
って事でさっそくPlagger用にPublish::MixiDiary書いてみました。出来立てほやほやなので、あまりテストしていません。codereposに上げているので、良かったらテストしてみて下さい。

これでtwitterの様にmixiを使って、マイミク外される事うけあいですね。

Publish::MixiDiary
ちなみに中ではたけまるさんのAtompubを使っています。
Posted at by



2008/07/29


まずは素晴らしい出だしだと思う。
google-app-engine-oil - Google Code
Yet another web framework on Google App Engine.

Google App Engine Oil (GAEO) is an open-source web framework running on Google App Engine. It enables the web development on App Engine quick and less configurations.

http://code.google.com/p/google-app-engine-oil/
railsを狙ってるのかなぁ。
これから勉強を始めますが、まずは1分でアプリが作れてしまうという素晴らしい所をご紹介します。

Google App Engine Oil(GAEO)は、上記URLからダウンロード(もしくはsvn trunk取得)して下さい。
"gaeo/bin"にパスが通っている状態から説明します。

アプリケーションテンプレートの作成

gaeoコマンドで生成します。 # gaeo.py hello
The hello project has been created.
これだけでも動きます。試しに
# cd hello
# dev_appserver.py .
...
としてブラウザで"http://localhost:8080/"を開くと
gaeo1
いう画面が起動します。これだけでも実はモデルやディスパッチャやコントローラが生成され、テンプレートが用意されているのでいきなりコーディングが始められます。

コントローラの作成

gaeogenコマンドで生成します。 # gaeogen.py controlller say
Creating application/templates/say/ ...
Creating say.py
これでsayコントローラが生成されます。中身は from gaeo.controller import BaseController

class SayController(BaseController):
    pass
こんな感じ。あとはこれを少し修正して #!-*- coding:utf-8 -*-
from gaeo.controller import BaseController

class SayController(BaseController):
  def hello(self):
    self.render(text = 'こんにちわ世界')
とし、"http://localhost:8080/say/hello"にアクセスすると"こんにちわ世界"と表示されます。また"templates/say/"に"hello.html"を以下の様に作成し こんにちわ {{ word }}
先ほどのsay.pyを以下の様に書き換えます。
#!-*- coding:utf-8 -*-
from gaeo.controller import BaseController

class SayController(BaseController):
  def hello(self):
    self.render(
        template = 'hello',
        values = { 'word' : '世界' }
    )
これでテンプレートを使ったページが出来上がります。

目新しい点

私はここに注目しました。
これまでjsonやxmlをGAEから出力する際には、Content-Typeの設定が必要であったりしましたがGAEOならば上記textやtemplateと同じ様に
#!-*- coding:utf-8 -*-
from gaeo.controller import BaseController

class SayController(BaseController):
  def hello(self):
    self.render(
        xml = '<xml />',
    )
としたり #!-*- coding:utf-8 -*-
from gaeo.controller import BaseController

class SayController(BaseController):
  def hello(self):
    self.render(
        json = '{ word : "Hello, World!" }',
    )
とするだけで、正しいContent-Typeを返してくれるようになります。

期待したい所

リクエストパラメータself.paramsでパラメータをdictとして受け取れたり、self.sessionで簡単にセッションが扱えたりと開発者にとって扱いやすいフレームワークになっていると思われます。
ただ現状gaeogenコマンドではcontrollerしか生成出来ていません。おそらく今後modelの生成も出来る様になるかと思いますので今後に期待したいです。出来ればrails相当になってくれれば良いですね。

まだちゃんと勉強してないのでこれから色々調べていきます。
Posted at by



2008/07/28


不具合報告を頂きまた。自分でも動かなくなってたのに気付いてませんでした。
Big Sky :: XSLとjQuery/HTMLだけで作る、amazon最速検索
http://mattn.kaoriya.net/software/lang/javascript/20080605165643.htm
どうやら、jQueryがcacheを無効にする時に使用する特殊パラメータ"_"がAmazonで蹴られる様になったのが原因です。
結構ハマった(30分)割には、直し方は簡単。
$.ajaxSettings.cache = true; まぁ、Amazon検索にはリアルタイムなキャッシュ無効は必要なさそうなので、これで良しとします。
一応、直ったのでこの記事にも実行例置いときます。コードは↑のリンク先を参照下さい。

続きを読む...

Posted at by



2008/07/25


これはいいね。
Google App Engine Blog: Some small updates!
We're happy to announce we've released some small updates to Google App Engine. Among the more significant changes:
  • More apps: Want to create more than 3 applications with your App Engine account? Now you can now create up to 10!
  • Time windows for Dashboard graphs: Zoom in on the data in your dashboard to get a more accurate picture of whats going on. You can zoom in to see graphs for the last 24, 12, and 6 hour periods.
  • Logs export: You can now use appcfg.py to download your application's logs in plaintext format. Use appcfg.py --help for more information on how to download your logs.
  • Send email as logged in user: If you're using the users API, you can now send email from the email address of the currently-logged-in user.
最新版のダウンロードはこちらから。
作れるアプリ数が10個に増え
あと8個
ダッシュボードのデータ時系列が拡大できる様になって
6時間単位で拡大可能
appcfg.pyでログがエクスポート出来る様になって # appcfg.py request_logs <directory> <output_file> 現在ログイン中のユーザのメールアドレスが得られる様になったらしい。

やるね。Google App Engine。
Posted at by



2008/07/24


とは言ってもUser Agent Switcherが必要なのですが...

使い方は
:ua MyUserAgent
でUser Agent Switcherに"MyUserAgent"という名前で登録されたUser Agentに変更します。
:ua Default
でリセット :ua!
で設定ダイアログ表示となります。
急遽モバイルページに行きたいけどメニューに手を伸ばすの面倒臭いって方には使えるかも。
ちなみに私はデフォルトで登録されていた物の他に、DoCoMo、Vodafone、SoftBank、Hatenaクローラ等のUserAgentを登録してます。
uaSwitch.js
よろしければどーぞ。
Posted at by



2008/07/23


遅ればせながらGoogle Protocol Buffersで遊んでみました。
protobuf - Google Code
Protocol Buffers are a way of encoding structured data in an efficient yet extensible format. Google uses Protocol Buffers for almost all of its internal RPC protocols and file formats.
まず、インストールは以下の様に行いました。なおprotobufを展開したフォルダは以下のフォルダ。 C:¥temp¥protobuf-2.0.0beta¥

cpp版のインストール

展開したフォルダのvsprojectsフォルダにある"protobuf.sln"をVisual Studioで開きReleaseビルド。
必要であれば環境変数PATHの通る場所へ"protoc.exe"、"libprotobuf.dll"、"libprotoc.dll"を置く。また"libprotobuf.lib"と"libprotoc.lib"はコンパイル時に必要になるので分かりやすい位置に置いておく。

java版のインストール

まずApache MavenをインストールしPATHを通す。
次にjavaフォルダへ移動し C:¥temp¥protobuf-2.0.0beta¥java> mvn test install
とすれば必要なライブラリがダウンロードされ、テスト後インストールされる。
targetフォルダに"protobuf-java-2.0.0beta.jar"が出来るのでこれまた分かりやすい位置に置く。

python版のインストール

pythonフォルダに移動し、cpp版をビルドした際に出来上がった"protoc.exe"にパスが通っている事を確認して C:¥temp¥protobuf-2.0.0beta¥python> python setup.py bdist_wininst
...
C:¥temp¥protobuf-2.0.0beta¥python> cd dist
C:¥temp¥protobuf-2.0.0beta¥python¥dist> protobuf-2.0.0beta.win32.exe
※Windowsじゃない人は"bdist_wininst"の代わりに"bdist_rpm"とか"install"とか...

テストコードによる検証

まずデータ定義となるprotoファイルを作る。詳しい説明は省略するが知りたい人はココとかココとか。今回は名前と年齢を格納出来るPersonクラスを扱う。
person.proto
package protocol;

message Person {
  required string name = 1;
  required int32  age  = 2;
}

このファイルを指定して、スタブクラスを生成する。
まずcpp版。 C:¥temp¥protobuf-2.0.0beta¥tmp> protoc --cpp_out=. person.proto
これで"person.pb.cc"と"person.pb.h"が生成される。以下テストコード。
test.cpp
#include <iostream>
#include <fstream>
#include <person.pb.h>

void save(const char* fn) {
    protocol::Person person;

    person.set_name("mattn");
    person.set_age(18);

    std::ofstream ofs(fn);
    person.SerializeToOstream(&ofs);
    ofs.close();
}

void load(const char* fn) {
    protocol::Person person;

    std::ifstream ifs(fn);
    person.ParseFromIstream(&ifs);

    std::cout <<
        "name:" << person.name() <<
        ",age:" << person.age() << std::endl;
}

int main(int argc, char* argv[]) {
    if (argc != 2) return -1;

    save(argv[1]);
    load(argv[1]);

    return 0;
}
ビルドは以下の手順で行う。 C:¥temp¥protobuf-2.0.0beta¥tmp> cl -I../src -I. /EHsc test.cpp person.pb.cc libprotoc.lib libprotobuf.lib
実行すると C:¥temp¥protobuf-2.0.0beta¥tmp> test person_cpp.txt
name:mattn,age:18
と表示される。そう...18歳です。ウソです。
生成される"person_cpp.txt"は以下の様なファイルとなる。

^Emattn^P#
次にjava版。"person.proto"というファイル名からPersonクラスを生成する際にファイル名がバッティングしてしまうので"persons.proto"という別名にコピーしておく。
C:¥temp¥protobuf-2.0.0beta¥tmp> protoc --java_out=. persons.proto
"protocol/Persons.java"が生成される。
テストコードは以下の通り。
test.java
import protocol.Persons;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class test {
    public static void main(String[] args) throws Exception {
        Persons.Person.Builder personBuilder = Persons.Person.newBuilder();
        Persons.Person mattn = personBuilder.setName("mattn").setAge(18).build();

        FileOutputStream fos = new FileOutputStream(args[0]);
        mattn.writeTo(fos);
        fos.close();

        FileInputStream fin = new FileInputStream(args[0]);
        mattn = Persons.Person.parseFrom(fin);
        fin.close();

        System.out.printf("name:%s,age:%d", mattn.getName(), mattn.getAge());
    }
}
ビルドは以下の手順で行う。 C:¥temp¥protobuf-2.0.0beta¥tmp> javac -classpath protobuf-java-2.0.0beta.jar; test.java
実行結果はcpp版と同様。

最後にpython版。
C:¥temp¥protobuf-2.0.0beta¥tmp> protoc --python_out=. person.proto
"person_pb2.py"が生成される。以下テストコード
test.py
import sys
from person_pb2 import Person

fn = sys.argv[1]

person = Person()
person.name = "mattn"
person.age = 18
open(fn, "w").write(person.SerializeToString())

person = Person()
person.ParseFromString(open(fn).read())
print "name:%s,age:%d" % (person.name, person.age)
実行結果はcpp版、java版と同様。

所感

結局の所、"protocol buffers"とは、"Serialize Format"生成ツールおよびライブラリと、"Serializable"なクラス郡と言った所だろうか。
実際にはRPC等で転送してみないと良さは分からないかもしれないけど、XMLの様に自信がValidation可能な物ではなさそうなので転送の際には外側からデータのサイズ送信やチェックサム実施も必要になるかもしれない。
そういった意味では、「【ハウツー】XMLはもう不要!? Google製シリアライズツール「Protocol Buffer」 (2) ダウンロードとインストール | エンタープライズ | マイコミジャーナル」の"XMLはもう不要!?"には少し疑問を感じる。

時間があれば、ネットワーク上にシリアライズしたデータを転送してみたいと思う。
Posted at by




少しずつですがバージョンアップしています。
The Unofficial Blosxom User Group :: Blosxom 2.1.0 released

New features

  • Support for external config files and multi-instance installations via environment variables. (Inspired and loosely based on the Debian patches for Blosxom 2.0.2.)
  • Support for multiple plugin directories using $plugin_path and @INC.
  • Support for configuring the plugins to use in a config file.
  • The default feed templates are now in RSS 2.0 format and prevent duplicated stories in feed readers or aggregators if posts are changed after publishing using a GUID element in each feed item.
  • There's now an encoding setting which controls what charset your blog and feeds should be marked as.
http://blosxom.ookee.com/blog/news/blosxom-2.1.0-released.html
バージョンアップの内容としては以下の通り。
  • 環境変数によるconfigファイルの読み込み
  • 複数のプラグインディレクトリをサポート
  • configファイルによるpluginの設定が可能に
  • デフォルトのRSSフィードがRSS2.0に
  • encodingの設定が可能に
また幾らかのバグフィックスも含んでいます。さっそくこのサイトでも導入しました。大きな修正点はありませんでしたがRSS1.0からRSS2.0に修正した事でフィードリーダに更新が配信されてしまったかと思います。
configファイルはmod_envを使って SetEnv BLOSXOM_CONFIG_FILE /path/to/my/blosxom/config/file/bloxsom.conf
とすることでblosxom.cgiを全く弄らずに動かす事が出来る様になりました。

しばらくは様子見です。
Posted at by




最近TheSchwartが賑わってる様ですね。
TheSchwartzで仕事をあとにまわす - bits and bytes

なければ勝手に作ってくれるのかなと思ってSYNOPSISのコードを実行してみたけどやっぱり自動でできたりはしなそうだったので調べたらCatalyst and TheSchwartz: Reliable JobQueue in a great framework - Voxschema.sqlを使うといいと書いてありました。このスキーマをmysqlで実行すればTheSchwartzのキューを管理するためのテーブルが出来上がります。

http://labs.gmo.jp/blog/ku/2008/06/theschwartz.html

Twitter / ku: TheSchwartzおもしろかった sqliteで動...

TheSchwartzおもしろかった sqliteで動くようにしたいけどsqliteようのschemeかきかたわかんない

http://twitter.com/ku/statuses/843135311

って事でSQLiteでも動く様に出来ないか調べてみました。
schema.sqlは、MySQL用のスキーマで CREATE TABLE funcmap (
        funcid         INT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT,
        funcname       VARCHAR(255) NOT NULL,
        UNIQUE(funcname)
);

CREATE TABLE job (
        jobid           BIGINT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT,
        funcid          INT UNSIGNED NOT NULL,
        arg             MEDIUMBLOB,
        uniqkey         VARCHAR(255) NULL,
        insert_time     INTEGER UNSIGNED,
        run_after       INTEGER UNSIGNED NOT NULL,
        grabbed_until   INTEGER UNSIGNED NOT NULL,
        priority        SMALLINT UNSIGNED,
        coalesce        VARCHAR(255),
        INDEX (funcid, run_after),
        UNIQUE(funcid, uniqkey),
        INDEX (funcid, coalesce)
);

CREATE TABLE note (
        jobid           BIGINT UNSIGNED NOT NULL,
        notekey         VARCHAR(255),
        PRIMARY KEY (jobid, notekey),
        value           MEDIUMBLOB
);

CREATE TABLE error (
        error_time      INTEGER UNSIGNED NOT NULL,
        jobid           BIGINT UNSIGNED NOT NULL,
        message         VARCHAR(255) NOT NULL,
        funcid          INT UNSIGNED NOT NULL DEFAULT 0,
        INDEX (funcid, error_time),
        INDEX (error_time),
        INDEX (jobid)
);

CREATE TABLE exitstatus (
        jobid           BIGINT UNSIGNED PRIMARY KEY NOT NULL,
        funcid          INT UNSIGNED NOT NULL DEFAULT 0,
        status          SMALLINT UNSIGNED,
        completion_time INTEGER UNSIGNED,
        delete_after    INTEGER UNSIGNED,
        INDEX (funcid),
        INDEX (delete_after)
);
というSQLなので、これをSQLite用に書き換えます。
cl.pocari.org - SQLite で auto-increment なフィールドを作成する方法

つまり,SQLite で auto-increment なフィールドを作りたければ,INTEGER PRIMARY KEY を指定してあげればいいらしい.

http://cl.pocari.org/2006-02-12-1.html
こちらの記事でも書かれている通り、SQLiteではINTEGER PRIMARY KEYを指定すればok。
※ちなみにUNSIGNEDとかNOT NULLとか付けると動かないです。

で書き換えたSQLは以下の通り
CREATE TABLE funcmap (
        funcid         INTEGER PRIMARY KEY,
        funcname       VARCHAR(255) NOT NULL,
        UNIQUE(funcname)
);

CREATE TABLE job (
        jobid           INTEGER PRIMARY KEY,
        funcid          INTEGER NOT NULL,
        arg             MEDIUMBLOB,
        uniqkey         VARCHAR(255) NULL,
        insert_time     INTEGER UNSIGNED,
        run_after       INTEGER UNSIGNED NOT NULL,
        grabbed_until   INTEGER UNSIGNED NOT NULL,
        priority        SMALLINT UNSIGNED,
        coalesce        VARCHAR(255),
        UNIQUE(funcid, uniqkey)
);
CREATE INDEX job_idx_1 ON job (funcid, run_after);
CREATE INDEX job_idx_2 ON job (funcid, coalesce);

CREATE TABLE note (
        jobid           INTEGER NOT NULL,
        notekey         VARCHAR(255),
        value           BLOB
);

CREATE TABLE error (
        error_time      INTEGER UNSIGNED NOT NULL,
        jobid           INTEGER NOT NULL,
        message         VARCHAR(255) NOT NULL,
        funcid          INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX error_idx_1 ON error (funcid, error_time);
CREATE INDEX error_idx_2 ON error (error_time);
CREATE INDEX error_idx_3 ON error (jobid);

CREATE TABLE exitstatus (
        jobid           INTEGER PRIMARY KEY,
        funcid          INTEGER NOT NULL DEFAULT 0,
        status          SMALLINT UNSIGNED,
        completion_time INTEGER UNSIGNED,
        delete_after    INTEGER UNSIGNED
);
CREATE INDEX exitstatus_idx_1 ON exitstatus (funcid);
CREATE INDEX exitstatus_idx_2 ON exitstatus (delete_after);
あとはコレを # sqlite3 the_schwartz < theschwartz_schema.sql としてDBファイルを作る。
さてサーバのコードはDSNを"dbi:SQLite:the_schwartz"にするだけ。
#!/usr/bin/env perl
package MyWorker;
use strict;
use utf8;
use warnings;
use base qw( TheSchwartz::Worker );
use Data::Dumper;

binmode STDERR, ":encoding(cp932)" if $^O eq "MSWin32";

sub work {
    my ($class, $job) = @_;
    warn "お仕事ですよ! @{[ Dumper($job->arg) ]}\n";
    $job->completed();
}

package main;
use strict;
use warnings;
use TheSchwartz;

my $client = TheSchwartz->new(
    databases => [ +{ dsn => 'dbi:SQLite:the_schwartz' } ]
);
$client->can_do('MyWorker');
$client->work();
さらにクライアントのコード
#!/usr/bin/env perl
use strict;
use warnings;
use TheSchwartz;

my $client = TheSchwartz->new(
    databases => [ +{ dsn => 'dbi:SQLite:the_schwartz' } ]
);
$client->insert('MyWorker' => +{ hoge => "fuga" });
実行すると
お仕事ですよ! $VAR1 = {
          'hoge' => 'fuga'
        };
という風にサーバ側で表示されます。 ちなみに、DBD::SQLiteのバグで
closing dbh with active statement handles at .../Data/ObjectDriver/Driver/DBI.pm line 566.
という表示が出る様ならばココにある"issue-17603.tar.gz"のパッチを当てるといいです。どうやらこのバグ、デグレっぽいですね。

ま、結局SQLiteでやっちゃったら同一マシンだし負荷分散にはならない(UIロックを避けるという意味では有用)んですけどね。
Posted at by



2008/07/11


卑怯と言われようが構いません。

let X = "ひだまり"
let _ = "スケッチ"

try
  X / _ / X < また見て下さいね
catch /^Vim\%((\a\+)\)\=:E488/
  echo eval(
    \  substitute(
    \    substitute(
    \      v:exception,
    \      '^.*E\(\d\+\).*:\s*\(\(.\) / \(.\).*\). <\(.*\)$',
    \      '\2"\3".(\1-123)."\5"', ''), '/', '.', 'g'))
endtry
Posted at by



2008/07/08


http://subtech.g.hatena.ne.jp/cho45/20080708/1215450151
http://subtech.g.hatena.ne.jp/miyagawa/20080708/1215473551 #!/usr/bin/env python
# -*- coding: utf-8 -*-

class X:
  def __init__(self):
    self.s = "ひだまり"

  def __lt__(self, v):
    print "%s %s" % (self.s, v)
    return self
 
  def __div__(self, v):
    if isinstance(v, int):
      self.s += "スケッチ"
    else:
      self.s += "365"
    return self

X = X()
_ = 1

X / _ / X < "来週も見てくださいね!"
ごめんなさい。それが何かしらない...orz
Posted at by



2008/07/07


twitter APIはXMLにリクエスト投げてもBUSY画面でtext/htmlなヘッダを返す時があるので、gtktwitterでは応答ヘッダがapplication/xmlじゃ無いときは処理しない様にしてあったんですが、wassrpod.plはAPI応答にtext/htmlを返していたので少し手を入れさせて頂き、見事gtktwitterでwassrにポストする事が出来ました。あとプロフアイコンもPROXY経由で取れる様に修正しました。おかしかったら直して下さい(id:yappo)
wassrpod経由でgtktwitter
動かすには、環境変数「HTTP_PROXY」に「localhost:9277」を設定してgtktwitterを起動すればok。ただtwitterの設定が残ってるだろうから、UN*Xならば「~/.config/gtktwitter/config」を、Windowsならば「~/Application Data/gtktwitter/config」を修正して起動して下さい。同じアカウント名使ってるならばそのままでも動くかも。

ま、gtkwassrもあるんですけどね...

関連URL:YappoLogs: WassrPodというMacからwassrを快適に使うツールを作ったよ
Posted at by



2008/06/27


TheSchwartzも良いのですが、MySQLが必要だったりポーリングのタイムラグがあったりと、要件に合わない場合というのも出て来たりします。こんな時はどうするか、解決策の一つにメッセージバスがあります。
メッセージバスとして一般的に有名なのが、DBusです。Linux等では数多くのアプリケーションが採用しており、GNOMEもBonoboからの移行を表明、KDE4でも採用される様です。ちなみに私が愛用している軽量デスクトップROXは当初に採用しており、ちょっとしたpythonのコードでリッチなアプリケーション間通信が実現出来ています。他にも既に色々なプロジェクトでDBusが使われています。
さて今日はNet::DBusを使い、TheSchwartzと似たような事が出来ないかをWindows上でやってみようかと思います。
まず、DBusのWindows実装であるwinDBusをインストールします。winDBusはこちらで配布されておりバイナリもダウンロード出来ます。
ここでまず、DBusの動作を確認します。winDBusを展開したディレクトリの中のbinフォルダに移動し以下の様にしてデーモンを起動します。 C:¥winDBus¥> dbus-daemon.exe --config-file=C:/winDBus/dbus/bin/session.conf winDBusを"C:¥Programme"に展開された場合には C:¥Programme¥> dbus-daemon.exe --session とするだけでも動作します。winDBusの場合、unixドメインソケットをサポートしていませんので、上記session.confに書かれている <listen>tcp:host=localhost,port=12434</listen>
の値を環境変数「DBUS_SESSION_BUS_ADDRESS」に設定します。 set DBUS_SESSION_BUS_ADDRESS=tcp:host=localhost,port=12434 その上でdbus-daemon.exeと同じ位置にあるtest_names.exeを実行し Successfully acquired name 'org.freedesktop.DBus.Test'
Successfully acquired name 'org.freedesktop.DBus.Test-2'
Successfully acquired name 'org.freedesktop.DBus.Test_2'
と表示されれば動作確認は終了です。
次にNet::DBusをインストールします。まずNet::DBusはビルドにpkg-configを使うので`pkg-config --cflags dbus-1`が扱えないVCではビルドに難があります。私の場合はMinGWを使用しました。
まず、winDBusに同梱されていなかったdbus-1.pcを作成します。winDBusをインストールしたディレクトリを指定して prefix=C:/winDBus/dbus
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include

Name: D-Bus
Description: D-Bus Protocol Library
Version0.90
Libs: -LC:/winDBus/dbus/lib -ldbus-1
Cflags: -IC:/winDBus/dbus/include
という内容でdbus-1.pcを保存します。次に環境変数PKG_CONFIG_PATHにこのdbus-1.pcを保存したディレクトリを設定し、cpanからインストールします。おそらくこれでインストール出来るかと思いますが、出来なかった方はVCのコンパイラのパスを外し、MinGWのパスを設定して確認して見て下さい。
これでようやくperlからDBusを使える準備が揃いました。
さて、サーバのソース。DBusのセッションからバスを取得し、サービスをエクスポート、ワーカーの役目をするオブジェクトを作成します。ワーカーオブジェクトにはJobStartというメソッドを定義します。
#!/usr/bin/perl

use strict;
use warnings;
use threads;
use YAML;
use Net::DBus;
use Net::DBus::Service;
use Net::DBus::Reactor;

package MyWorker;
use base qw(Net::DBus::Object);
use Net::DBus::Exporter qw(net.kaoriya.mattn.DBusWorker);

sub new {
    my $class = shift;
    my $service = shift;
    my $self = $class->SUPER::new($service, "/MyWorker");
    bless $self, $class;
    
    return $self;
}

dbus_method("JobStart", [["dict", "string", "string"]], [["dict", "string", "int32"]]);
sub JobStart {
    my $self = shift;
    my $job = shift;
    my $thread = threads->new(sub {
        my $job = shift;
        printf "processing job%03d : %s\n", threads->tid, $job->{request};
        sleep 10;
        printf "done job%03d\n", threads->tid;
    }, $job);
    return { "id" => $thread->tid };
}

package main;

my $bus = Net::DBus->session();
my $service = $bus->export_service("net.kaoriya.mattn.DBusWorker");
my $object = MyWorker->new($service);
Net::DBus::Reactor->main->run();
DBusは非同期通信も提供していますが、リクエストを連続で送った際サーバがビジー状態だとクライアントが側が応答を待ってロックします。
そこで上記ソースではスレッドを起こし、重たい処理をさせる様になっています。スレッド内では"threads->tid"で言わばジョブ番号の役割をするスレッドIDを使ってユニークな処理が出来るかと思います。
さらにクライアントのコード。 #/usr/bin/perl

use strict;
use warnings;

use Net::DBus;
use Net::DBus::Reactor;
use Net::DBus::Callback;
use Net::DBus::Annotation qw(:call);

my $request = shift @ARGV || 'request at '.time();
my $bus = Net::DBus->session();
my $service = $bus->get_service("net.kaoriya.mattn.DBusWorker");
my $object = $service->get_object("/MyWorker");
my $res = $object->JobStart({ request => $request });
printf "job was accepted as job%03d\n", $res->{id};
DBusセッションからもらったMyWorkerオブジェクトのJobStartメソッドにリクエスト文字列を指定して送信します。
まずクライアントの実行結果は C:¥winDBus¥test¥> perl client.pl
job was accepted as job001
C:¥winDBus¥test¥> perl client.pl
job was accepted as job002
C:¥winDBus¥test¥> perl client.pl
job was accepted as job003
クライアントではjob001を実行した後、10秒待ってjob002、job003を投入しました。そしてサーバの実行結果 processing job001 : request at 1214560175
done job001
processing job002 : request at 1214560192
processing job003 : request at 1214560195
done job002
done job003
おぉ!非同期ですね!
DBusでは、引数の構成および戻り値の構成をdbus_methodにて指定でき、文字列(string)や数値(int32等)、配列(array)、タプル(tuple)、構造体(struct)を引き渡したり、戻り値として返したり出来ます。
さらに、Net::DBusに同梱されている"example-client-async.pl"の様に非同期送信結果を受け取り、完了イベントで結果を受け取るという事も出来ます。 #/usr/bin/perl

use warnings;
use strict;

use Net::DBus;
use Net::DBus::Reactor;
use Net::DBus::Callback;
use Net::DBus::Annotation qw(:call);

my $bus = Net::DBus->session();

my $service = $bus->get_service("org.designfu.SampleService");
my $object = $service->get_object("/SomeObject");

print "Doing async call\n";
my $reply = $object->HelloWorld(dbus_call_async, "Hello from example-client.pl!");

my $r = Net::DBus::Reactor->main;

sub all_done {
    my $reply = shift;
    my $list = $reply->get_result;
    print "[", join(", ", map { "'$_'" } @{$list}), "]\n";

    $r->shutdown;
}

print "Setting notify\n";
$reply->set_notify(\&all_done);

sub tick {
    print "Tick-tock\n";
}


print "Adding timer\n";
$r->add_timeout(500, Net::DBus::Callback->new(method => \&tick));

print "Entering main loop\n";
$r->run;
今回はWebで扱いやすい様に、非同期で結果を待機する処理ではなくサーバ側でスレッドを実行していますが、デスクトップクライアントを作る際にはdbus_call_asyncを使う事になるかと思います。

TheSchwartzも面白いですが、DBusも面白いですね。
perlの他にも、C(GLib)はもちろんpythonやJava等でも実装されています(参照)。
皆さんも試してみては如何でしょうか。
Posted at by




vimで自前でYAML読み込むとか面倒くさすぎる。
こう言う時はif_pythonとかif_perlとかを使わせてもらう。運良くvimスクリプトはjsonと相性が良いのでYAMLを読み込み、JSONに変換してvimに戻してあげる。
function! LoadYAML(file)
  perl << EOF
use YAML::Syck;
use JSON::Syck qw(Dump);
eval {
  VIM::DoCommand("let ret = " . Dump(LoadFile("".VIM::Eval('a:file'))));
};
VIM::DoCommand("let v:errmsg = substitute('$@', \"\\n\", '', 'g')") if $@;
EOF
  if !exists('ret')
    throw v:errmsg
  endif
  return ret
endfunction

echo LoadYAML("config.yaml")
戻り値はDictionary形式になります。
読み込めなかった場合はthrowしているのでvim7限定になりますがcatchしてやって下さい。

さて、if_perlですがperl510で動かなくなってました。今日パッチを作成してvim-devに送ったのですが、如何せん自信がありません(Shibuya.xsでvimmerな皆様、どうか私にお力をお貸し下さい)。
パッチは
vim72-perl510-fix.diff
にあります。不具合報告等あればご連絡下さい。
Posted at by



2008/06/24


ようやくblosxomで動く用になりました。
tomblooハックス - share on WordPressにポストするためのMetaWeblog API « ku

アップデート 2008.6.24

mattnさんにいただいたBig Sky :: tomblooハックス90_MetaWeblog.jsのパスワードをパスワードマネージャに保存するパッチを文字化けしないようにしてtombloo - Google Codeにコミットしました。ページのView raw fileのところからダウンロードしてください。

http://ido.nu/kuma/2008/06/07/werwer/
google codeのtomblooから90_MetaWeblog.jsを取得し、"extensions/tombloo@brasil.to/chrome/content/library/"に入れると動きます。詳しい説明はkuさんのページを確認して下さい。
kuさんのスクリプトから、若干手を入れさせて頂いていてXMLRPCのエントリポイントである"extensions.tombloo.posters.MetaWeblog.endpoint"の他に"extensions.tombloo.posters.MetaWeblog.mediapath"という設定が出来る様になっています。
ブログツールの中には画像をアップロードするパスが設定出来る物もあり、環境に合わせて
mediapathの設定
の様に設定します。これで90_MetaWeblog.jsが使えるブログツールが増えたかと思います。恐らくMTでもendpointを http://www.example.com/cgi-bin/mt/mt-xmlrpc.cgi の様に設定すれば動きます。確認してませんが。
今回blosxomでは"SourceForge.net: BXR: Blosxom XML-RPC Interface"を使い、特定のカテゴリ(90_MetaWeblog.jsは「reblog」というカテゴリ)のみ
  • インデックスやRSSから隠す
  • dynamic_cacheしない
  • アクセスランキングに載せない
処理を入れ、reblogサイトっぽく動く様になりました。名づけてreblosxomとします!
blosxom-xmlrpc.cgiを色々なブログツールに対応させる為のパッチを当てれば、日本語周りや動的なカテゴリ変更などにも対応出来るblosxom-xmlrpc.cgiが出来上がります。オリジナルのBXRで上手く行かない人は試してみて下さい。
実際に以下の様にして
reblosxomでポスト
ポストした結果が「reblogカテゴリ」となります。
自分の写真しかreblogしてないので、全くreblogじゃないんですが家族や友人の写真からreblogなんて事ならokですかね...
わずか3クリック程でreblog出来てしまうtombloo & 90_MetaWeblog.js すばらしいです。

今回blosxomプラグイン等に行った修正は後日公開したいと思います。

ku++ brazil++ tombloo++ blosxom++
Posted at by



2008/06/20


これすごいわ。
wildoptions=autoとcomplete=lが凄い件 - Dis Communication - 符号無し

vimperator1.2pre 2008-06-19以降で実装されてるwildoptions=autoとcomplete=lがlifechangingだったので紹介しておきますね。

何が凄いって :set complete=l が凄い。「たかがtab押さなくても良くなっただけだよね」と思うでしょうが、使ってみたら無茶苦茶便利。
vimperator使いだと、おそらくURLを開くのに :open http://... とか「<c-l>」してアドレスバーにフォーカス合わしたりしてるかと思いますが、firefox3で登場したアドレスバーの便利機能(おせっかい機能とも言われてるらしい)、「AweSomeBar」
firefox-awesomebar
:open で使えるのです。例えば :open twitter teramako とすると
vimperator-awesomebar
と出るんです。URLなんか殆ど打たなくても良くなります。あとは通常と同じくtabで選べばok。すばらしい。
これでおそらくvimperatorがブラウジング最速になったかと思います。

ところで、vimperator使ってると稀にパススルーモードが外れてしまい、「Google Readerでキーバインドが効かなくなった」とか「LivedoorReaderで上手く動かなくなった」って事になったりします。
私の場合、以下の様にキーマップして回避しています。 noremap <c-i> :js modes.passAllKeys = true<cr>
ただ、これを使ってて気づいたのですがどうやらvimperatorの補完はmapで実行する「:ex」コマンドにも反応してる臭い。これはいずれ直るかと思うけど気になる様だったらパッチを送ってみよう。 とりあえずは、mapで実行するんじゃなくてaddUserMap使ってやる事にした。 javascript <<EOM
liberator.mappings.addUserMap([liberator.modes.NORMAL], ['<C-i>'],
    'Pass through mode',
    function () { liberator.modes.passAllKeys = true }
);
EOM
Posted at by



2008/06/19


グダグダの文章ですみません。
perl-monger.orgってはてなグループじゃダメなの? - DTP+印刷営業メモ

なんとなくだが近所の人がperl-monger.orgな話をしていてさっきページを見ていたんだけど、これってはてなグループ(なんとか部みたいな)のじゃダメなのかなぁ、と思った。

...snip...

っていうかWikiに色々なコンテンツを追加していくてのはダメなんだろうかと普通に思ったんだけど。

Wikiでもログイン制にしてしまえば同じような事は出来るし、弄れば同じ風貌に出来なくもないと思う。ただWikiだと筆者に掛かる責任ぽい物だとか、評価だとか、「perl-mongers」っていう組織感だとかが表現し辛いんじゃないかなぁ。
例えば「Journal of miyagawa」がWikiだったら少し感じが変ってくる様な気がする。(私だけかもしれない)

記事を原稿用紙に書くのかフリースケッチに描くのか...そんな程度の違いなのかもしれないけど、私は今の形がCoolだと思う。

答えになってないなぁ。

追記
組織感ってのは、例えばsubtechみたいなもんかな。
Posted at by




面白いもの見つけた。(via ku's twitter post)
Tumblr, RMagick and a Photo Frame! - igvita.com

Over the course of the past year or so, I got myself into a habit of using Tumblr for storing insightful snippets and quotes from my daily endeavours on the internet. Usually, this entails saving a paragraph (too long for tweet, and not enough for del.icio.us) so I can revisit it later.

However, having Tumblr to save the quotes is nice, but I rarely (if ever) went back to read them. Pondering this dilemma, I spotted my Phillips digital photo frame, and a quick weekend project was born: Tumblr API, RMagick to render the images, and an SD card full of wisdom and quotes for easy consumption!

どっちかって言うと、書いてる理由が面白い。要は「Tumblrの引用は面白いけど一々見に行くのめんどいよね!ならって事でデジタルフォトスタンドを使っちゃおうと思った」て感じかな。

動かすにはrubyのImageMagick拡張rmagickが必要。
私はPhillips digital photo frameは持ってないけど、chumbyとか使うと良く似た事出来るのかな?
ソースはそのまま使えました。日本語はImageMagickがghostscriptを使ってるのでTrueTypeフォント直接指定すれば動きます。
--- render-tumblr.rb.orig   Sat Jun 07 19:32:46 2008
+++ render-tumblr.rb    Thu Jun 19 12:02:57 2008
@@ -62,7 +62,7 @@
        img.background_color = "transparent"
        img.pointsize = 23
        img.antialias = true
-       img.font = "Helvetica"
+       img.font = "c:\\windows\\fonts\\meiryo.ttc"
    end
    quote.write "text.png"
    
@@ -72,12 +72,12 @@
        img.background_color = "transparent"
        img.pointsize = 18
        img.antialias = true
-       img.font = "Helvetica"
+       img.font = "c:\\windows\\fonts\\meiryo.ttc"
    end
    source.write "source.png"  
 end
 
-doc = Hpricot.XML(open("http://igrigorik.tumblr.com/api/read?type=quote&num=5"))
+doc = Hpricot.XML(open("http://mattn.tumblr.com/api/read?type=quote&num=5"))
 (doc/'post').each do |post|
    text = CGI::unescapeHTML((post/'quote-text').inner_html).gsub(/&[^;]*;?/,'')
    source = CGI::unescapeHTML((post/'quote-source').inner_html).gsub(/\<.*?\>/, '').gsub(/&[^;]*;?/,'').strip

実行するとquotesディレクトリに以下の様なファイルが出来上がりました。
render-tumblr-34978042

render-tumblr-36274314

render-tumblr-37077963

render-tumblr-37111519

render-tumblr-38695135

面白いですね。あとは何とかしてこれをchumbyとかで動かせればいいのですが、いかんせんchumby持ってない!chumbyでrubyが動くかどうかも知らない。
って事で誰か後はよろしく。
Posted at by



2008/06/18


試してみた
memcpy 最適化 - id:kazuhookuのメモ置き場

バイト単位でコピーするアホなコードの方が、勝手にベクトル化される分、gcc 内蔵のヤツより最大3倍高速なんだってwww

x64じゃないけど、最近のgccはどれくらい最適化が出来てるのかを見たかったので確認してみた。
#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <time.h>
#include <sys/time.h>

void *(memcpy2)(void *__restrict__ b, const void *__restrict__ a, size_t n){
    char *s1 = b;
    const char *s2 = a;
    for(; 0<n; --n)*s1++ = *s2++;
    return b;
}

static double getsec() {
    struct timeval tv;
    double t;
    gettimeofday(&tv, NULL);
    return tv.tv_sec + (double)tv.tv_usec*1e-6;
}

int main() {
    char foo[BUFSIZ];
    char bar[BUFSIZ];
    int n = 0;
    double t;

    t = getsec();
    for(n = 0; n < 30000000; n++)
        memcpy2(foo, bar, sizeof(foo));
    printf("adhock memcpy: %f\n", getsec()-t);

    t = getsec();
    for(n = 0; n < 30000000; n++)
        memcpy(foo, bar, sizeof(foo));
    printf("normal memcpy: %f\n", getsec()-t);

}
※「-O3 -ftree-vectorize -msse2」
※gcc (GCC) 4.3.0 20080305 (alpha-testing) mingw-20080502

P4 3GHzでこんな結果
adhock memcpy: 3.484375
normal memcpy: 3.296875
オプション効いてるのかなぁ...
ちなみに「-O3」とかオプション全部抜いたら adhock memcpy: 75.281250
normal memcpy: 4.515625
こんな酷い結果になった。
「-ftree-vectorize」抜いても微妙にしか変らなかった。

シャアにはまだ遠い様だ。

ちなみに、こんな結果もある様なので今後に期待。
Posted at by




blog chart.jp

「ブログチャート」なら、今注目のブログのヒットチャートや木になるブログの影響力レベルがすぐに分かります。

もちろんあなたのブログも登録可能。自分のブログの特徴を他のブログと比較してみて下さい。

http://blogchart.jp/
404 Blog Not Found:"ブログ"とは"\xa5\xd6\xa5\xed\xa5\xb0"ですかとblogchart.jp

文字文字化け化けなmailが来たので調べてみたら...

  • Content-Type:ヘッダーもMime-Version:ヘッダーも不在
  • Subject:ヘッダーもMIME header encodingされていない
  • 全部EUC-JPのまま

もうしわけないけど、最近のSPAMだってこのあたりはきちんとしてるよ!

http://blog.livedoor.jp/dankogai/archives/51067631.html
メールの文字化けもなんだけど、届いたブログチャート貼り付け用スクリプトが <script type="text/javascript" src="http://blogchart.jp/js/blogparts.js"></script>
<script type="text/javascript"><!--
id="1784";blogurl="http://mattn.kaoriya.net";partstype="b";viewBlogparts();
// --></script>
思いっきりグローバル汚染。...orz
Posted at by



2008/06/17


元ネタ
M.C.P.C.: はてなお気に入りAPIを使ってブログパーツ作例

はてなお気に入りAPIを使ってブログパーツ?を作ってみた...

http://blog.dtpwiki.jp/dtp/2007/09/api_60ed.html
ソースは以下
jquery.hatenaFav.js
// --------------------------------------------------------------
// jquery.hatenaFav.js : hatenaお気に入り画像表示ブログパーツ
// based on: http://blog.dtpwiki.jp/dtp/2007/09/api_60ed.html
// (required: jquery.js)
// --------------------------------------------------------------

(function($){
  $.fn.hatenaFav = function(options){
    return this.each(function(index){
      var it = this, $this = $(this);
      it.opts = $.extend({}, $.fn.hatenaFav.defaults, options);
      if (it.opts.loader) $('<img/>').attr('src', it.opts.loader).appendTo($this);
      else $this.html('loading...');
      $.ajaxSetup({cache:true});
      $.getJSON('http://s.hatena.ne.jp/' + it.opts.user + '/favorites.json?callback=?', function(data) {
        $this.html('');
        $.each(data.favorites, function(index, item) {
          if (index > it.opts.max) {
            $('<a/>').attr('href', '#').click(function() {
              options.max = data.favorites.length;
              $this.hatenaFav(options);
              return false;
            }).attr('class', 'hatena-fav-more').css('font-size', '0.8em').text('more...').appendTo($this);
            return false;
          }
          $('<img/>').attr('src', 'http://www.hatena.ne.jp/users/' + item.name.slice(0, 2) + '/' + item.name + '/profile_s.gif')
          .css({
            width: it.opts.size,
            height: it.opts.size,
            border: it.opts.border,
            title: 'id:' + item.name,
            alt: 'id:' + item.name
          }).wrap('<a/>')
            .parent().attr('href', 'http://d.hatena.ne.jp/' + item.name + '/').attr('target', it.opts.target).appendTo($this);
        });
      });
    });
  };
  $.fn.hatenaFav.defaults = {
    user:   'jkondo',
    size:   '16px',
    border: '0px',
    target: '_blank',
    max:    10,
    //loader: 'http://mattn.kaoriya.net/images/ajax-loader.gif'
  };
})(jQuery);
使い方はこんな感じ
<style type="text/css"><!--
.hatena-fav-container {
    font-family: meiryo, osaka;
    font-weight: bold;
    text-align: left;
    width: 200px;
    border: 1px solid gray;
}
.hatena-fav-container a, .hatena-fav-container a:visited {
    color: blue;
}
.hatena-fav-title {
    background-color: #ddd;
    text-align: center;
}
.hatena-fav-icons img {
    margin: 1px;
}
--></style>
<script type="text/javascript" src="jquery-latest.js"></script>
<script type="text/javascript" charset="utf-8" src="jquery.hatenaFav.js"></script>
<script type="text/javascript"><!--
$(function() {
    $('.hatena-fav-icons').hatenaFav({ user: 'mattn', size: 16 });
});
--></script>
<div class="hatena-fav-container">
    <div class="hatena-fav-title">
        <a href="http://mattn.kaoriya.net/">はてなお気に入り</a>
    </div>
    <div class="hatena-fav-icons"></div>
</div>
hatenaFavメソッドの引数に渡すuserとして、はてなのユーザIDを入れるとそのユーザのお気に入りユーザが表示されます。
実行結果は↓

続きを読む...

Posted at by



2008/06/16


はてなダイアリーモバイルでも、はてなスターが付けられる様になった様です。
モバイル版はてなダイアリーではてなスターに対応しました - はてなダイアリー日記

本日、モバイル版はてなダイアリーではてなスターを見たり付けたりできるようになりました。

これまでモバイル版ではてなスターに対応していたサービスははてなハイクだけでしたが、はてなダイアリーでも同様に☆を閲覧したり追加したりできるようになりました。

http://d.hatena.ne.jp/hatenadiary/20080613/1213342619
モバイル端末で試した所、確かにスターが付けられます。「もしや...」と思って自分のサイトでもやってみたら...
できちゃいました。
スターを付けるURLは
http://s.hatena.ne.jp/star.add?sid={SID}&rks={RKS}&uri={記事URL}&location={元の場所}
の様なのですがモバイル端末ではログインし直す事もある為、リンク先には「ログイン」や「かんたんログイン」へのリンクが張られています。
これがあれば外部ドメインでもスターが付けられるって事ですね。
先日、blosxomでモバイル様にはてなスターを表示する為のプラグインを書いたのですが、その際に修正したフレーバに以下のリンクも付け足しました。
<a href="http://s.hatena.ne.jp/star.add?sid=&rks=&uri=$url$path/$fn.htm&location=$url$path/$fn.htm"><img src="http://s.hatena.com/images/add_bl.gif" border="0"/></a>$hatenastar_mobile::stars<br />
おそらく一度かんたんログイン等でログインすれば、はてなドメイン上のクッキーは少しの間は使える筈なので、二三個付ける場合でも再度ログインする事は無いと思います。
Posted at by



2008/06/13


あんまり確認してないですが...
tomblooハックス - share on WordPressのためのMetaWeblog API poster « ku

MetaWeblog APIはWordPressだけでなくMovableTypeでもサポートされているのでMovableTypeでも使うことができる。ただユーザ名とAPI用パスワードの設定を簡単にはできないので、ファイルに直接書くことになる。

--- 90_MetaWeblog.js.orig   Fri Jun 13 10:45:53 2008
+++ 90_MetaWeblog.js    Fri Jun 13 10:48:48 2008
@@ -427,11 +427,23 @@
        var endpointUri = createURI(this.endpoint);
        var hostname = endpointUri.prePath;
        var formSubmitURL = endpointUri.prePath;
-       var httprealm = null;
-       var logins = lm.findLogins({}, hostname, formSubmitURL, httprealm);
+       var logins = lm.findLogins({}, hostname, formSubmitURL, null);
        var loginInfo = logins.shift();
        if ( ! loginInfo ) {
-           throw "No login infomation found. Please make Firefox remeber your login information at " + this.endpointUri.replace(/\bxmlrpc\b/, 'wp-login');
+           var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
+           var [user, pass] = [{ value : null }, { value : null }];
+           var ret = ps.promptUsernameAndPassword(
+               window, formSubmitURL, "tombloo metaWeblog poster", user, pass, null, {});
+           if(ret){
+               var nsLoginInfo = new Components.Constructor(
+                   "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
+               loginInfo = new nsLoginInfo(
+                   formSubmitURL, formSubmitURL, null, user.value, pass.value, '', '');
+               passwordManager.addLogin(loginInfo);
+           }
+           if ( ! loginInfo ) {
+               throw "No login infomation found. Please make Firefox remeber your login information at " + this.endpointUri.replace(/\bxmlrpc\b/, 'wp-login');
+           }
        }
 
        var mw = new MetaWeblogAPI(loginInfo.username, loginInfo.password, this.endpoint);
これで外部ファイルにパスワード保存しなくても良くなったりするかなぁ...

時間があまり無いので今度検証する。
Posted at by



2008/06/12


JavaScriptコンソールからパスワードぶっこ抜き - hogehoge

JavaScriptコンソールの上部の入力欄はChrome特権のコードも実行できるんで、XPConnectからゴニョゴニョっとやると平文パスワードを覗くことができちゃうんだね。

PCから離れるときは画面ロックをしないと危険ということかな。

ホントだ怖い。
具体的なコードは

続きを読む...

Posted at by



2008/06/11


これだけメソッドがあれば大概の事は出来る。
The Console Module

The Console module provides a simple console interface, which provides cursor-addressable text output, plus support for keyboard and mouse input.

The Console module is currently only available for Windows 95, 98, NT, and 2000. It probably works under Windows XP, but it hasn’t been tested on that platform.

試しに作ってみた。以下ソース

Oppai.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys, time, codecs
import Console

class Oppai:
  def __init__(self):
    self.nest = 1

  def __getattr__(self, name):
    if name == 'Oppai':
      self.nest += 1
      return self
    else:
      return self.__dict__[name]

  def __call__(self):
    aa = [u"""
    _  ∩
  ( ゜∀゜)彡 おっぱい!おっぱい!
  (  ⊂彡
   |   | 
   し ⌒J
""", u"""
    _  ∩
  ( ゜∀゜)彡 おっぱい!
  (    | 
   |   | 
   し ⌒J
""", u"""
    _  
  ( ゜∀゜)  おっぱい!
  (  ⊂彡
   |   | 
   し ⌒J
"""]

    c = Console.getconsole()
    c.title("Oppai")
    for n in range(self.nest * 4):
      c.page()
      oppai = aa[n % 4 in (0, 2) and (n % 4)/2+1 or 0]
      l = 0
      for line in oppai.split("\n"):
        c.text(0, l, line.encode("mbcs"))
        l += 1
      time.sleep((n % 4) in (0, 2) and 0.5 or 0.15)
Oppai = Oppai()

if __name__ == '__main__':
  Oppai.Oppai.Oppai.Oppai()
使い方は
import Oppai

Oppai.Oppai.Oppai.Oppai()
こんな感じ。動かすと属性参照した分だけ、おっぱいアニメーションが流れます。
おっぱいそん
簡単ですね!
Posted at by



2008/06/06


このサーバではJSON::Syck::Load(YAML::Syck::LoadJSON)が動かなかった(おそらくXSコンパイルされてない)のでJSON::PPを使ってますが、使える人はJSON::Syck::Loadを使うという事で
hatenastar_mobile
# Blosxom Plugin: hatenastar mobile
# Author(s): mattn
# Version: Fri, 06 Jun 2008

package hatenastar_mobile;

use strict;
use warnings;
use vars qw($stars);
use LWP::UserAgent;
use URI::Escape;
use HTTP::Request;
use JSON::PP;

$stars = '';

my $permalink_flavour = 'htm';
my @mobile_ua = qw(UP\.Browser KDDI PDXGW DoCoMo J-PHONE L-mode Vodafone SoftBank);

sub start {
    return 1 if map { $ENV{'HTTP_USER_AGENT'} =~ /$_/ } @mobile_ua;
}

sub story {
    my($pkg, $path, $fn, $story_ref, $title_ref, $body_ref) = @_;
    return 0 if $ENV{'PATH_INFO'} !~ /\.$permalink_flavour$/;

    eval {
        my $uri = 'http://s.hatena.ne.jp/entries.json?uri=' . URI::Escape::uri_escape("$blosxom::url$path/$fn.$permalink_flavour");
        my $ua = LWP::UserAgent->new;
        my $req = HTTP::Request->new(GET => $uri);
        my $res = $ua->request($req);
        $res->is_success or return 0;
        my $json = decode_json( $res->content );
        my @sts = @{$json->{entries}->[0]->{stars}};
        for my $st (@sts) {
            $stars .= '<img src="http://s.hatena.ne.jp/images/star.gif" title="' . $st->{name} . '" />'
        }
    };
    1;
}
パーマリンクとなるflavour名をpermalink_flavourに指定し、flavourに"$hatenastar_mobile::stars"を入れると表示されます。
Posted at by



2008/06/02


GoogleからjQueryやdojoがロード出来るAjaxライブラリが公開されました。

The AJAX Libraries API is a content distribution network and loading architecture for the most popular open source JavaScript libraries. By using the Google AJAX API Loader's google.load() method, your application has high speed, globaly available access to a growing list of the most popular JavaScript open source libraries including:

http://code.google.com/apis/ajaxlibs/
でも、リンク先に載ってるコード、良く見たら動かないよ><
googl ajax library typo
正しくは <script src="http://www.google.com/jsapi"></script>
<script>
  var renderResults = function(results) {
      $.each(results, function(index, item) {
        $('#result')
          .append('<div id="result' + index + '"></div>');
        $('#result' + index)
          .append('<a href="' + item.url + '">' + item.title + '</a>')
          .append('<div>' + item.content + '</div>')
          .append('<span>' + item.visibleUrl + '</span>')
          .css('padding', '0.5em');
        $('#result' + index + ' div')
          .css('border', '1px dotted gray')
          .css('width', '500px')
          .css('padding', '1em')
          .append('<span>');
        $('#result' + index + ' span')
          .css('color', 'green')
          .css('margin-left', '1em');
      });
  }

  // Load jQuery
  google.load("jquery", "1");

  // on page load complete, fire off a jQuery json-p query
  // against Google web search
  google.setOnLoadCallback(function() {
    $.getJSON("http://ajax.googleapis.com/ajax/services/search/web?q=google&v=1.0&callback=?",

      // on search completion, process the results
      function (data) {
        if (data.responseData.results &&
            data.responseData.results.length>0) {
          renderResults(data.responseData.results);
        }
      });
    });

</script>
<div id="result"></div>
こんな感じになります。あと、visualization等の場合はgoogle.loadの第3引数が有効で"callback"の記述が出来たのですが、jQueryやdojo等サードパーティな物では動かなかった。残念。
ちなにみ上のコードを動かすと、こんな感じになります。

続きを読む...

Posted at by




about: rel-bookmark戦争
『オレ、rel-bookmarkも嫌いなんだわー』とブツクサ言うsnj14 - otsune tumblr まとめサイト 画像保管庫Q
『オレ、rel-bookmarkも嫌いなんだわー』とブツクサ言うsnj14 - otsune tumblr まとめサイト 画像保管庫Q(追記)
3:14 - 『オレ、rel-bookmarkも嫌いなんだわー』とブツクサ言うsnj14
さて、私も少し考えてみた。
Basic HTML data types

Refers to a bookmark. A bookmark is a link to a key entry point within an extended document. The title attribute may be used, for example, to label the bookmark. Note that several bookmarks may be defined in each document.

http://www.w3.org/TR/REC-html40/types.html#h-6.12
mattn的に訳すと

Bookmarkについて。 bookmarkは広範囲に渡るドキュメント内の唯一のキーとなるエントリポイントへのリンクとなります。ブックマークをラベリングする際にはタイトル属性が使用され、ドキュメント内には幾らかのブックマークが定義され得る事に気をつけるべきでしょう。

そしてkuさんが言及している部分
Basic HTML data types

Authors may wish to define additional link types not described in this specification. If they do so, they should use a profile to cite the conventions used to define the link types. Please see the profile attribute of the HEAD element for more details.

http://www.w3.org/TR/REC-html40/types.html#h-6.12
訳すと
作者は本仕様では記述し得ないリンク型を別途定義したいと思うかもしれません。 であれば、リンク型を定義するのに一般的に用いられるprofileを使用すべきでしょう。 その他の詳細に関してはHEAD要素のprofile属性を参照して下さい。
そしてrel-bookmarkについて、microformats wikiでは
rel-design-pattern - Microformats

By convention (citation needed), this entry point also captures the notion of a "permalink".

http://microformats.org/wiki/rel-bookmark
訳すと

一般的には(言及が必要)、このエントリポイントとはpermalinkの概念で表現される物。

意訳過ぎたらごめんなさい
と言う事で
rel-bookmarkとは、それが含まれるドキュメントの内1点を指すべき物であり、rel-bookmarkがドキュメント内で実際のリンク先を指すべき、とまでは決めてない。あくまでpermalink扱い。
が私の考え。が上から読み取った私の解釈。
例えば末っ子の小学生が「姉ちゃん」と言えば「姉」を指し、60代サラリーマンが「姉ちゃん」と言えば「キャバクラの姉ちゃん」になる訳で...
つまりはそのテリトリ内で一意に現すことが出来る何か...がrel-bookmarkなのではないかと思った。
Posted at by




これ、すごいっす。
The Memcache API - Google App Engine - Google Code

High performance scalable web applications often use a distributed in-memory data cache in front of or in place of robust persistent storage for some tasks. Google App Engine includes a memory cache service for this purpose.

http://code.google.com/appengine/docs/memcache/
何が凄いって仕様が凄い。
通常のset/getの他、dictとして格納出来るset_multi/get_multiもある。Tagtterの様にタグ毎にキャッシュしたい場合には持って来いなAPIです。
どうやらdangaをベースにしている様で、LiveJournalsourceforge、さらにはWikiPediaでも使われているライブラリらしいです。

さて、今日Tagtterにこのmemcache APIを使ってみました。
使い方としてはページをそのまま!Tagtterの場合、動的なコンテンツと言えばタグ、Vote、ユーザですが、これらが変更されない限りトップページタグページは変る事は無いのです。他の情報はjavascript(jQuery)で動的に取得しており、サーバには格納されていません。
どんなに大胆な使い方かと言うと
class TopPage(webapp.RequestHandler):
  def get(self):
    cache = memcache.get('tagtter_top_page')
    if cache:
      # キャッシュがあるならそのまま出力
      self.response.out.write(cache)
      return

    ・・・ 巨大なデータ処理 ・・・

    path = os.path.join(os.path.dirname(__file__), get_template(self))
    cache = template.render(path, template_values)
    # キャッシュに溜め込む
    memcache.set('tagtter_top_page', cache, cache_time)
    self.response.out.write(cache)

class AddTagsAPI(webapp.RequestHandler):
  def get(self, username):

    ・・・ タグの追加処理 ・・・

    # 結果を出力
    dump_json(self, { 'status': 'ok', 'message': 'done' })
    # キャッシュをクリアしてやる
    memcache.flush_all()
上記の様に、キャッシュがあればそのまま出力し、タグが追加されればキャッシュをクリアするという方法。
おかげでトップページタグページの表示速度が激変しました。
但し、ユーザ一覧ページ等は、pageというパラメータにより出力する内容が異なる為、このままでは対応出来ません。やっちゃうとどんどん同じページが表示されてしまいます。
今のところ、トップページとタグページにしか取り入れていませんが今後少しずつ取り入れて行きたいと思います。

ところで、公開されたAPIのもう一つImages APIも使ってみました。
使い方は簡単。
from google.appengine.api import images

...

image = images.Image(data) # input data
image.resize(100, 100)
image.rotate(90)
data = image.execute_transforms(output_encoding=images.JPEG)
現状、合成したり90度単位でない回転等は出来ませんが、コレ使えばなんでもありちゃんかいっと思った。いずれやる。

動いている物は以下
Image API Test
Posted at by




以前、MOONGIFTさん所で見付けた「Growl for Windows」を試してみた。
Growl for Windows
Features currently supported:
  • Fully compatible with original Growl for Mac
  • API for local and network applications
  • Notification forwarding
  • Experimental support for WebKit-based display styles
http://www.tripthevortex.com/growl/
まず、Growl本家からtarボールを持ってくる(SDKはdmgなので...)
Growl Developer Downloads

Information regarding the SVN repository and other areas of interest are addressed in the developer documentation. Growl requires Xcode 2.3 to compile, which is available for free from Apple.

Growl 1.1 Source
http://growl.info/downloads_developers.php
次に、"/Growl-1.1-source/Bindings/python"に移動し以下のパッチを当てる。
--- Growl.py.orig   Wed Jan 24 05:36:17 2007
+++ Growl.py    Fri May 30 11:22:18 2008
@@ -72,7 +72,7 @@
         if userInfo.has_key(GROWL_NOTIFICATION_STICKY):
             sticky = userInfo[GROWL_NOTIFICATION_STICKY]
         else:
-            priority = False
+            sticky = False
         data = self.encodeNotify(userInfo[GROWL_APP_NAME],
                                  userInfo[GROWL_NOTIFICATION_NAME],
                                  userInfo[GROWL_NOTIFICATION_TITLE],
おそらく開発者のtypoかと思う。
そして"python setup.py install"する。
気持ち悪い人は"python setup.py bdist_wininst"する

後はサンプルコード
Objective-Cのモジュールをインストールしていないので、netgrowl経由でしか送信出来ないです。
#!/usr/bin/python
import Growl
g = Growl.GrowlNotifier(
    applicationName='GrowlExample',
    notifications=["PyGrowl"],
    defaultNotifications=[0],
    hostname="localhost",
    password="My Really Secure Password")
g.register()
g.notify(
    noteType="PyGrowl",
    title='wanings',
    description='toilet spilled some xxx!',
    sticky=False)
まずGrowl for WindowsをインストールするとシステムトレイにGrowlというアイコンができ、「Settings」を選ぶと設定画面が表示されます。次にNetworkタブを開き
  • Listen for incoming notifications
  • Allow remote application registration
にチェックを入れます。そして"Server Password"に上記ソースにあるpassword、"My Really Secure Password"を入れます。(実際はソースも設定も変更して使用しましょう)
growl settings 1

後は上記サンプルプログラムを起動すると以下の様にGrowlが表示されます。
growl notify
なお、設定の"Applications"にて挙動を変えられますで色々試してみてもいいかもしれません。
growl settings 1
また、上記ではpythonの例を示しましたがperlで"Net::Growl"を使い use strict;
use warnings;
use Net::Growl;
register(host => 'localhost',
         application=>"growl.pl",
         password=>'My Really Secure Password', );
notify(
       application=>"growl.pl",
       title=>'warning',
       description=>'toilet spilled some xxx more!',
       priority=>2,
       sticky=>False,
);
この様なサンプルでもGrowlは表示されます。
ちなみ、Growl for Windowsのせいなのか幾らかの日本語で文字化けが発生しました。
registerしたアプリケーションを消すには %USERPFOFILE%¥Local Settings¥Application Data¥Vortex¥Vortex.Growl.WindowsClient のディレクトリを一斉に消すと設定が消えてくれます。
個別で消す方法は見つかりませんでした。

色々な言語でBindingされている様なので皆さん試してみてはどうでしょうか
Posted at by



2008/05/30


追記
最新のWeb::Scraperでは"Web::Scraper::user_agent"でユーザエージェントが変更出来る様になっています。

昨日書いたエントリ「WWW::MechanizeとXPathでtwitterのfriendsを全部取ってみる」のはてなブックマークでotsune氏から
「Web::Scraper/eg/twitter-friends.pl」
というコメントを貰いました。
ただ、この「Web::Scraper/eg/twitter-friends.pl」は、アンカーのrel属性でcontactとなっている物を並べており、私のようなfollowerが少ない人ならばいいのですが、多い人だと100人超えてしまいます。
結果、100人以上取得出来ない事になります。

以上、おしまい!...というのもアレなので...

無理やり感たっぷりですが、Web::Scraperを使ってみました。
ログインしないと、friendsが辿れないのでそこはWWW::Mechanizeに任せ、LWP::UserAgentを継承しているのを利用して、Web::Scraperの__uaを置き換えました。(ウホ!無理やり)
何か他に、キレイなやり方ないですかねぇ...

#!/usr/local/bin/perl

use warnings;
use strict;
use WWW::Mechanize;
use Web::Scraper;
use YAML;

my $username = 'your_username';
my $password = 'your_password';

my $m = WWW::Mechanize->new(timeout => 10);
$m->add_header('Accept-Encoding', 'identity');
$m->get('http://twitter.com/login');
$m->submit_form(
    form_number => 1,
    fields    => {
        username_or_email  => $username,
        password           => $password,
    },
    button    => 'commit',
);

undef &Web::Scraper::__ua;
*Web::Scraper::__ua = sub {
    $m;
};
my $twitter = scraper {
    process 'tr.vcard',
        'friends[]' => scraper {
            process 'td strong a', nick => 'TEXT';
            process 'td.thumb img', image => '@src';
            process 'td.thumb img', name => '@alt';
            process 'td strong a', description => '@title';
            process 'td.thumb a', url => '@href';
        };
    result 'friends';
};

my $num_page = 1;
while (1) {
    my $uri  = URI->new("http://twitter.com/friends/?page=$num_page");
    my $friends = $twitter->scrape($uri);
    %$friends or last;
    $num_page++;
    warn Dump $friends;
}
Posted at by




全然難しくない話
filtered feed for coderepos
CodeReposフィードにはcategoryにshare配下のパスが埋め込まれている様なので、これをマッチングに使ってるだけです。
"Search Text"には"lang/javascript"とか"lang/python"とか入力します。
これで"lang/javascript"以下だけ欲しい人とか、"lang/perl"以下だけ欲しい人とか、"poem"だけ欲しい人とかがウォッチしやすくなるのかな? ちなみに正規表現でやるまでもなかいかな...と思って単純マッチングにしてあります。正規表現がお好み人はcloneして変更して下さい。
Pipes: Filtered Feed for CodeRepos
Posted at by



2008/05/28


「はてなスター」の置かれている位置によっては思いっきり誤爆してしまうhatenaStar.jsですが、皆さんのご不満を軽減すべく誤爆確認機能をつけました。これまで :hatenastar 1 としていた場面で :hatenastar 1? とすると、つけようとしている「はてなスター」がブリンクします。
点灯している「はてなスター」を見て、「あぁ、あそこに付けるんだな」と思って頂ければと思います。
あとは安心して":↑<bs>"でコマンドヒストリを出してバックスペースで"?"を消し :hatenastar 1 と実行して下さい。
hatenaStar.js
Posted at by



2008/05/27


追記
検索順位が変ってしまったようです。

おそらくこれ、Technoratiがやりたかった事のはず。

- Yahoo! Search Results
http://www.yr-bcn.es/demos/microsearch/

microsearch [Wiki]

Using microformats

We support two kinds of microformats: hCard and hCalendar. hCard is used to add personal information to your webpage, hCalendar is used to add events. You can use the hCard creator and the hCalendar creator to create the code that you will need to insert into your webpage.

http://www.yr-bcn.es/dokuwiki/doku.php?id=microsearch#using_microformats
1段目の引用部にあるリンクで、「mattn」と検索してみて下さい。おそらく10秒から20秒程かかりますが検索結果が現れるかと思います。他の検索サービスと違う事に気付いた方、すばらしい。
そう、microformatsを使ってるのです。このサイト「Big Sky」に埋め込まれたmicroformatsの内、「hCard」と呼ばれる物(ここでは「Osaka, Japan」)を使用して地図上の大阪付近に「mattn」とプロットしているのです。
microsearch
2段目の引用部

Using microformats

hCardとhCalendarという、2種類のmicroformatsをサポートしています: hCardとは、貴方のウェブページに個人情報を加えるのに用いられ、hCalendarはイベントを加えるのに使用されます。 これらをウェブページに追加するには挿入するにはhCard CreatorFOAF-a-matic -- Describe yourself in RDFを使う事でコードを生成出来ます。

から、Yahooもmicroformatsに何かしらの期待を持っている事が伺えます。いずれ検索メタデータとして扱っていこうという事でしょうか。
リンク先には、今後GRDDLへの対応も予定されているので、ちょっとYahooが面白い事をやってくれそうな気がしてきました。
Posted at by



2008/05/26


以前、「Big Sky :: 意外と知られていないvimのtips(calendar.vimの日記機能をGoogle Calendarと同期させる)」という記事を書いたのですが、どうやらこれを完成品にまで作り上げてくれた人が居るようです。
見たところ、私のソースを流用されてはいなさそうですが仕組みは似通っています。
diaryvgc - Google Code

Synchronism Python Script between VIM Calendar diary and Google Calendar .

DiaryVGC == Diary Vim Google Calendar

http://code.google.com/p/diaryvgc/
私の場合はファイルの存在や更新時刻で動作しますが、こちらはログファイルを使った全て。また私の様に「--- Google Calendar ---」という文字列で対象ファイルを絞ったり、イベント期間を設定出来たりはしません。
私の場合は「--- Google Calendar ---」という文言を見付けて、それだけを対象ファイルとしていますが、こちらはログファイルを使った全て。
ただ、中国の方のようでコード内にcp936やらgb2312といったエンコーディング名がちらほら。
適当にpatch当てて、動くまでは確認しました。
Index: diaryvgc.py
===================================================================
--- diaryvgc.py (revision 4)
+++ diaryvgc.py (working copy)
@@ -257,15 +257,15 @@
                             if sys.platform[:3] == 'win':
                                 try:
                                     diaryinfo = open(tfile).read()
-                                    info_done = unicode(diaryinfo,'cp936')
+                                    info_done = unicode(diaryinfo,'mbcs')
                                     #print "=============win===UTF8=============="+tfile
                                 except Exception,ex:
-                                    diaryinfo = codecs.open(tfile,'r','gb2312').read()
+                                    diaryinfo = codecs.open(tfile,'r','mbcs').read()
                                     info_done = diaryinfo.encode("utf-8")
                                     #print "============win====GB2312=============="+tfile
                             else:
                                 try:
-                                    diaryinfo = codecs.open(tfile,'r','gb2312').read()
+                                    diaryinfo = codecs.open(tfile,'r','mbcs').read()
                                     info_done = diaryinfo.encode("utf-8")
                                     #print "=============notwin===GB2312=============="+tfile
                                 except Exception,ex:

使い方は
diaryvgc.py --user your-gmail-id --pw your-pass-word --dir /path/to/vim/diary で更新です。
追記
よくみたら場所で「China」がデフォルトになってますね。
使う人は適当に「Japan」とかに書き換えるのが良いかと...

自分が作った物が何かしらの形で流用されるってのは、うれしいですね。
Posted at by




先日、Open Tech Pressの連載コラム「Vimマスターへの道」の中に「Vim/gVimをユーザフレンドリなエディタに変えるCream」という記事を見つけました。
Creamは以前から知っており、Creamの作者から「calendar.vim」の取り込み可否を尋ねられた事があります。現在は少し古いバージョンがCreamにバンドルされています。
上記本文中に、「...例えばカレンダ機能を追加するCalendarプラグインが使えないと自分は生きていけないと語っていた者と何回か出会ったことがある。」の文言を見つけた時には正直嬉しかったです。
ただ、Calendarに付属している日記機能は現状メモ書き用途にしか使えず、現状のCalendarは単なるカレンダでしかありません。何かとリンクする訳でもなく、このデータを使ってvimと連携する事もありません。しいて言うならば日記ファイルがあるとcalendar.vimで日付が点灯するくらいです。

vim_calendar1

今日は、あまりvimと関係しないかも知れませんが、このcalendar.vimとGoogle Calendarのスケジュールを同期するプログラムをご紹介したいと思います。
Google Calendarは、Googleが無償で提供するカレンダアプリケーションです。
携帯からも使用でき、ホビーユーザからヘビーユーザまで満足出来るアプリケーションです。
Google CalendarにはAPIが公開されており、Googleのアカウントをお持ちで開発経験のある方ならば自前のアプリケーションからGoogle Calendarに対してアクセスする事が出来ます。
今回はPythonを使用しますのでgdata-python-clientから「gdata.py-1.0.5.tar.gz」をダウンロードしました。
calendar.vimでは、ホームディレクトリに置かれた「diary」というディレクトリ(変更可能)から年月日をそれぞれディレクトリに分割した構成で拡張子「.cal」のファイルを管理します。
この「.cal」ファイルにGoogle Calendarのスケジュールを取得し、書き込みたいと思います。
gdata-python-clientでは、Googleアプリケーションのデータストレージ(gdata)に対する認証からカレンダへのアクセスまで行ってくれます。
認証は以下の手順で行います。
cal_client = gdata.calendar.service.CalendarService()
cal_client.email = myemail
cal_client.password = mypasswd
cal_client.source = "Google-Calendar_Python_Sample-1.0"
cal_client.ProgrammaticLogin()
これで、GData認証サービスへのトークン取得、認証までが完了しています。
そして以下がカレンダへのアクセスです。今回は当月から3ヶ月分のスケジュールを取得しています。
today = datetime.date.today()
query = gdata.calendar.service.CalendarEventQuery("default", "private", "full", "")
query.start_min = str(today + relativedelta(day=1))
query.start_max = str(today + relativedelta(months=+3,day=1,days=-1))
feed = cal_client.CalendarQuery(query)
for i, an_event in enumerate(feed.entry):
  for a_when in an_event.when:
    print an_event.title.text, a_when.start_time, a_when.end_time
今回は、横着して月始めと月末の計算に便利な「dateutil」を使用しています。
これで、当月の一日から、2ヵ月後の月末までのイベントオブジェクト(feed)が取得出来ましたので、これをcalendar.vimの日記ファイルとリンクします。
def update_schedule(path, an_event, a_when):
    dir = os.path.dirname(path)
    if not os.path.isdir(dir):
        os.makedirs(dir)
    lines = []
    resep = re.compile("^" + schedule_sep + "$")
    has_data = False
    if os.path.isfile(path):
        fp = open(path)
        for line in fp.readlines():
            if resep.search(line):
                has_data = True
            lines.append(line)
    if not has_data:
        lines.append("%s\n" % schedule_sep)
    lines.append("%s - %s\n" % (
        a_when.start_time.replace("-", "/"),
        a_when.end_time.replace("-", "/")))
    lines.append("\t%s\n" % an_event.title.text.encode("utf-8", "replace"))
    print "updating schedule of '%s'" % path
    fp = open(path, "w")
    fp.writelines(lines)
    fp.close()
schedule_sepは「--- Google Calendar ---」というセパレータを示し、「.cal」ファイルの下部からデータが開始します。
データは、「開始日付 - 終了日付」の次の行にイベント名をインデントした以下の形式にしています。 --- Google Calendar ---
2007/09/25 - 2007/09/26
    休暇予定
上記update_scheduleでは、セパレータ以降にGoogle Calendarから取得した開始日付、終了日付、タイトルを書き込んでいます。

ただ、このアプリケーションを何度も実行すると同じデータが繰り返し追加される事になりますので、update_scheduleと同様の処理でセパレータ以降をクリアするremove_scheduleを実装し # clean exising schedules
for i, an_event in enumerate(feed.entry):
  for a_when in an_event.when:
    remove_schedule(datetime2diary(a_when.start_time))

# append newer schedules
for i, an_event in enumerate(feed.entry):
  for a_when in an_event.when:
    update_schedule(datetime2diary(a_when.start_time), an_event, a_when)
とする事で毎回データを作り直しています。
これをあとは、プログラム引数からユーザIDとパスワードを貰い、動作するアプリケーションとして仕上げれば以下のようなCalendarの画面が出来上がります。

vim_calendar2

一応、Windows版のPython2.5で検証してますが、おそらく2.4でも動作するかと思います。
このアプリケーションには、まだ更新する機能がありませんが、ぼちぼち暇を見つけて更新して行こうかと思います。

ダウンロード:

Posted at by



2008/05/23


I wrote a plugin which moved the focus along the input fields.
When the web page has multiple <input> or <textarea> fields,
the focus walks the input fields by pressing A-i (or M-i).

http://www.pqrs.org/tekezo/tmp/vimperator/walk-input.js

It seems there was no similar function in the core code, would you merge this?

Regards,
Takayama Fumihiko.
http://www.mozdev.org/pipermail/vimperator/2008-May/001497.html
便利。<M-i>で順に入力フィールドへフォーカスが飛ぶ。
Dotan Cohenから「TABとどう違うの?」って返信への返信通り、"gi"だと届かないフィールドへフォーカスを飛ばすのはTABの押しすぎで指がつる。
使わせて頂く事にしよう。

tekezo++
Posted at by




Perlハブサイトコアメンバーに訊きたい質問 - [51]注目!パソコンの使い方-はてなダイアリー

Perl日本人ユーザーハブサイト(Perl-users.jp - 日本のPerlユーザのためのハブサイト) が立ち上がり、他言語でもユーザーハブサイトができ、挙句の果てにはRailユーザーハブサイトまでできた模様。

これまでいくつものPerlユーザーハブサイトをコピペしてPerlを使ってきた立場から今回のサイトが末永く続くように運営に関わろうとする篤い志の方へその志の大きさを客観的に示してもらえるよう質問を用意しました。プロフィールページがあったら少しはサイトも長く続くかと考えています。

質問の順番も重要でしょうから、追加・改変はご自由に、コピペして100の質問のようにご利用ください。

http://d.hatena.ne.jp/Akira51/20080523/1211500311

  • rubyは好きですか?
  • pythonは好きですか?
  • perl命と言いながらjava-jaに参加してませんか?
  • 織田祐二は好きですか?
  • 今日のパンツは何色ですか?
Posted at by



2008/05/19


lomoさんが言ってた
caramel*vanilla » Twitterユーザーにタグ付けするWebサービスが欲しい
共通点のあるユーザーを効率的に探したい
... 誰かなんとかしてくれませんか?
http://caramel-tea.com/2008/05/twitter_tagging/
を作ってみた。まだ途中でlomoさんが要望してる機能の内
caramel*vanilla » Twitterユーザーにタグ付けするWebサービスが欲しい
  • 誰でも自由にユーザーページを登録できる
  • 誰でも自由にユーザーにタグをつけられる
  • Bookmarkletかなにかで今見ているTwitterのユーザーページを登録する仕組みを作る
  • 既存のタグに対して+-して数をカウントする
  • ユーザーごとの個別ページにはタグクラウドを表示する
  • ユーザーごとの個別ページにはアイコンと最新発言を表示する
  • ユーザーごとの個別ページからFollowできるようにする
  • GreasemonkeyなどでTwitterのユーザーページにもタグクラウドを表示させる
  • ユーザーごとの個別ページにはそのユーザーにタグをつけたユーザーのアイコンを表示させる
  • 付けられたタグが似たユーザーのアイコンを表示させる
  • タグごとのランキングページを生成する
  • ユーザーページとランキングページのRSSを配信する
http://caramel-tea.com/2008/05/twitter_tagging/
までしか出来てない。
まぁとりあえず動いてるんで遊んでやって下さい。
なお、ログインするためにはtwitterでログインしている必要があります。お気を付けて。
「add User」でユーザを追加して、ユーザページにて「add Tags」でタグが追加出来ます。ログイン状態であればユーザページ上のタグにマウスを当てると「+-」が表示されます。
Tagtter - Find twitter's fun on tag cloud!
まだbetaですし、データ構造も変えるかもしれないので、今後変更削除してしまうかもしません。ご了承下さい。

Enjoy!
Posted at by



2008/05/13


これまで本サイトの画像の扱いはblosxomのimagerefという、自作ながらお粗末なプラグインを使っていました。
私としては、記事本文に記述される画像へのパスはサイトのURLとは切り離して考えるべきと思っているので、以下のプラグインを使っています。

imageref
# Bloxsom Plugin:ImageRef
# Author: Yasuhiro Matsumoto
# Version: 0.1

package imageref;
use strict;
my $images_url = "http://mattn.kaoriya.net/images";
sub start {1}
sub story {
    my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
    $$body_ref =~ s@<!--\s+imageref\s+thumb:([^:\. ]*)(\.\S+)?\s+-->@<a href="$images_url/$1$2" rel="shadowbox"><img src="$images_url/$1-thumb$2" alt="$1" border="0" /></a>@ig;
    $$body_ref =~ s@<!--\s+imageref\s+thumb:([^:]+?):([^:\. ]*)(\.\S+)?\s+-->@<a href="$images_url/$2$3" rel="shadowbox"><img src="$images_url/$2-thumb$3" alt="$1" border="0" /></a>@ig;
    $$body_ref =~ s@<!--\s+imageref\s+([^:\. ]*)(\.\S+)?\s+-->@<img src="$images_url/$1$2" alt="$1" border="0" />@ig;
    $$body_ref =~ s@<!--\s+imageref\s+([^:]+?):([^:\. ]*)(\.\S+)?\s+-->@<img src="$images_url/$2$3" alt="$1" border="0" />@ig;
    1;
}
1;
※今回の修正を含んだソースです。
※少しvimの色付け変ですね...
これを使うと、記事本文に
以下の画像を見てください。<br />
<!-- imageref パンダ:panda.png -->
と書くことで
<img src="http://mattn.kaoriya.net/images/panda.png" alt="パンダ" border="0"/>
というHTMLが出力されます。例えばサイトの引越しや画像格納パスの変更で全ての記事に手直しをする必要が無くなる訳です。

また、このプラグインでは 以下の画像を見てください。<br />
<!-- imageref thumb:パンダ:panda.png -->
と書く事で <a href="http://mattn.kaoriya.net/images/panda.png"><img src="http://mattn.kaoriya.net/images/panda-thumb.png" alt="パンダ" border="0"/></a>
と変換してくれる為、使い手側は「panda.png」とサムネイル画像「panda-thumb.png」を用意しておけばいい事になります。
実際は、「panda.png」から「panda-thumb.png」への画像変換は手作業で convert panda.png -resize 300x200 panda-thumb.png
※convertはImageMagick付属コマンド
とやってるのですが、ここも本当はオートメーション化したいと思っています。

で、今回はこのプラグインを紹介したかったのではなく、jQueryでサムネイル画像から実画像への表示をビジュアルに表現してくれる「Shadowbox」というプラグインを導入しました。
mjijackson » Shadowbox 1.0 released
なぜ導入に至ったかというと、このimagerefプラグインを少し修正すれば簡単にShadowboxが使えるのでは無いかと思ったからです。
Shadowboxを使うためには、jqueryのロードは必要になります。そして初期化処理として <script type="text/javascript"><!--
$(document).ready(function() {
    Shadowbox.init();
});
--></script>
というコードを書き、あとは
<a src="xxx-big.jpg">
  <img src="xxx-small.jpg"/>
</a>
となっている部分に「rel="shadowbox"」を足して
<a src="xxx-big.jpg" rel="shadowbox">
  <img src="xxx-small.jpg"/>
</a>
とすれば良し。
class属性ではなく、rel属性を使っているので少し気味が悪い気もしますがそんなの気にしない。

今回はこれをimagerefが勝手に出力してくれる様に修正したまでです。

これでいつも通りにimagerefを使えば
<blockquote>
    <!-- imageref thumb:giraffe-photo.jpg -->
</blockquote>

キリン
となる訳です。
ちなみにこのShadowbox、画像だけでなく
twtter - mattn
といったインラインHTMLやFlash、動画もサポートしている様です。結構手軽に導入出来る割に得られる効果が大きいかもしれません。
一度使って見られてはどうでしょうか?

関連記事
MOONGIFT: » クールに表示「Shadowbox.js」:オープンソースを毎日紹介

追記
shadowboxやめてjquery.lightbox(balupton edition)にしました。
Posted at by




そろそろNabeAtzzにも飽きてきたので、エドはるみグリモン作ってみた。
Firefoxで閲覧中に現れる「ing」とか「グ」に反応してエドはるみが登場します。 Googleでsomethingとか検索してしまったら鬱陶しい事間違いありません。
edo-good
動作画面はこのような画面になります。
good-goo-goo-goo-good.user.js
ぜひご活用下さい。
Posted at by



2008/05/11


twitterを辞めてwassrに移ってしまった皆様、常にwassrだけ常駐されておられる皆様、こんにちわ。
vimperatorでtwitter出来るvimperator plugin「twitter.js」を改造してwassr出来る「wassr.js」を作ってみたよ。
機能としては
  • ステータス更新
  • ステータス参照
  • TODO管理
  • 足跡参照
が出来ます。まずステータス参照は「twitter.js」と同じく :wassr
ステータス更新は :wassr wassrかわいいよwassr
TODOは :wassr -todo
で一覧表示 :wassr -todo+ かめはめ波を発射する
でTODO追加。
追加後のID一覧が表示されます :wassr -todo- XXXXXXX
で削除。
XXXXXXXはTODOのID :wassr -todo* XXXXXXX
で開始 :wassr -todo/ XXXXXXX
で停止 :wassr -todo! XXXXXXX
で完了となります。TODOのIDは補完が効くので<tab>で選んで下さい。
また :wassr -footmark
で足跡の一覧も見れます。
オプションは :wassr<tab> で候補が出ます。
よろしければどぞ。
wassr.js

wassrかわいいよwassr
Posted at by



2008/05/09


元々はきゃーさん所で知った「小粋空間: サイドメニュー折りたたみの動作遅延対処」をパクってやってたんですが、良く考えたらこのサイトってjQuery使ってたなぁ...と
$.each({
    tags : true,
    category : true,
    archives : true,
    comments : false,
    trackbacks : false
}, function(i) {
    var f = $('#' + i + 'name');
    f.click(function() {
        $('span', this).html(($('#' + i + 'list').css('display') != 'none') ? '▼' : '▲');
        $('#' + i + 'list').toggle('fast');
    }).css('cursor', 'pointer').append('<span>▲</span>');
    if (this == true) f.click();
});
すんなり少量のコードになりました。jQuery++
Posted at by




twitter検索しまくり - noreplyのブログ
twitter_viewで,fとかすると今表示されてるステータスをfav
http://d.hatena.ne.jp/noreply/20080503/1209785708
twitter_viewってtwitteViewer.jsの事だったんだろうかと悩み中...
Posted at by



2008/05/02


以前、「Big Sky :: 意外と知られていないvimのtips(vimでソースコードをHTML化するコツ)」という記事を書いて、なかなか好評だったのですが(前のサーバで公開した記事なので反響は見えませんが)、vimでHTMLを編集しておられる方って結構多いと感じました。
私もしかり、このブログにおいても全てvimで編集しています。
ソースコードは上記リンクに記した通り「:TOhtml」を使い、その他はほぼ全て打ち込んでいます。ただ別のサイトにある記事やリンクを引用する場合、少し手間が発生します。例えば
<a href="http://example.com">サンプル</a>
とタイプする場合、ブラウザのアドレスバーからURLを、さらにブックマーク画面を出してタイトルをコピーして上の様な記述に置き換えるとなるとキーボードだけで操作出来なかったりして少し煩わしい感じがしますね。オーサリングツールやmarkdown等で楽する事も出来ますがちょっとした更新で、しかもvimが使いたいなんて場合もありますよね。
また、ブラウザは起動していないけどURLならもう知ってるなんて場合、タイトル抜き出すのが大変なんて思っていませんか?
今日は私が使っているvimスクリプトを紹介してみます。
まずソース let g:Anchorize_Format = '<a href="%s" class="external" target="_blank">%s</a>'
function! Anchorize_TITLE(url)
  silent! split _FETCHTITLE_
  silent! exec "0r!curl -s ".a:url
  if executable('nkf')
    if &enc == 'utf-8'
      silent! %!nkf -X8
    elseif &enc == 'cp932'
      silent! %!nkf -Xs
    endif
  endif
  silent! %join!
  silent! %g/^\s*$/d _
  silent! %s/^.\{-}<title[^>]*>\([^<]\+\)<\/title>.*/\1/i
  let ret = getline('.')
  silent! bw!
  if exists('Anchorize_Format')
    let format = Anchorize_Format
  else
    let format = '<a href="%s">%s</a>'
  endif
  return printf(format, a:url, ret)
endfunction
function! Anchorize_URL(v)
  let url = input('URL:')
  if len(url)
    if exists('Anchorize_Format')
      let format = Anchorize_Format
    else
      let format = '<a href="%s">%s</a>'
    endif
    if a:v
      silent! normal! gvs
      let word = getreg('"')
      exec "normal! a".printf(format, url, word)
    else
      let word = expand('<cword>')
      exec "normal! ciw".printf(format, url, word)
    endif
  endif
endfunction
nnoremap <leader>,u :call Anchorize_URL(0)<cr>
vnoremap <leader>,u :<c-w>call Anchorize_URL(1)<cr>
vnoremap <leader>,U s<c-r>=Anchorize_TITLE(getreg('"'))<cr><esc>
これをanchorize.vimとかでpluginディレクトリに放り込むとインストール完了。
使い方は、まず 今日もGoogleで検索 の「Google」という部分を「http://www.google.co.jp/」へのリンクにしたい場合には「Google」の部分までカーソルを移動して <leader>,u 「<leader>」は何も設定していなければ「¥」になっているかと思います。
とタイプすると URL: というプロンプトが出るのでここに URL:http://www.google.co.jp/ とタイプしてENTERを押すと 今日も<a href="http://www.google.co.jp/">Google</a>で検索
と変換されます。また空白を含んだ、例えば「Google Search」の様な場合「Google Search」をビジュアル選択して「<leader>,u」とすれば同じ動作になります。
さらに例えば 今日もhttp://b.hatena.ne.jp/でホットエントリ というテキストのURL部分をビジュアル選択し、今度は <leader>,U 「U」は大文字
とタイプすると 今日も<a href="http://b.hatena.ne.jp/">はてなブックマーク - ソーシャルブックマーク</a>でホットエントリ
と変換してくれます。
急いでネタ記事をアップしたいvimmerには重宝するのではないかと思います。なお、動作にはnkfというフィルタコマンドが必要です。Windowsであれば「nkf win32」あたりで検索すればヒットするかと思います。ちなみに、vimrc等でAnchorize_Formatという変数を let g:Anchorize_Format = '<a href="%s" class="external" target="_blank">%s</a>'
等といった感じに編集する事も出来るので、class属性を常に付けたい人にも使えるかと思います。
元々公開するつもりも無かったスクリプトなので、拡張性ありませんがよろしければどうぞ。
Posted at by




vimperator otsuneさんが以前、「Firefox 3.0b5とvimperator0.6とAutoPagerizeとLDRizeとMinibufferとldr_cooperation.jsを組み合わせると快適過ぎる - otsune's SnakeOil - subtech」で紹介していたvimperator、もうインストールしてしまった人はいるだろうか。
まだインストールしていない人は、今後vimperaorでウェブブラウジングが実際にどの様に変っていくかが知りたいかと思います。
Firefoxをvimぽくする拡張、vimperatorを入れると、まさしくFirefoxがvimぽくなる。このぽくが重要。vimのままを期待している人は拍子抜けするかもしれない。でも、キーボードしか触りたくないユーザにとってvimperatorは心地よい操作感を与えてくれると思います。
一般的な使い方は「ナレッジエース - Firefoxをキーボード操作できるプラグイン「Vimperator」の使い方」が参考になるかと思います。
ここでは通常のウェブブラウジング以外の操作がどの様に変るかをご紹介したいと思います。
例えば通常以外の操作を以下の様に大別してみます。
  • Googleでの検索
  • ソーシャルブックマークへポスト
  • twitter
  • はてなスター

Googleでの検索

vimperatorではテキスト選択もキーボードで行う。「i」を押してキャレットが表示されるので「hjkl」で移動したり「0」や「$」で行頭や行末に、「gg」や「G」でページの先頭やページの末尾に移動する。選択したくなったら「v」を押して選択モードに移り、同じく「hjkl」等で移動後「y」でyank(コピーの意味)出来ます。 さらに単語を検索もキーボードで。例えば上のGoogle検索結果で「firefox」を検索するならば /firefox<ENTER> と入力します。「firefox」という単語がハイライトされvimの様に「n」で次の検索単語へジャンプ出来る。リンクの上までジャンプし「ENTER」を押すとリンク先にもジャンプ出来ます。
そして肝心な検索ですが、vimperatorには既に数多くのpluginが作られており、その中の一つに「googlesuggest.js」があります。
この「googlesuggest.js」を使えば先ほどyankした単語を簡単に検索出来ます。
:google <s-insert><ENTER> 「<s-insert>」はSHIFTを押しながら「INSERT」キー。「<c-v>」(CTRLを押しながらvでもok) とすればGoogleの検索結果が出来ます。ただ、そこまでしなくても「googlesuggest.js」を使えば :google fire<tab> とすれば :google firefox と補完してくれます。
「googlesuggest.js」はcodereposからダウンロード出来るので「~/.vimperator/plugin」(Windowsなら「%USERPROFILE%¥vimperator¥plugin¥」)にコピーすれば完了(ブラウザ再起動「:restart」が必要)。

ソーシャルブックマークへのポスト

これもvimperator pluginで行う。使うのは「direct_bookmark.js」でインストール方法は「googlesuggest.js」と同じ。
ブックマークしたいページを見付けたら :sbm タグやコメントを付けたいならば「はてな方式」で :sbm [vimperator]それvimperatorで出来るよ といった感じに。現状既に「はてなブックマーク」、「del.icio.us」、「livedoor clip」に対応しています。タグは補完が可能でサーバからタグ一覧を再取得するには
:btags さらにブックマークエントリページに行きたいならば :bentry 「:bentry h」で「はてなブックマーク」に...といった感じ

twitter

twitterは「twitter.js」を使う。
:twitter とすればfollowerのステータスが表示されます。
vimperator-twitter
ステータスを更新したければ :twitter 声が小さい!おいっす! とでもすればいい。

はてなスター

「hatenaStar.js」を使う。使い方は :hatenastar 現状豪快仕様プラグインでページ内の全ての「はてなスター」をクリックする為、「はてなスター」がいっぱい付いたページで実行するとエライ事になるので注意。
既にid:retletさんがやってしまってますが...
一応、何番目の「はてなスター」かを引数でも指定出来るので :hatenastar 1 とかしとくとブラウザが爆発する心配もありません。

その他、色んなvimperator pluginがあるので興味ある方は試してみてはどうでしょうか。
coderepos にあるvimperator plugin
また「ldr_cooperation.js」を使えばgreasemonkeyのminibufferと共存する事も出来るので、キーボードだけでブラウザを操作したい人には「ウマー」な環境が出来上がるかと思います。

Firefox 3のリリース時に、「えっvimperatorで出来ないの?」なんて事にならない様、vimperator plugin開発者の皆様、頑張っていきましょう。
Posted at by



2008/05/01


幾らかのサーバにしか対応出来ていませんが、とりあえずリリース。
現在
  • はてなダイアリー
  • はてなグループダイアリー
  • ライブドアブログ
  • Seesaa ブログ
  • Nowa
に対応出来ています。入力欄にpermalinkを入力して「Generate RSS」をクリックするとフィードが表示されます。
BeautifulSoupを使っているので、あまり白熱したコメント欄だとサーバが火を噴くかもしれません。
また過激な議論、18才未満に相応しく無いもの等は、強制でフィード出力を停止する場合があります。

CommentRSS
よろしかったら使ってみて下さい。
Posted at by




日頃よりご愛好頂きありがとうございます。まぐれです。
おそらくまもなくランキングから消え去りますので、記念に...
feed-meter-297-20080501
Posted at by



2008/04/28


床屋。もし結果同じ髪型に仕上がる床屋が2軒あったとして、片方は5分で片方は40分で仕上がる店があったとしたら。私は迷わず40分掛かる床屋に行くだろう。

髪切り落とされる様をただ見る時間。髭をそるクリームを塗られ、髭を剃られるまでの「じらし」。これも床屋の魅力であると思う。
別にしっかり肩を「パンパン」叩いてくれとも思わない。しっかり叩いたら床屋で無くなる気がする。

もしかしたら、コンピュータ業界も同じなのかもしれない。
要件定義から、すぐさま答えの出る業界ならば面白くもなんともないかもしれない。
ああでもない。こうでもないと考えてこそ面白いのであって、仕様が、そして納期が決められているからこそ面白いのだ。別の担当者が作る部品の完成をドキドキしながら待つ時間。それも床屋と同じと考えれば気が楽になったりしないだろうか。しないか...
もしかしたら気の効きすぎた、仕事を100%こなすソフトウェアというのは、実は魅力的でないのかもしれない。

プログラミング言語を取ってみても、各言語毎にそれぞれライブラリがあり目的を達成させる為の近道になる物が沢山ある。
それも良い事だとは思うけれど、気の効いてない言語もまた楽しかったりするんだろうな。

でも、髭くらいはちゃんと剃れ。
Posted at by



2008/04/24


書き換えてしまえ...
*** elementtree/ElementTree.py.orig Thu Apr 24 21:02:09 2008
--- elementtree/ElementTree.py  Thu Apr 24 21:34:42 2008
***************
*** 1252,1254 ****
--- 1252,1267 ----
          tree = self._target.close()
          del self._target, self._parser # get rid of circular references
          return tree
+
+ try:
+     from xml.parsers import expat
+ except ImportError:
+     import xmllib
+     import SimpleXMLTreeBuilder
+     def __init__fake(self, html=0):
+         self.__init__orig(html)
+         xmllib.XMLParser.__init__(self, accept_utf8=1)
+     clazz = SimpleXMLTreeBuilder.TreeBuilder
+     clazz.__init__orig = clazz.__init__
+     clazz.__init__ = __init__fake
+     XMLTreeBuilder = SimpleXMLTreeBuilder.TreeBuilder
ちなみにpatch当てなくても、ElementTree.pyの最下行に
try:
    from xml.parsers import expat
except ImportError:
    import xmllib
    import SimpleXMLTreeBuilder
    def __init__fake(self, html=0):
        self.__init__orig(html)
        xmllib.XMLParser.__init__(self, accept_utf8=1)
    clazz = SimpleXMLTreeBuilder.TreeBuilder
    clazz.__init__orig = clazz.__init__
    clazz.__init__ = __init__fake
    XMLTreeBuilder = SimpleXMLTreeBuilder.TreeBuilder
を足せば出来上がる。こうすると大概のものは小細工無しに動くと思う。たぶん
エラーが出てたGoogle App Engineのサンプル、muvmuvのレビュービルド「http://localhost:8080/build」も問題なく通った。
良いやり方ではないですが...
Posted at by




Google App EngineでwebSimple.pyを使って作りました。

twitterのfollower発言で、漢字が読めなかった貴方。「@mattn_jp それなんて読むの?」とか聞くのが恥ずかしい貴方。そんな貴方にピッタリのサービスです。twitter followerの発言に「読み」を付けて表示します。(まぁ私は難しい単語なんて使いませんが...)

文字の分解にはYahoo! JAPANの「日本語形態素解析Webサービス」を使用しています。

色の変っている部分にマウスを当てると、ツールチップにて読みを教えてくれるようになっています。

よみふったー
で、いきなりですが「よみふったー」のソースです。
ライブラリとしてはwebSimple.pyのほかにPyWrapperに含まれるElementTree、BeautifulSoupを使っています。
#!-*- coding:utf-8 -*-
import os
import re
import base64
import xmllib
import logging
import elementtree.SimpleXMLTreeBuilder as xmlbuilder
import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from BeautifulSoup import BeautifulSoup
from webSimple import Simple as webSimple

class MainPage(webapp.RequestHandler):
  def post(self):
    twitter_user = self.request.get('twitter_user').encode('utf-8', 'replace')
    template_valuse = {
        'twitter_user' : twitter_user,
        'statuses' : []
    }
    try:
      twitter_url = "http://twitter.com/statuses/user_timeline/%s.xml" % twitter_user
      twitter = webSimple({ 'base_url': twitter_url })
  
      yahoo = webSimple({
        'base_url': 'http://api.jlp.yahoo.co.jp/MAService/V1/parse',
        'param' : { 'appid' : 'xxxxxxxx', 'results' : 'ma', },
      })
  
      xml = twitter.get().content
      r = re.compile(r'(\&#\d+;)')
      for st in BeautifulSoup(xml)('status'):
        name = r.sub(lambda x: unichr(int(x.group(1)[2:-1])), st.user.screen_name.string).encode('utf-8', 'replace')
        msg = r.sub(lambda x : unichr(int(x.group(1)[2:-1])), st.text.string).encode('utf-8', 'replace')
        xml = yahoo.get({ 'sentence': msg }).content
        words = []
        for word in BeautifulSoup(xml)('word'):
          words.append({
            'reading' : word.reading.string.encode('utf-8', 'replace'),
            'pos' : word.pos.string.encode('utf-8', 'replace'),
            'surface' : word.surface.string.encode('utf-8', 'replace'),
          })
        template_valuse['statuses'].append({
           'screen_name' : name,
           'words' : words,
        })
    except Exception, e:
      template_valuse['error'] = e
      pass
    path = os.path.join(os.path.dirname(__file__), 'yomifutter.html')
    self.response.out.write(template.render(path, template_valuse))

  def get(self):
    path = os.path.join(os.path.dirname(__file__), 'yomifutter.html')
    self.response.out.write(template.render(path, {}))

def main():
  application = webapp.WSGIApplication([('/yomifutter/', MainPage)], debug=True)
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
  main()
適当なコードで申し訳ない...汗
Posted at by




Google App Engine上でweb.pyとかCherryPyとか動かないという報告がいくらかあったのでまとめてみます。
Google App Engineでは、以下で述べられているように「WSGIに対応したCGI(と呼ばれるもの)であれば、フレームワークとして扱えるよ」と言っています。
Using the webapp Framework - Google App Engine - Google Code

The CGI standard is simple, but it would be cumbersome to write all of the code that uses it by hand. Web application frameworks handle these details for you, so you can focus your development efforts on your application's features. Google App Engine supports any framework written in pure Python that speaks CGI (and any WSGI-compliant framework using a CGI adaptor), including Django, CherryPy, Pylons, and web.py. You can bundle a framework of your choosing with your application code by copying its code into your application directory.

http://code.google.com/appengine/docs/gettingstarted/usingwebapp.html
今日はこの動かないと言われている部分を解決して頂ける(かもしれない)ポイントをご紹介。

web.py

まずweb.pyですが、オフィシャルがtarballで配布している物(web.py-0.23.tar.gz)では動きません。最新はhttp://webpy.org/bzr/webpy.dev/で配布されており、bzrを使って取得する必要があります(必要であればhttp://bazaar-vcs.org/Downloadからbzrを取得して下さい)。 bzr get http://webpy.org/bzr/webpy.dev/
これまでの1スクリプトからソケットサーバまで起動するweb.pyのコーディング習慣ではそのままでは動きません。flupを使ってWSGIモジュールを動かす必要があります。以下の様にWSGIハンドラとしてアプリケーションを作成しwsgiref.handlersに起動させます。mod_pythonの場合と同じですかね。
ソースコードの中を覗いた所、Google App Engineに対応するコードが入っています。以前までは一般的なCGIとして「print "Hello World!"」してしまえばそのまま出力されていましたが方式が変った様で、GET等はreturnで文字列として返す様になっています。
動くコードとしては以下の様になります。
#!-*- coding:utf-8 -*-
import web

urls = (
  '/hello/(.*)', 'hello'
)

class hello:
  def GET(self, name):
    web.header("Content-Type", "text/html; charset=utf-8")
    return "Hello World!"

if __name__ == "__main__":
  web.application(urls, globals()).cgirun()
但し、「web.pyが内部でimportしているopenid.consumerが無いよ!」と怒られるのでpython-openidをいっそ入れてしまうか、以下のパッチを当てる必要があります。 --- web/__init__.py.orig    Wed Apr 23 19:12:33 2008
+++ web/__init__.py Wed Apr 23 19:12:35 2008
@@ -26,7 +26,7 @@
 from httpserver import *
 from debugerror import *
 from application import *
-import webopenid as openid
+#import webopenid as openid
 
 try:
     import cheetah
まだ開発版の様ですから、今後に期待したいです。

CherryPy

次にCherryPyですが少し小細工が必要です。Google App EngineではPure Pythonで無いものは動かないのですがCherryPyに含まれるWSGIServer(実際にはSSL機能)がsocket._fileobjectを使ってしまっていてモジュールのインポートに失敗します。以下の様にしてSSL_fileobjectを殺してやる必要があります。
--- cherrypy/wsgiserver/__init__.py.orig    Sun Jan 13 17:56:50 2008
+++ cherrypy/wsgiserver/__init__.py Wed Apr 23 16:47:34 2008
@@ -57,11 +57,12 @@
 from urllib import unquote
 from urlparse import urlparse
 
-try:
-    from OpenSSL import SSL
-    from OpenSSL import crypto
-except ImportError:
-    SSL = None
+#try:
+#    from OpenSSL import SSL
+#    from OpenSSL import crypto
+#except ImportError:
+#    SSL = None
+SSL = None
 
 import errno
 socket_errors_to_ignore = []
@@ -676,19 +677,19 @@
                 raise socket.timeout("timed out")
     return ssl_method_wrapper
 
-class SSL_fileobject(socket._fileobject):
-    """Faux file object attached to a socket object."""
-    
-    ssl_timeout = 3
-    ssl_retry = .01
-    
-    close = _ssl_wrap_method(socket._fileobject.close)
-    flush = _ssl_wrap_method(socket._fileobject.flush)
-    write = _ssl_wrap_method(socket._fileobject.write)
-    writelines = _ssl_wrap_method(socket._fileobject.writelines)
-    read = _ssl_wrap_method(socket._fileobject.read, is_reader=True)
-    readline = _ssl_wrap_method(socket._fileobject.readline, is_reader=True)
-    readlines = _ssl_wrap_method(socket._fileobject.readlines, is_reader=True)
+#class SSL_fileobject(socket._fileobject):
+#    """Faux file object attached to a socket object."""
+#    
+#    ssl_timeout = 3
+#    ssl_retry = .01
+#    
+#    close = _ssl_wrap_method(socket._fileobject.close)
+#    flush = _ssl_wrap_method(socket._fileobject.flush)
+#    write = _ssl_wrap_method(socket._fileobject.write)
+#    writelines = _ssl_wrap_method(socket._fileobject.writelines)
+#    read = _ssl_wrap_method(socket._fileobject.read, is_reader=True)
+#    readline = _ssl_wrap_method(socket._fileobject.readline, is_reader=True)
+#    readlines = _ssl_wrap_method(socket._fileobject.readlines, is_reader=True)
 
 
 class HTTPConnection(object):
あとはweb.py同様に import cherrypy
import wsgiref.handlers

class OnePage(object):
  def index(self):
      return "one page!"
  index.exposed = True
 
class HelloWorld(object):
  onepage = OnePage()

  def index(self):
    return "hello world"
  index.exposed = True

def main():
  app = cherrypy.Application(HelloWorld(), "/helloworld")
  wsgiref.handlers.CGIHandler().run(app)

if __name__ == '__main__':
  main()
とすればGoogle App Engine上でも動きます。上の例にあるCherryPy独特の「/helloworld/onepage」も動きますよ!
これで幾らかのフレームワークが動くようになりました。幾らか敷居が低くなるのではないでしょうか。皆さんも色んなアプリケーションを作ってみませんか。
また時間が出来たら、残るPylonsも検証して見たいと思います。

最後に私が好きなCDを...
いけないチェリー・パイ いけないチェリー・パイ
ウォレント
Sony Music Direct CD / ¥1,575 (2004年07月22日)
 
発送可能時間:

Posted at by




こんなのあるんだ...
freshmeat.net: Project details for pytumblr - ロックスターになりたい
pytumblr is a Python library for the tumblr.com API. freshmeat.net: Project details for pytumblr
Google App Engine そう言えば前に「Windowsのエクスプローラで「送る」からShareOnTumblr」なんてのも作ったなぁ。 pythonで作ってあってlinuxなんかでも動くように作ったはず。

でpytumblrですが、ソース見たら簡単なソースだったのでGoogle App Engineで動くように改造してみました。以下パッチ
--- pytumblr.py.orig    Thu Apr 24 03:15:26 2008
+++ pytumblr.py Thu Apr 24 12:02:32 2008
@@ -1,7 +1,9 @@
 #!/usr/bin/env python
+#!-*- coding:utf-8 -*-
 
-import string, httplib, urllib2, urllib
-from xml.dom import minidom
+import urllib2, urllib
+from google.appengine.api import urlfetch
+from BeautifulSoup import BeautifulSoup
 
 class pytumblr(object):
    """Tumblr API Object.
@@ -75,14 +77,11 @@
 
        data = urllib.urlencode(values)
        headers = {"Content-type": "application/x-www-form-urlencoded"}
-       conn = httplib.HTTPConnection(self.url)
-       conn.follow_all_redirects = True
-       conn.request("POST",'/api/write', data, headers)
-       response = conn.getresponse()
-       if ( int(response.status) == 201):
+       response = urlfetch.fetch("http://%s/api/write" % self.url, headers=headers, method='POST', payload=data)
+       if ( int(response.status_code) == 201):
            return 'Success'
-       elif( int(response.status) != 201):
-           raise 'Error - Status %s (%s) returned' %(response.status, response.reason)
+       elif( int(response.status_code) != 201):
+           raise 'Error - Status %s (%s) returned' %(response.status_code, 'something wrong')
 
    def auth(self):
        values = {
@@ -92,13 +91,10 @@
            }
        data = urllib.urlencode(values)
        headers = {"Content-type": "application/x-www-form-urlencoded"}
-       conn = httplib.HTTPConnection(self.url)
-       conn.follow_all_redirects = True
-       conn.request("POST", '/api/write', data, headers)
-       response = conn.getresponse()
-       if ( int(response.status) != 200 ):
-           return '< There was a tiny problem: %s (%s) >' %(response.status, response.reason)
-       if ( int(response.status) == 200 ):
+       response = urlfetch.fetch("http://%s/api/write" % self.url, headers=headers, method='POST', payload=data)
+       if ( int(response.status_code) != 200 ):
+           return '< There was a tiny problem: %s (%s) >' %(response.status_code, 'something wrong')
+       if ( int(response.status_code) == 200 ):
            return '< Authenticated! >'
    
 
@@ -112,25 +108,8 @@
            opts = opts + "?type=" + type
        if ( id != 'None' ):
            opts = opts + "?id=" + id
-       return urllib.urlopen('http://%s.tumblr.com/api/read%s' %(self.user, opts)).read()
+       return urlfetch.fetch('http://%s.tumblr.com/api/read%s' %(self.user, opts)).content
        
    def getblog(self):
-       rxml = minidom.parseString(self.blogread())
-       titles = rxml.getElementsByTagName('regular-title')
-       postid = rxml.getElementsByTagName('post')
-       i = 0
-       n = 0
-       posts = {}
-       while ( i < len(postid)):
-           if ( postid[i].attributes['type'].value == 'regular' ):
-               t = titles [n]
-               t = t.toxml()
-               t = t.replace('<regular-title)', '')
-               t = t.replace('</regular-title)', '')
-               poid = postid[i].attributes["id"].value
-               posts[ poid ] = t
-               n = n + 1
-           if ( postid[i].attributes['type'].value != 'regular' ):
-               pass
-           i = i + 1
-       return posts
+       soap = BeautifulSoup(self.blogread())
+       return soap('post')
思いっきり弄ってますね。。。
minidomの代わりにBeautifulSoupを、urllib2の代わりにurlfetchを使っています。したがってgetblogは自分のregular情報のdictだけを返すのではなくBeautifulSoupを使ってlink、photo、quoteを返す様にしてあります。
実際に動くよって所は以下のサイトで確認して下さい。
pytumblr
このサイトのURLの後ろに http://mattn.appspot.com/tumblr/mattn と言った感じにtumblrアカウント名を付けてみて下さい。
今回のデモにはweb.pyというフレームワークを使用してみました。
以下スクリプトソースです。 #!-*- coding:utf-8 -*-
import web
from pytumblr import pytumblr
from BeautifulSoup import BeautifulSoup

def unescape(str):
  return  BeautifulSoup(str, convertEntities=BeautifulSoup.HTML_ENTITIES).contents[0].encode('utf-8', 'replace')

urls = (
  '/tumblr/(.*)', 'tumblr'
)
render = web.template.render('templates/')

class tumblr:
  def GET(self, name):
    web.header("Content-Type", "text/html; charset=utf-8")
    template_values = { 'name': '', 'posts': [] }
    name = name.replace('/', '')
    if name:
      pt = pytumblr(name, None, None)
      template_values['name'] = name
      for blog in pt.getblog():
        if blog['type'] == 'link':
          template_values['posts'].append({
            'type': 'link',
            'text': unescape(blog('link-text')[0].string),
            'desc': blog('link-description') and unescape(blog('link-description')[0].string) or '',
            'link': unescape(blog('link-url')[0].string),
          })
        if blog['type'] == 'photo':
          template_values['posts'].append({
            'type': 'photo',
            'text': unescape(blog('photo-caption')[0].string),
            'link': unescape(blog('photo-url')[0].string),
          })
        if blog['type'] == 'quote':
          template_values['posts'].append({
            'type': 'quote',
            'text': unescape(blog('quote-text')[0].string),
            'link': unescape(blog('quote-source')[0].string),
          })
    return render.tumblr(template_values)

if __name__ == "__main__":
  web.application(urls, globals()).cgirun()
案外短く書けますね。そしてテンプレートHTML
$def with (res)
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="/static/css/tumblr.css" type="text/css" />
$if res['name']:
    <title>pytumblr - $res['name']</title>
$else:
    <title>pytumblr</title>
</head>
<body>
$if res['name']:
    <h1>pytumblr - $res['name']</h1>
$else:
    <h1>pytumblr</h1>
    <img src="http://b.hatena.ne.jp/entry/image/http://mattn.appspot.com/tumblr/" title="はてなブックマーク" />
    <div id="content">
$if res['name']:
    <h2>$res['name']'s tumblr</h2>
    $for post in res['posts']:
        $if post['type'] == 'photo':
            <b>PHOTO:</b>$:post['text']<br />
            <blockquote><img src="$post['link']" /></blockquote>
        $elif post['type'] == 'link':
            <b>LINK:</b><a href="$post['link']">$:post['text']</a><br />
            <blockquote class="link">$:post['desc']</blockquote>
        $elif post['type'] == 'quote':
            <b>QUOTE:</b><br />
            <blockquote class="quote">
                $:post['text']<br />
                <cite>$:post['link']</cite>
            </blockquote>
    </div>
    <hr clear="all" />
    <p style="text-align: center">provided by <a href="http://mattn.kaoriya.net">mattn</a>, hosted on google app server.</p>
</body>
</html>
このweb.pyのテンプレートって癖があってpythonインデント方式なのですが、これってpreとかcodeで先頭が入っちゃったら不味いんじゃないかと思ったり...
解決方あるのか、調べてみます。pytumblrで面白いもの作ってみて下さい。

みんなのPython みんなのPython
柴田 淳
ソフトバンククリエイティブ 単行本 / ¥50 (2006年08月22日)
 
発送可能時間:

Posted at by




クァーってサービスが出来たみたいです。
Qaa(クァー):Twitterで簡単アンケート!(まだまだ開発中)
Qaa

Qaa(クァー)は、Twitterを利用して簡単にアンケートをとることができるサービスです。

Qaa(クァー)を通じて投稿されたアンケートはYes or No形式で回答する事ができ、その結果はアンケート毎に用意されるページでパイチャートで確認する事ができます。


http://qaa.orig.jp/h#about
twitterを使ったソーシャルアンケートサービスですね。
でもいちいちtwitterやIMから「@qaa qid14 YES」とか入力するの面倒臭いのでMinibufferコマンド作ってみました。
アンケートエントリページに行って、「q y」もしくは「q n」で「YES」もしくは「NO」の回答をtwitter経由で投稿します。
qaa_yes_or_no.user.js
よろしければどうぞ。
※一応、firefox3/greasemonkeyからBasic認証のダイアログが出ない件は対応してあります。
Posted at by



2008/04/21


なるべくdictぽく扱えるように作ってみました。
WedataオブジェクトのコンストラクタにAPIKEYを掘り込んで操作します。
api = Wedata('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
databases = api.databases()
for database in databases:
  print database
  for key in database.keys():
    print " %s=%s" % (key, database[key])
  print
print
また、AutoPagerizeのデータベースであれば以下の様にdataプロパティからの属性参照の様に書く事も出来ます。
database = api.database('AutoPagerize')
print "%s : %s" % (database.name, database.description)
items = database.items()
for item in database.items():
  print " %s" % item.data.pageElement
  print " %s" % item.data.insertBefore
  print
Database.create_databaseの戻り値にはデータベース名(キー名)、Item.create_itemの戻り値にはアイテムIDが戻ります。このIDを使ってDatabase.delete_datebaseおよびItem.dalete_itemを呼び出す事が出来ます。
dbid = api.create_database('my_example_database', 'my_example_database', ['name', 'description'], ['value', 'xpath'], False)
api.delete_database(dbid)
今のところGoogle App Engineには対応していません。Google App Engine上での用途を見付けたら対応するかもしれません。
いつものようにコードはcodereposに置いてあります。
/lang/python/wedata
Posted at by



2008/04/18


世界のナベアツにはてなブックマークで挑戦する - ネットランダム

はてなブックマークのコメントで、1から40まで数えて、3の倍数 と3がつく数字の時だけアホになります。

それではどうぞ!

http://d.hatena.ne.jp/fk_2000/20080418/p2
なんと!ソフトウェアでやるべきネタを人力で!!!

むむむ...こうなればこれをまたソフトウェアで表現するべき!
fk_2000氏と愉快な仲間達がお届けする「世界のナベアツ」

ちなみにコードはこんなの
#!-*- coding:utf-8 -*-
import os
from google.appengine.ext.webapp import template
from google.appengine.api import urlfetch
from BeautifulSoup import BeautifulSoup
import logging

try:
  res = urlfetch.fetch('http://b.hatena.ne.jp/entry/rss/http://d.hatena.ne.jp/fk_2000/20080418/p2')
  print "Content-Type: text/html; charset=UTF-8"
  print ""
  items = []
  for item in BeautifulSoup(res.content)('item'):
    logging.info(item.title)
    logging.info(item.title.string)
    items.insert(0, {
      'title' : item.title.string,
      'description' : item.description.string,
    })
  template_values = {
    'items': items
  }
  path = os.path.join(os.path.dirname(__file__), 'nabeatzz_fk_2000.html')
  print template.render(path, template_values)
except Exception, e:
  print "Status: 400"
  print ""
  print e
Posted at by




reblogぽいですが、私は「We Heart It」の方が好きかな。画像だけに絞っているのがカッコイイ
Minibufferからキー「h i」だけでポスト出来るグリモン書いた。オリジナルのブックマークの動作に従い、250x100以上の画像をポストします。ブックマークレットの場合には「Heart It」のリンクが付きますが、このグリモンではLDRizeのカレントノードに含まれるサイズ250x100以上の画像(PNG/JPG/GIF)をweheartit.comにポストします。
あと、コマンド好きな人は :pinned-or-current-node | weheartit | clear-pin
とかやっとけ!

さっきからtumblr dashboardで使っているのですが便利すぎる!
weheartit.user.js
よかったら、もってけ!

tomblooのコミット権貰ったので気が向いたら...
Posted at by



2008/04/15


出来た。とりあえずの機能としては
  • デザインテンプレート
  • プラグイン機能
  • 検索(プラグイン実装)
  • ページング(プラグイン実装)
  • 編集(プラグイン実装)
  • コメント/トラックバック(プラグイン実装)
まで出来た。なるべくblosxomぽくなるように作ったつもり。動いてる物は
Weblog
にあります。適当にmicroformatに対応してあります。
google-app-engine-blog
この後codereposに上げる予定です。
でもどのディレクトリに上げていいか分かりません><

追記
http://coderepos.org/share/browser/websites/appspot.com/blog/trunk
にアップした。
Posted at by



2008/04/10


pythonにはxmlrpclibがあり、常駐型のSimpleXMLRPCServerやCGIから使えるCGIXMLRPCRequestHandlerというとても有用なモジュールが存在します。
たとえば def plus(num1, num2):
    return num1 + num2
という関数をXMLRPCサーバから公開したい場合 from SimpleXMLRPCServer import CGIXMLRPCRequestHandler
handler = CGIXMLRPCRequestHandler()
handler.register_function(plus)
handler.handle_request()
とすれば出来上がります。
またモジュールとしてインタフェースを提供したい場合は class Foo:
    def plus(self, num1, num2):
        return num1 + num2
というインタフェースに対して from SimpleXMLRPCServer import CGIXMLRPCRequestHandler
handler = CGIXMLRPCRequestHandler()
handler.register_instance(Foo())
handler.handle_request()
こうすれば出来上がり。
先ほど、NabeAtzz APIをXMLRPCとして呼び出せるインタフェースを作りました。
エントリポイントは
http://mattn.appspot.com/nabeatzz_xmlrpc
となります。試しにpythonでXMLRPC Clientを作成し呼び出してみます。
# -*- coding: utf-8 -*-
import sys
from xmlrpclib import ServerProxy

api = ServerProxy("http://mattn.appspot.com/nabeatzz_xmlrpc")
for n in range(1, 11):
  res = api.nabeatzz(n)
  print "%s : %s" % (res['number'], res['japanese'])
  if res.has_key('nabeatzz'):
    for c in res['nabeatzz']:
      print "  %s" % c
コードはこんな感じでしょうか。これを実行すると正しく
1 : いち
2 : に
3 : さん
  アホになる
4 : よん
5 : ご
  犬っぽくなる
6 : ろく
  アホになる
7 : なな
8 : はち
9 : きゅう
  アホになる
10 : じゅう
  犬っぽくなる
という結果が得られました。
Posted at by




追記
日本語表記を返す様に修正しました。japaneseという属性で返ります。
例) 35 : さんじゅうご

JSONとXMLをサポートしています。
template engineでjavascriptとXMLがどう処理出来るかを調べてる間に、いつのまにか作ってしまっていました...。 JSONの場合は以下の様なテンプレート

nabeatzz.json
{% if callback %}{{ callback }}({% endif %}{ number: {{ number }}, japanese: '{{ japanese }}', nabeatzz : [{% if nabeatzz %}'{{ nabeatzz|join:"','" }}'{% endif %}] }{% if callback %}){% endif %}
「value|join: "xxx"」とか便利ですね。
次にXMLの場合は

nabeatzz.xml
<?xml version="1.0" encoding="UTF-8"?>
<response>
    <number>{{ number }}</number>
    <japanese>{{ japanese }}</japanese>{% for c in nabeatzz %}
    <nabeatzz>{{ c }}</nabeatzz>{% endfor %}
</response>
こんな感じです。これはすごい!フツーすぎます。
なお、templateについてはdjangoのtemplate engineと同じ物ですので「テンプレート作者のための Django テンプレート言語ガイド : Django オンラインドキュメント和訳」を参考にすると良いかと思います。

でpythonコードは以下

nabeatzz.py
#!-*- coding:utf-8 -*-
import os
import cgi
from google.appengine.ext.webapp import template

ndg = [
  {0: ''},
  {0: 'じゅう'},
  {0: 'ひゃく', 3: 'ぴゃく', 6: 'ぴゃく', 8: 'ぴゃく'},
  {0: 'せん', 3: 'ぜん'},
]

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

num = [
  {0: 'ぜろ'},
  {0: 'いち'},
  {0: ''},
  {0: 'さん'},
  {0: 'よん'},
  {0: ''},
  {0: 'ろく', 3: 'ろっ'},
  {0: 'なな'},
  {0: 'はち', 3: 'はっ', 4: 'はっ'},
  {0: 'きゅう'}
]

def num2ja(arg):
  sn = str(arg)
  ln = len(sn)
  if sn == 0:
    return num[0][0]
  if ln >= 17*4+1:
    return dig[17][0]
  n = 0
  ret = ""

  while n < ln:
    if sn[n] != 0 and (sn[n] != 1 or (ln-n)%4 == 1):
      ret += num[int(sn[n])].has_key(ln-n) and num[int(sn[n])][ln-n] or num[int(sn[n])][0]
    if sn[n] != 0:
      ret += ndg[(ln-n-1)%4].has_key(sn[n]) and ndg[(ln-n-1)%4][int(sn[n])] or ndg[(ln-n-1)%4][0]
      ret += dig[(ln-n-1)/4].has_key(sn[n]) and dig[(ln-n-1)/4][int(sn[n])] or dig[(ln-n-1)/4][0]
    n += 1

  return ret

try:
    form = cgi.FieldStorage()
    number = ""
    format = "json"
    nabeatzz = []
    nabeatzz_3 = "アホになる"
    nabeatzz_5 = "犬っぽくなる"
    callback = ""
    mime = {
      'json': 'text/javascript',
      'xml': 'text/xml',
    }

    if form:
        if form.has_key('number'):   number = int(form['number'].value)
        if form.has_key('format'):   format = form['format'].value
        if form.has_key('callback'): callback = form['callback'].value

    if number and format in mime.keys():
        if (number % 3) == 0 or str(number).find("3") != -1: nabeatzz.append(nabeatzz_3)
        if (number % 5) == 0: nabeatzz.append(nabeatzz_5)
        template_values = {
            'number'   : number,
            'japanese' : num2ja(number),
            'nabeatzz' : nabeatzz,
            'callback' : callback,
        }
        print "Content-Type: %s; charset=UTF-8" % mime[format]
        print ""
        path = os.path.join(os.path.dirname(__file__), 'nabeatzz.%s' % format)
        print template.render(path, template_values)
    else:
        print "Content-Type: text/html; charset=UTF-8"
        print ""
        path = os.path.join(os.path.dirname(__file__), 'nabeatzz.html')
        print template.render(path, {})
except:
    print "Status: 400"
一応、動く物も用意しました。
NabeAtzz API
おもいっきりネタですね...。
Posted at by



2008/04/09


アプリ第2号です。といってもflickr画像検索同様に有用な物ではありません。
pythonで動作するWebService::Simple「webSimple」を使ってネタバイザーのRSSから最新ネタを取得し、LingrのチャットルームにこれまたwebSimpleで発言するアプリです。
まずテンプレート

lingr.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>ネタバイザーのネタをlingrに転送</title>
<style tyle="text/css"><!--
body {
    font-family: 'メイリオ', 'Osaka'
}
#content {
    margin-left: 50px;
}
--></style>
</head>
    <body>
        <h1>ネタバイザーのネタをlingrに転送</h1>
        <div id="content">
        <p><a href="http://netaviser.woresukebe.com/" class="external" target="_blank">ネタバイザー</a>の最新発言を受信し、<a href="http://www.lingr.com/" class="external" target="_blank">Lingr</a>のチャットルーム「<a href="http://www.lingr.com/room/hO4SmQWTdJ4">LingrAPI Test</a>」に転送します。</p>
        <p>「ネタ転送」ボタンを押下して下さい。</p>
        <form method="post">
            <input type="submit" value="ネタ転送" />
        </form>
        <div>
            {% if neta %}
            ネタ「{{ neta|escape }}」を転送しました。
            {% endif %}
        </div>
        </div>
        <hr />
        <p style="text-align: center">provided by <a href="http://mattn.kaoriya.net">mattn</a>, hosted on google app server.</p>
    </body>
</html>

そしてハンドラ

lingr.py
#!-*- coding:utf-8 -*-
import os
import cgi
import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.api import urlfetch
from webSimple import Simple
import elementtree.SimpleXMLTreeBuilder as xmlbuilder
import xmllib

class MainPage(webapp.RequestHandler):
  def get_neta(self):
      neta = urlfetch.fetch('http://netaviser.woresukebe.com/index.xml').content
      parser = xmlbuilder.TreeBuilder()
      xmllib.XMLParser.__init__(parser, accept_utf8=1)
      parser.feed(neta)
      xml = parser.close()
      return xml.find('channel/item/title').text

  def post(self):
    api = Simple({
        'base_url' : 'http://www.lingr.com',
        'param' : {
            'api_key' : 'your-api-key',
            'format'  : 'xml'
        },
    })

    neta = self.get_neta()

    session = api.get({}, {
        'path' : '/api/session/create',
    }).parse_xml().find('session').text;

    ticket = api.get({
        'session'  : session,
        'id'       : 'hO4SmQWTdJ4',
        'nickname' : 'ネタバイザー転送サーバ',
    }, {
        'path' : '/api/room/enter',
    }).parse_xml().find('ticket').text;

    status = api.get({
        'session'  : session,
        'ticket'   : ticket,
        'message'  : neta,
    }, {
        'path' : '/api/room/say',
    }).parse_xml().find('status').text;

    api.get({
        'session'  : session,
    }, {
        'path' : '/api/session/destroy',
    })

    path = os.path.join(os.path.dirname(__file__), 'lingr.html')
    template_values = {
        'session' : session,
        'ticket'  : ticket,
        'neta'    : neta,
        'status'  : status,
    }
    self.response.out.write(template.render(path, template_values))

  def get(self):
    path = os.path.join(os.path.dirname(__file__), 'lingr.html')
    self.response.out.write(template.render(path, {}))

def main():
  application = webapp.WSGIApplication([('/lingr/', MainPage)], debug=True)
  wsgiref.handlers.CGIHandler().run(application)

で、動いている物がこちら
ネタバイザーのネタをlingrに転送
ネタバイザーに負荷が掛かりますので、あまりにリクエストが多い場合には停止させて頂く所存です。またネタバイザーの方が苦情があれば、これまた停止させて頂く所存です。

なお、先ほど修正したのですがPyWrapperのTreeBuilderはlibxml.XMLParserを初期化する際にutf-8を許可するかどうかのフラグ、「accept_utf8」を0のまま渡してしまっています。よってutf-8なXMLが通りませんでした。
初期化が冗長ですが、作ったTreeBuilderをlibxml.XMLParser.__init__で再初期化する様修正しています。
Posted at by




Google App Engineを弄り始めています。
昨日はGoogle App EngineにXMLパーサが入っていない(実際にはpyexptが入っていない)為、python版のWebService::Simpleが動きませんでしたが、PyWrapperを使う事で解決する事が分かりました。結構知れれていないと思いますので、実は結構有益情報かもしれません。
PyWrapper - Trac
これはすごい!

さっそく昨日作ったpython版WebService::Simple「webSimple」を改良し
  • google.appengine.api.urlfetchがimport出来るならばfetchを使用
  • elementtree.SimpleXMLTreeBuilderがimport出来るならTreeBuilderを使用
となる様にしました。
そしてflickr検索のコードは以下の様になりました。
api = Simple({
    'base_url' : 'http://api.flickr.com/services/rest',
    'param' : {
        'api_key' : 'your-api-key'
    },
})
res = api.get({
    'method'   : 'flickr.photos.search',
    'text'     : keyword.encode('utf-8', 'replace'),
    'per_page' : 3,
});
print res.parse_xml().find("photos").getchildren()
あとは、WSGIApplicationに仕立てる為に以下の様なテンプレートを用意します。

flickr.html
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>flickr画像検索</title>
    </head>
    <body>
        <form method="post">
            <input type="text" name="keyword" value="{{ keyword }}" />
            <input type="submit" />
        </form>
        <div id="content"></div>
        {% for e in photos %}
        <a href="http://www.flickr.com/photos/{{ e.attrib.owner }}/{{ e.attrib.id }}/">
            <img src="http://static.flickr.com/{{ e.attrib.server }}/{{ e.attrib.id }}_{{ e.attrib.secret }}_m.jpg" title="{{ e.attrib.title }}" />
        </a>
        <br />{% endfor %}
        <!--
        <pre>{{ content|escape }}</pre>
        -->
    </body>
</html>
さらに以下の様なハンドラを書きます。

flickr.py
#!-*- coding:utf-8 -*-
import os
import cgi
import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from webSimple import Simple

class MainPage(webapp.RequestHandler):
  def post(self):
    keyword = cgi.escape(self.request.get('keyword'))
    api = Simple({
        'base_url' : 'http://api.flickr.com/services/rest',
        'param' : {
            'api_key' : 'your-api-key'
        },
    })
    res = api.get({
        'method'   : 'flickr.photos.search',
        'text'     : keyword.encode('utf-8', 'replace'),
        'per_page' : 3,
    });
    path = os.path.join(os.path.dirname(__file__), 'flickr.html')
    photos = []
    template_values = {
        'keyword' : keyword,
        'photos'  : res.parse_xml().find("photos").getchildren(),
        #'photos'  : [],
        'content' : res.content,
    }
    self.response.out.write(template.render(path, template_values))

  def get(self):
    path = os.path.join(os.path.dirname(__file__), 'flickr.html')
    self.response.out.write(template.render(path, {}))

def main():
  application = webapp.WSGIApplication([('/flickr/', MainPage)], debug=True)
  wsgiref.handlers.CGIHandler().run(application)

最後に「dev_appserver.py」で動作確認し、「appcfg.py」で更新したら出来上がり。
flickr検索ページが出来上がりました。現状は写真3枚までしか検索出来ないようにしてあります。
flickr画像検索

なお、ディレクトリ構造は ─mattn
  │  flickr.html
  │  flickr.py
  │  webSimple.py
  │  
  └─elementtree
          ElementInclude.py
          ElementPath.py
          ElementPath.pyc
          ElementTree.py
          ElementTree.pyc
          HTMLTreeBuilder.py
          SgmlopXMLTreeBuilder.py
          SimpleXMLTreeBuilder.py
          SimpleXMLTreeBuilder.pyc
          SimpleXMLWriter.py
          TidyHTMLTreeBuilder.py
          TidyTools.py
          XMLTreeBuilder.py
          __init__.py
          __init__.pyc
となっており、elementtreeはPyWrapperから部分的に使用しています。
またwebSimpleのコードはcodereposに上げておきますので、興味のある方は見てください。
/lang/python/webSimple/webSimple.py - CodeRepos::Share - Trac

I love python!
Posted at by



2008/04/08


vimperator pluginの3個です。
direct_delb.js
使い方は「direct_hb.js」と同じ :delb [これはすごい][vimperator]ktkr! です。
Posted at by




コードはcodereposに置いておきます。
/lang/python/webSimple
google appengineのurlfech APIがある場合は、そちらを使うようになっています。
ただしgoogle appengineにはminidomが使っているpyexpatが入ってないのでcontentを取得するまでは動きますがparse_xmlは動きません。
つまりはgoogle appengineのボツネタ第一号って事になりました。
なおfilckr_echo.plをpythonで書いた場合のコードは以下の様になります。
#!-*- coding:utf-8 -*-
from webSimple import Simple

api = Simple({
    'base_url' : 'http://api.flickr.com/services/rest',
    'param' : {
        'api_key' : 'your-api-key'
    },
})
res = api.get({
    'method' : 'flickr.test.echo',
    'name' : 'value',
});
print res.parse_xml().getElementsByTagName('name')[0].childNodes[0].data
google appengineでない場合には使えるのでよかったらどうぞ。
10分程度で作った物なので適当ですが
Posted at by




vimperator pluginの2個です。
gmailは、basic認証で
http://mail.google.com/mail/feed/atom
にアクセスすると、以下の様なXMLが返ってきます。
<?xml version="1.0" encoding="UTF-8"?>
<feed version="0.3" xmlns="http://purl.org/atom/ns#">
    <title>Gmail - Inbox for xxxxxxx@gmail.com</title>
    <tagline>New messages in your Gmail Inbox</tagline>
    <fullcount>1</fullcount>
    <link rel="alternate" href="http://mail.google.com/mail" type="text/html" />
    <modified>2008-04-08T05:10:53Z</modified>
    <entry>
        <title>test!</title>
        <summary>On Tue, Apr 8, 2008 at 2:00 PM, XXXX &lt;xxxxxxx@gmail.com&gt; wrote: &gt; Help Me! ...</summary>
        <link rel="alternate" href="http://mail.google.com/mail?account_id=xxxxxxx%40gmail.com&amp;message_id=XXXX&amp;view=conv&amp;extsrc=atom" type="text/html" />
        <modified>2008-04-08T05:10:41Z</modified>
        <issued>2008-04-08T05:10:41Z</issued>
        <id>tag:gmail.google.com,2004:XXXXXXXXXXXXXXXXXXX</id>
        <author>
            <name>me</name>
            <email>xxxxxxx@gmail.com</email>
        </author>
    </entry>
</feed>

このfullcountに未読数が入ってます。このプラグインはこれを見てvimperatorのステータスバーに未読あり/なしのアイコンを表示します。
新着なしの場合
gmail-biff-status2

新着ありの場合
gmail-biff-status1
現状、30秒単位のポーリングです。またon/offの切り替えもありません。きっとcodereposの誰かが設定出来る様に機能拡張してくれると信じています。

よろしければどうぞ。
gmail_biff.js
Posted at by




私は貧弱じゃないと信じてるんですけどね。
α置換 どう書く?org

標準入力から与えられたソースコードの変数名を置換するプログラムを作ってください。最近はリファクタリングツールなどの普及でこのような需要は少ないかと思われますが、viなど貧弱なエディタを使っているときに困るのが変数名の置換です。

http://ja.doukaku.org/171/
これ「貧弱な」は「エディタ」にかかる言葉なのか、「vi」にかかる言葉なのか気になる。
「それVim」タグ付けたり、「それエディタじゃなくてリファクタリングツールの仕事」とツッコミ入れるつもりもありませんし、意識無く書かれたのだと思いますが、他のエディタと並べたときに「viは少し劣ります」と見えるのは悲しいな。

言葉って難しいですね。一言付け加えるだけで意味合いが凄く変ってしまいます。 となりの家、今日焼肉よ なんて褒め言葉も一言付け加えれば となりの家、安月給なのに今日焼肉よ なんて言葉になるのですから。
Posted at by



2008/04/07


なんか「入れろ!」と言われた気がしたので入れました。vimperator pluginデビュー?

まぁ大したものではないですが...
hatenaStar.js

使い方は :hatenastar もしくはノーマルモードから「,?s」です。カウントが使えますからotsune氏の記事に10個はてなスターを付けたかったら「10,?s」とすればよし。

ま、vimperatorはちょっと変態的なので常用するかはこれから決める。
Posted at by



2008/04/04


なんかFirefox3 Beta5でGoogle Readerの記事表示部が真っ白け。
原因がFirefox3 Beta5にあるのかGoogle Readerにあるのか分かんないけど、どうやら記事表示部のスタイルシートで'100%'となってる部分が問題みたい。
独自のCSSを当てるか、以下のような限定パッチ用グリモン入れれば直る。
// ==UserScript==
// @name           fix broken 'Google Reader' on Firefox3 beta5 2008/04/04
// @namespace      tag:mattn.jp@gmail.com,2008-03-25:/coderepos.org
// @description    fix broken 'Google Reader' on Firefox3 beta5 2008/04/04
// @include        http://www.google.com/reader/*
// ==/UserScript==

(function(d) {
    apply_fixed_style = function(c) { if (c) { c.style.width = '99.9%'; } }
    apply_fixed_style(d.getElementById('viewer-box-table'));
    apply_fixed_style(d.getElementById('search-item-box-table'));
})(document);

あと、Google Reader Full Feedも動かなくなってる。修正してCodeReposにcommitしておいたので、「動かない!」って人は更新して下さい。
googlereaderfullfeed.user.js
暫定的な処置なので、落ち着いてから再度検討し直します。
Posted at by




たぶん「ポインタ変数はポインタ型」で「配列変数は配列型」というイメージが持てないから。
C/C++のポインタの機能--変数の場所(アドレス) - builder by ZDNet Japan
int *n;
*n = 5; /* ポインタ変数nに値5を代入 */ 
現在は本文が修正されています。

C/C++のポインタの機能--配列との関係 - builder by ZDNet Japan

ポインタ変数と配列との深い関係を表す例を示そう。それは、配列の変数名をそのままポインタ変数名として扱えるということだ。


たぶん int *n;
こう宣言するから *n = 5;
こうしたくなる訳で int* n;
こう宣言すれば、もしくは typedef int * int_ptr
こうしておいて int_ptr n;
こう宣言すれば *n = 5;
こんな事が間違ってる事に気付くかな?
int m[] = {1,2,3,4};
int *n;
n = m;
ここでいうnへのmの代入は、mの先頭アドレスをnが指しているだけなのです。ポインタは「指し棒」!配列はメモリのかたまり!
※最近のC言語の本って「先頭アドレス」って表現あまり見ない気がする。

ちなみに、間違っても char* n, m;
m = "Hello, World";
とか #define char_ptr char *
...
char_ptr n, m;
m = "Hello, World";
しちゃ駄目(typedef使ってね)。

ちなみに...
char *s = "abc";
char s[] = "abc";
の話も前者が「ポインタ型」、後者が「配列型」と考えればいい。
つまり typedef char char4[4];
char4 n = "abcef"; // char[4]型変数をchar[6]で初期化
こうすると、コンパイラによっては警告出力してくれる訳です。
Posted at by



2008/04/03


追記
ゆーすけべーさんのほうで、最終引数をパスではなくオプションハッシュにする対応を入れて頂きました。 これにより以下のパッチを当てなくても動的にメソッドパスを設定出来る様になりました。ゆーすけべーさんにはサンプルも変更に対応して頂き、さらにはパッケージに含んで頂いたようです。
yusukebe++
修正されたソースは
http://coderepos.org/share/browser/lang/perl/WebService-Simple/trunk/example/lingr.pl
より参照下さい。以下、パッチも一応残しておきますが当たりませんのでお気をつけ下さい。サンプルは入れ替えておきました。

便利だなぁ。
ゆーすけべー日記: POX over HTTP のウェブAPIにアクセスするためのモジュール「WebService::Simple」を作ってみた

俗に言う「POX over HTTP」のウェブAPIにアクセスするためのシンプルな(?)Perlモジュール「WebService::Simple」なるものを作ってみました。

ゆーすけべー日記: WebService::Simple でキャッシュできるようにしたよ

dannさんから「WebService::Simpleで(取得したコンテンツを)キャッシュしたいよ!」と言われて、俺もその機能欲しかったので追加しました。

flickrとか簡単に扱えてしかもキャッシュ機能もある。1セッション当たりにサーバへのアクセス数が多いサービスでは重宝するんじゃないかな。で、試そうかと思ったんですが、ベースURLが固定なRESTサービス(methodパラメータとか無い物)って案外無い。どっちかっていうと、URLにメソッドを含んでいる物が多い。そうした場合、メソッド単位でURLを変えなきゃいけない。って事でちょっとだけ改造してみました。
この変更は別の方法で取り込まれました。
Index: lib/WebService/Simple.pm
===================================================================
--- lib/WebService/Simple.pm    (revision 8637)
+++ lib/WebService/Simple.pm    (working copy)
@@ -3,9 +3,9 @@
 use warnings;
 use strict;
 use Carp;
+use URI;
 use URI::Escape;
 use LWP::UserAgent;
-use URI::Escape;
 use WebService::Simple::Response;
 
 our $VERSION = '0.02';
@@ -21,8 +21,8 @@
 }
 
 sub get {
-    my ($self, $request_param) = @_;
-    my $url = $self->_make_url($request_param);
+    my ($self, $request_param, $path) = @_;
+    my $url = $self->_make_url($request_param, $path);
     my $response = $self->_fetch_url($url);
     return $response;
 }
@@ -44,14 +44,21 @@
     return $response;
 }
 sub _make_url{
-    my ($self, $request_param) = @_;
+    my ($self, $request_param, $path) = @_;
     my $base_url = $self->{base_url};
     my $url = $base_url =~ /\?$/ ? $base_url : $base_url . "?";
     my @params;
     push(@params, $self->_hashref_to_str($self->{param}));
     push(@params, $self->_hashref_to_str($request_param));
-    my $str = join("&",@params);
-    return $url . $str;
+    $url .= join("&",@params);
+    if ($path) {
+        # append additional path.
+        my $u = URI->new( $url );
+        $path =~ s!^/!! if $u->path =~ /\/$/;
+        $u->path( $u->path . $path );
+        $url = $u->as_string;
+    }
+    return $url;
 }
 
 sub _hashref_to_str {
やっているのは「get」メソッドのパラメータにoptionalな「path」パラメータを足しただけです。このpathパラメータはbase_urlに指定のパスを付け足します。例えば
http://example.com/rest?test=1 とういベースURLに対して $api->get( {
        value => 123,
    }, '/api/foo/bar' );
とすれば http://example.com/rest/api/foo/bar?test=1&value=123 というアクセスになる様な修正です。
ただ、引数の一番最後で良いのかどうかで悩んでcommitはしていません。

今日はこの改造後のWebService::Simpleを使ってLingrに発言するスクリプトを作ってみました。(こんなにスマートになるよ!という意味で)
このサンプルは上記codereposにある物のコピーです。
use strict;
use warnings;
use WebService::Simple;

my $api_key  = "your_api_key";
my $room_id  = "hO4SmQWTdJ4"; # http://www.lingr.com/room/hO4SmQWTdJ4
my $nickname = "lingr.pl";
my $message  = $ARGV[0] || "Hello, World.";

my $lingr = WebService::Simple->new(
    base_url => 'http://www.lingr.com/',
    param    => { api_key => $api_key, format => 'xml' }
);

# create session, get session
my $response;
$response = $lingr->get( {}, { path => '/api/session/create' } );
my $session = $response->parse_xml->{session};

# enter the room, get ticket
$response = $lingr->get(
    {
        session  => $session,
        id       => $room_id,
        nickname => $nickname,
    },
    { path => '/api/room/enter' }
);
my $ticket = $response->parse_xml->{ticket};

# say 'Hello, World'
$response = $lingr->get(
    {
        session => $session,
        ticket  => $ticket,
        message => $message,
    },
    { path => '/api/room/say' }
);
my $status = $response->parse_xml->{status};

# destroy session
$lingr->get( { session => $session, }, { path => '/api/session/destroy' } );
base_urlが変る事で毎回WebService::Simpleを作らなくても良くなり、スッキリした感じです。
ゆーすけべーさんには、案だけ採用して貰いたい。(私の下手なコードは使わないで下さいという意味)
Posted at by




tumblrは閲覧一方な私ですが、うざったく感じていたのが画像を大きくして見たい場合や動画を再生したい場合に、いちいちマウスに手を伸ばさなきゃいけない事。
minibuffer入れてる人なんかはキーボードだけで操作したい派のはず。イライラの種ですね。

そんなめんどくさがりやさんの貴方にお勧めしたいのがコレ!
ENTERキーだけで画像を拡大縮小出来ます。また動画の再生停止も行えます。
音声の再生停止は出来ません!!!!><
動作にはminibufferが必要です。
j,kで移動してENTERで見る!って感じです。
ソースは汚いので見てはいけません。

動かなかったら「pray on tumblr」って事で...
playontumblr.user.js
Posted at by



2008/04/02


ただいま、JavaとCOMを使ったお仕事をしているんですが、どうも正常に動かないものがありまして、困っておりました。

どんな現象かといいますと、Microsoft AgentをAgentObjectsから起動した時に、ConnectedというBOOL型のプロパティをTRUEに設定してやるのですが、どうやらJavaから渡していた値trueが、VARIANT変換により1になっていた事が原因していました。

まぁ、1だしfalseじゃないなら動作するのが一般的なアプリでしょ...と思ってましたが、どっこいMSAgent殿は例外も出さずにConnectedをFALSEに設定して下さいました。

実際、COM上ではBOOLはVARIANT_TRUEもしくはVARIANT_FALSEを設定するのがお決まりで、VARIANT_TRUEは ((VARIANT_BOOL)0xffff)
と宣言されていました。C++上でのtrueとは値が異なる訳です。

まぁ、それは自分のミスとして...

きっとMSAgentのConnectedプロパティ処理では
if (VARIANT_TRUE == arg) {
  // Connected 設定時の処理...
}
というコードが書かれているのだと思うのですが、これは if (VARIANT_FALSE != arg) {
  // Connected 設定時の処理...
}
って書くべきなんじゃないかな...

ちなみに、Visual Studioのウォッチエリアに追加した、1の値を持つ問題のVARIANT変数は、VT_BOOL:Trueと表示されていました。
Posted at by



2008/04/01


以前、miyagawa氏作のXML::Atom::ServerをベースとしてXSをなるべく使わない「XML::Atom::Server::Lite」というのを作ってCodeReposに放置していたのですが、そろそろ放置しすぎな感もあったので発作的にエントリ。
「XML::Atom::Server::Lite」は「XML::Atom::Server」と構造をほぼ同じにしてあり、中で使用しているXML::Atomを自前のXSを使わないクラスで置き換えてしまおうというモジュールです。
LibXMLが使えないサーバには、もしかしたら有用かもしれません。サーバ用途だけならばLWPも使いませんし、簡単に低コストでAtom::Serverが実現出来ると思います。
「Digest::SHA」も「Digest::SHA::PurePerl」で置き換えています。
今日はこの「XML::Atom::Server::Lite」を使ってblosxomのAtom::Serverを作ってみました。
ソースは以下の通り。
#!/usr/bin/perl

package Blosxom::AtomPP;
use strict;
use base qw( XML::Atom::Server::Lite );
use File::Find;
use File::stat;

if (-e "/path/to/blosxom/config/file/config.cgi") {
    package blosxom;
    require '/path/to/blosxom/config/file/config.cgi';
}

my %Passwords = (
    yourname => 'yourpassword',
);
my $postsWanted = 10;

sub password_for_user {
    my $server = shift;
    my($username) = @_;
    $Passwords{$username};
}

sub getFilenameFromPostId {
  my $postid = shift @_;
  unless ( $postid =~ /$blosxom::datadir/ ) {
    if ( $postid =~ m!^/! ) {
      #Post ID now has a preceeding /
      $postid = $blosxom::datadir . $postid;
    } else {
      $postid = "$blosxom::datadir/$postid";
    }
  }
  return $postid;
}

sub getRandomFilename {
  my $time = time;
  $time .= int( rand(10) );
  $time .= int( rand(10) );
  return $time;
}

sub decode_html {
    my $str = shift @_;
    $str =~ s/&#([0-9]+);/chr($1)/ge;
    $str =~ s/&#[xX]([0-9A-Fa-f]+);/chr(hex $1)/ge;
    $str =~ s!\x0D|\x0A!<br />\n!g;
    $str =~ s/\&gt;/>/g;
    $str =~ s/\&lt;/</g;
    $str =~ s/\&quot;/"/g;
    $str =~ s/\&amp;/\&/g;
    return $str;
}

sub getPost {
    my $filename  = shift @_;
    my $start     = shift @_ || $blosxom::datadir;
    my $extension = shift @_ || $blosxom::file_extension;

    if ( -e $filename ) {
        open POST, "$filename";
        my @post = <POST>;
        close POST;
        my %struct;
        $struct{'postid'}      = $filename;
        $struct{'dateCreated'} = File::stat::stat($filename)->mtime;
        my $title              = shift @post;
        $title                 =~ s!\x0D|\x0A!!g;
        $struct{'title'}       = $title;
        foreach (@post) {
            my $line = $_;
            if ( !$struct{'subject'} && $line =~ /^meta-tags: (.*)$/ ) {
                $line = $1;
                $line =~ s!\x0D|\x0A!!g;
                my @subjects = split(/\s*,\s*/, $line);
                $struct{'subject'} = \@subjects;
            } else {
                $struct{'description'} .= $line;
            }
        }
        my @cats;
        $filename =~ s/$start(\/.*)\/.*$extension$/$1/ or $filename = "/";
        push @cats, $filename;
        $struct{'categories'} = \@cats;
        return \%struct;
    }
    else {
        return 0;
    }
}

sub newPost {
    my $struct    = shift @_;
    my $start     = shift @_ || $blosxom::datadir;
    my $extension = shift @_ || $blosxom::file_extension;

    my $filename;

    if ( defined( $struct->{'postid'} ) ) {
        $filename = getFilenameFromPostId( $struct->{'postid'} );
    }
    else {
        $filename = lc( $struct->{'title'} );
        $filename =~ s/\W+/_/g;
        $filename =~ s/_+$//;
        $filename = getRandomFilename() unless $filename =~ m/[a-z]/;
        if ( $struct->{'categories'} ) {
            $filename =
              "$start/$struct->{'categories'}[0]/$filename.$extension";
        }
        else {
            $filename = "$start/$filename.$extension";
        }
    }

    chomp $struct->{'title'};
    chomp $struct->{'description'};

    unless ( -e $filename ) {
        open POST, ">$filename.old" or die "Can't Open File $filename: $!";
        print POST decode_html($struct->{'title'})."\n";
        print POST decode_html($struct->{'description'})."\n";
        close POST;
        my $files = chmod 0664, $filename;
    }
    return $filename;
}

sub getEntry {
    my $filename = shift;
    my $post = getPost( $filename );
    return unless $post;

    my $entry = XML::Atom::Server::Lite::Entry->new;
    my $extension = $blosxom::file_extension;

    $entry->title($post->{title});

    my $url = $blosxom::url.$post->{'postid'};
    $url =~ s/$extension$/htm/;
    $entry->link({
            type=>'text/html',
            rel=>'alternate',
            href=>$url,
    });

    $entry->content({
            mode=>'xml',
            type=>'xhtml',
            body=>$post->{description},
    });

    for my $cat (@{$post->{categories}}) {
        $entry->category({
                term=>$cat,
                label=>$cat,
        });
    }
    $entry->subject(@{$post->{subject}});
    return $entry;
}

sub handle_request {
    my $server = shift;
    my $method = $server->request_method;
    $server->authenticate or return;
    if ($method eq 'POST') {
        return $server->new_post;
    }
    $server->response_code(200);
    $server->response_content_type('application/x.atom+xml');

    my $start       = $blosxom::datadir;
    my $extension   = $blosxom::file_extension;
    my $url = $server->path_info;
    if ($url =~ /$blosxom::file_extension$/) {
         my $entry = getEntry( "$start/$url.old" );
        return $entry->as_xml;
    } else {
        my %posts;
        File::Find::find(
            sub {
                $File::Find::name =~ /$extension$/
                  ? $posts{$File::Find::name} =
                  File::stat::stat($File::Find::name)->mtime
                  : 0;
            },
            $start
        );
        my @postList = sort { $posts{$b} <=> $posts{$a} } keys %posts;
        if ( $#postList > $postsWanted ) {
            @postList = @postList[ 0 .. ( $postsWanted - 1 ) ];
        }
        my $feed = XML::Atom::Server::Lite::Feed->new;
        $feed->title($blosxom::blog_title);
        $feed->link({
            rel => 'self',
            type => 'application/atom+xml',
            title => $blosxom::blog_title,
            href => $server->uri,
        });
        $feed->link({
            rel => 'alternate',
            type => 'text/html',
            title => $blosxom::blog_title,
            href => $server->uri,
        });
        $feed->link({
            rel => 'service.post',
            type => 'application/x.atom+xml',
            title => $blosxom::blog_title,
            href => $server->uri,
        });
        for my $filename (@postList) {
            my $entry = getEntry( $filename );
            $feed->add_entry( $entry ) if $entry;
        }
        return $feed->as_xml;
    }
}

my %esc = (
    "\a" => "\\a",
    "\b" => "\\b",
    "\t" => "\\t",
    "\n" => "\\n",
    "\f" => "\\f",
    "\r" => "\\r",
    "\e" => "\\e",
);

sub qquote {
  local($_) = shift;
  s/([\\\"\@\$])/\\$1/g;
  my $bytes; { use bytes; $bytes = length }
  s/([^\x00-\x7f])/'\x{'.sprintf("%x",ord($1)).'}'/ge if $bytes > length;
  return qq("$_") unless
    /[^ !"\#\$%&'()*+,\-.\/0-9:;<=>?\@A-Z[\\\]^_`a-z{|}~]/;  # fast exit

  my $high = shift || "";
  s/([\a\b\t\n\f\r\e])/$esc{$1}/g;

  if (ord('^')==94)  { # ascii
    # no need for 3 digits in escape for these
    s/([\0-\037])(?!\d)/'\\'.sprintf('%o',ord($1))/eg;
    s/([\0-\037\177])/'\\'.sprintf('%03o',ord($1))/eg;
    # all but last branch below not supported --BEHAVIOR SUBJECT TO CHANGE--
    if ($high eq "iso8859") {
      s/([\200-\240])/'\\'.sprintf('%o',ord($1))/eg;
    } elsif ($high eq "utf8") {
#     use utf8;
#     $str =~ s/([^\040-\176])/sprintf "\\x{%04x}", ord($1)/ge;
    } elsif ($high eq "8bit") {
        # leave it as it is
    } else {
      s/([\200-\377])/'\\'.sprintf('%03o',ord($1))/eg;
      s/([^\040-\176])/sprintf "\\x{%04x}", ord($1)/ge;
    }
  }
  else { # ebcdic
      s{([^ !"\#\$%&'()*+,\-.\/0-9:;<=>?\@A-Z[\\\]^_`a-z{|}~])(?!\d)}
       {my $v = ord($1); '\\'.sprintf(($v <= 037 ? '%o' : '%03o'), $v)}eg;
      s{([^ !"\#\$%&'()*+,\-.\/0-9:;<=>?\@A-Z[\\\]^_`a-z{|}~])}
       {'\\'.sprintf('%03o',ord($1))}eg;
  }

  return qq("$_");
}

sub get_content_node {
    my $tree = shift;
    if ($tree->{children} && (@{$tree->{children}} eq 1)
            && $tree->{children}->[0]->{name} eq 'content') {
        $tree = $tree->{children}->[0];
        for my $item (@{$tree->{children}}) {
            if ($item->{name} eq 'div' && $item->{attributes}
                && $item->{attributes}->{xmlns} =~ 'http://www.w3.org/1999/xhtml') {
                $tree = $item;
                for my $nobr (@{$tree->{children}}) {
                    return $nobr if $nobr->{name} ne 'br' && $nobr->{type} eq 'tag';
                }
                last;
            }
        }
    }
    $tree;
}

sub to_xml {
    my $tree = shift;
    my $xml;
    if ($tree->{name}) {
        $xml .= "<".$tree->{name};
        for my $attr (keys %{$tree->{attributes}}) {
            $xml .= " $attr=".qquote($tree->{attributes}->{$attr});
        }
    } elsif ($tree->{content}) {
        $xml .= $tree->{content};
    }
    if ($tree->{children}) {
        $xml .= ">" if $tree->{name};
        foreach my $item (@{$tree->{children}}) {
            $xml .= to_xml($item);
        }
        $xml .= "</".$tree->{name}.">" if $tree->{name};
    } elsif ($tree->{name}) {
        $xml .= "/>";
    }
    $xml;
}
sub new_post {
    my $server = shift;
    my $entry = $server->atom_body or return;

    my $struct = {};

    my $url = $server->path_info || $entry->link;
    $url =~ s/htm$/$blosxom::file_extension/;
    $struct->{postid} = $url;
    $struct->{title} = $entry->title;
    my $tree_parser = XML::Parser::Lite::Tree::instance();
    my $tree = $tree_parser->parse( $entry->content->body );
    my $data = to_xml(get_content_node($tree));
    $struct->{description} = $data;
    $struct->{categories} = $entry->{category};

    my $filename = newPost( $struct );
    my $created = getEntry( $filename );
    $server->response_code(201);
    $server->response_content_type('application/x.atom+xml');
    $server->response_header(Location => $server->uri.$struct->{postid});
    return $created;
}

package main;
my $server = Blosxom::AtomPP->new;
$server->run;
厳密な「service.post」や「alternate」ではありませんが「XML::Atom::Server::Lite」の検証としてはこの程度で十分かなと思ってます。ポストも出来ます(認証はWSSE)。
Zoundry Ravenというブログツールでは動作を確認しました。
zoundry-raven

おそらくですが
  • 「XML::Atom::Server::Lite」を「XML::Atom::Server」
  • 「XML::Atom::Server::Lite::Feed」を「XML::Atom::Feed」に
  • 「XML::Atom::Server::Lite::Entry」を「XML::Atom::Entry」に
戻せば、そのまま動くかと思います。
なお、「XML::Atom::Server::Lite」はSOAPには対応していません。馬力のある方は適当に弄って下さい。

これでblosxomにXMLRPCとAtomのインタフェースが揃った事になるのかな?
Posted at by




今すぐこのページ javascript:void(function(n){m=/profile_s\.gif$/;for(i=0;i<n.length;i++){if(m.test(n[i].src)){n[i].src='http://mattn.kaoriya.net/images/unk.gif'}}})(document.getElementsByTagName('img'))
するんだ!
Posted at by



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によるプラグイン機能が実現出来ます。
意外と少ないコードで実装出来るので皆さん昔に作ったアプリケーション等を拡張して見られてはどうでしょうか。

VisualC++プログラマのためのCOM入門: はじめるWindowsシステムプログラミング VisualC++プログラマのためのCOM入門: はじめるWindowsシステムプログラミング
豊田 孝
翔泳社 単行本 / ¥1,172 (1999年06月01日)
 
発送可能時間:

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 と書けば
まるごとPerl! Vol.1 まるごとPerl! Vol.1
小飼 弾, 宮川 達彦, 伊藤 直也, 川合 孝典, 水野 貴明
インプレス 大型本 / ¥41 (2006年08月24日)
 
発送可能時間:


となるし
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>";
ちなみに、先日図書館で見つけた本で面白いの見つけた。
子供向け絵本らしいが、ちょっと笑った。
サトシくんとめんたくん (cub label) サトシくんとめんたくん (cub label)
デハラ ユキノリ
長崎出版 大型本 / ¥1,650 (2007年08月01日)
 
発送可能時間:

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



2008/02/26


まぁ、ぶっちゃけ(なくても)「はてブちゃんねる」のパクリなんですが...
単純にwith_friendsのRSSをGoogle Ajax Feed API使ってパースしてるだけです。
コードはこんな感じ。
<script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAAS_2fKEdj-fsDOrnYqd4nthQCjALL2TOLFFIzEhjgebS4V6SjnhTxtlSoL0NJ-Fa6B7tsqLuiiPI9Pg"></script>
<style type="text/css">
.twitter-2ch {
    background-color: #efefef;
    width: 400px;
    color: #000000;
    font-family: 'MS PGothic',sans-serif;
    padding: 1.0em;
}
#twitter_2ch_container a:visited {
    color: #660099;
}
#twitter_2ch_container a:active {
    color: #ff0000;
}
.twitter-2ch-title {
    color: red;
}
</style>
<script type="text/javascript"><!--
function twitter_2ch() {
  var container = document.getElementById("twitter_2ch_container");
  container.innerHTML = "";
  var img = document.createElement('img');
  img.src='http://mattn.kaoriya.net/images/ajax-loader.gif';
  container.appendChild(img);

  var twitterId = document.getElementById("twitterId").value;
  var feed = new google.feeds.Feed('http://twitter.com/statuses/friends_timeline/' + twitterId + '.rss');
  feed.setNumEntries(10);
  setTimeout(function() {
    feed.load(function(result) {
      if (!result.error) {
        var html = "";
        for (var i = 0; i < result.feed.entries.length; i++) {
          var entry = result.feed.entries[i];
          var link = entry["link"];
          var pubDate = new Date(entry["publishedDate"]).toLocaleString();
          var content = entry["content"];
          var author = content.replace(/:.+$/, '');
          var status = content.replace(/^[^:]+: /, '');
        html += (i+1) + ':<a href="' + link + '">以下、名無しにかわりましてfollowerがお送りします。</a>:' + pubDate + ' ID:' + author + '<br />';
        html += '<blockquote>' + status + '</blockquote>';
        }
        container.innerHTML = html;
      }
    });
  }, 100);
}
google.load("feeds", "1");
--></script>
<div class="twitter-2ch">
<span class="twitter-2ch-title">今何してる?</span>
twitter id:<input type="text" id="twitterId" value="mattn_jp"/>
<input type="button" onclick="twitter_2ch();" value="twitter→2ch"/><br />
<br />
<div id="twitter_2ch_container"></div>
</div>
で、実行画面は以下の通り

続きを読む...

Posted at by




ネットユーザ個々のWeb情報をアカウント個々に共有出来るサービスfooo.nameが誕生しました。
fooo.name!!!
もちろんAPIも用意されており、指定アカウントが保持しているサブアカウント情報やフィード情報も、XML,JSON,RSS,ATOM等といったフォーマットで取得する事が出来ます。
今日は、このfooo.nameからアカウントが保持する全フィードを取得し、そのコンテンツから任意の文字列を検索する事で
気になるアノ人の、気になるアノ言及を検索するWebアプリ
を作ってみました。
まず、fooo.nameのAPIで、アカウントに対するフィード一覧を取得します。
私mattnであれば
http://fooo.name/accounts/mattn/feeds
となります。フォーマット指定が出来ますので、「?format=json」を指定して、jsonオブジェクトを取得します。これをGoogle Ajax Feed APIを使用してコンテンツ取得し、そのコンテンツ内から任意の文字列を検索します。
なんだか最近、ライブラリが豊富で色んな事が簡単に出来てしまいますね。

以下、ソースと動作例です。

続きを読む...

Posted at by




vimのオライリー本が出るようです。

Learning the vi and Vim Editors: Text Processing at Maximum Speed and Power Learning the vi and Vim Editors: Text Processing at Maximum Speed and Power
Robbins, Arnold, Hannah, Elbert, Lamb, Linda
O'Reilly Media ペーパーバック / ¥5,875 (2008年07月29日)
 
発送可能時間:


これまでに私が買ったvi/vim本は

viデスクトップリファレンス viデスクトップリファレンス
Arnold Robbins, 日本ルーセント・テクノロジー株式会社
オライリー・ジャパン 単行本 / ¥179 (1999年06月15日)
 
発送可能時間:


vi (Linux magazine books 2) vi (Linux magazine books 2)
金光 雅夫
アスキー 単行本 / ¥1,177 (2000年09月01日)
 
発送可能時間:


Vi(ブイアイ) IMprovedーVim(ブイアイエム)完全バイブル Vi(ブイアイ) IMprovedーVim(ブイアイエム)完全バイブル
Steve Oualline, 高橋 則利
技術評論社 単行本 / ¥1,050 (2004年05月01日)
 
発送可能時間:


くらいかな。
誰か翻訳してくれ。

素のviって1冊もあれば基本は網羅出来てしまうんで、vim本には基本部分(viコンパチな部分)って書かなくてもいいよ。
特にvi/vimの歴史とか、もういらんし。
Posted at by



2008/02/25


私の場合、xargsの引数がオーバーする程のファイルを扱う時は、少し怖いので一度別のディレクトリに移動してから削除するなりをやってます。
また、数十個の場合ならばfindだけでやってしまいますね。その方が思考が止まらないし...。
ただ、findとxargsの組み合わせで結構多いのが
  • さらにその中から間引きたい
  • 実行は並び替えた結果で行いたい
といった場合が少なからずあるのです。
例えばYYYYMMDD等といった日付形式のファイル名で散乱しているファイルを日付通りに処理したい。
と言った場合、やはりsortやgrepのお世話になるのが楽ですよね。
sortやgrepを使う場合、findで-print0した結果ではフィルタフィルタ実行する事が出来ません。
こんな場合には、findでは-print0せずに find . -name "*.txt" | grep '/[0-9]¥{8¥}¥.txt' | sort -n | tr ¥¥n ¥¥0 | xargs -0 ... といった感じにxargsに渡す寸前でtrを使って改行コードを¥0に置き換えてやる。

もう少し手間になってきたらawkとか使うけど、ちょっとしたものならこれでもOK。例が良く無いか...
Posted at by



2008/02/22


最近は、Shift_JIS(Windowsでいうならcp932)には2バイト目に「¥」(0x5C)が混じった文字がある事すら知らない人が居るようで...
すこしカルチャーショックを受けたオジサンmattnです。
職場でUNIXからWindowsに移植したソースコードでコンパイルが通らない!と悩んでいらっしゃる。
そしてその原因がオリジナルを書いた人のコードが悪いと...
まぁ確かにソースコード内に日本語書いてた人が悪いのかもしれませんが...
今日はみんな知ってそうな話。知らなかったら知っといてねくらいの話。

そんなに珍しい話じゃないんですよね。ソースコード内に埋め込みの日本語って。まぁ理由はポータビリティだったり、ソースコードを簡潔に書く為だったり、単純に面倒臭かっただけだったりと(爆)...

昔は結構あったんですよ。ソースコードがeuc-JPなソースとか。

ただそれは、昔は普通にあった話で開発者なら知っておきたい事かな。
まぁここでボヤいても、その担当者には伝わらないんだろうけど。

euc-JPとは違い、Shift_JISには後続するバイトとして0x5Cが来る事があります。
例えば、「ソ」「表」「噂」「貼」など。あと「―」(DASH)もそうですね。
ソースコードの中で罫線などを書く場合によく埋め込みでDASHが書かれたソースコードってのもありました。

で、これが何故問題になるか。もうわかりますね。
C言語等のソースコードでは「¥」(0x5C)はエスケープ文字のリーダとして働くからです。
コンパイラは通常、そのソースがeuc-JPなのかShift_JISなのかUTF-8なのか知りません。javac(gcj)等では「--encoding」なんてオプションで入力ソースのエンコーディングを指定出来たりもしますが、通常のCコンパイラではそうは行きません。
Microsoft Visual C++(日本語版)のコンパイラはShift_JIS(もしくはUnicode)を特例として許しているだけです。
ソースコードは世界の誰がコンパイルしても同じモジュールを作ることが出来るべきであって、日本製のコンパイラでしかビルド出来ないソースってのは異端児もいいところかと。
まぁソースコードに生マルチバイト書くなんて...ってのが本筋ですが。

今日は、古き良き(?)時代のジョークプログラムをWindowsに移植する時の注意点を示してみます。

昔、UNIXで「ls」を「sl」とタイプミスした時に、端末上にSLが突っ走るジョークプログラムがありました。
私も気に入って入れたりもしました。
「sl」も気に入っていたのですが、改変版の「quit」がさらに好きでした。
さすがにshellをsqlplusなんかと間違う事は無かったので本気でshellに「quit」と打ち込むことは無かったですが、「もう来ねぇよ!」と端末を走るAAが気が和んで楽しかったです。
で、そのquitですが私が確認した所
福地さんののページにある
ショートショートプログラムの部屋
にバージョン1.2aとして置いてありました。
さっそくダウンロードし解凍。
まずeuc-JPで書かれているソースをShift_JISに変換するのですが、ここで問題が起きます。
上でも述べた様に、Shift_JISには後続するバイトに「¥」(0x5C)を持つ物があるのですが、このquitのソースに記述されている「―」(DASH)は、Shift_JISで表すと「0x81 0x5C」というバイト列。つまりうかつに変換してしまうとC言語のエスケープ文字を自ら作ってしまう事になるのです。
案の定、以下の様にnkfで変換したソースはそのままコンパイルする事が出来ません。 C:¥temp¥quit-1.2a>nkf -g *
Makefile:ASCII (LF)
quit.1:EUC-JP (LF)
quit.c:EUC-JP (LF)
quit.h:EUC-JP (LF)
quit.txt:ASCII (LF)
README:EUC-JP (LF)

C:¥temp¥quit-1.2a>mkdir tmp

C:¥temp¥quit-1.2a>copy quit.c + quit.h tmp
quit.c
quit.h
        1 個のファイルをコピーしました。

C:¥temp¥quit-1.2a>nkf -EsX quit.c > sjis_quit.c

C:¥temp¥quit-1.2a>nkf -EsX quit.h > sjis_quit.h

C:¥temp¥quit-1.2a>del quit.c quit.h

C:¥temp¥quit-1.2a>rename sjis_quit.* quit.*

C:¥temp¥quit-1.2a>mingw32-make CC=gcc CFLAGS="-O -DJAPANESE=1"
gcc -O -DJAPANESE=1 -o quit quit.c -lncurses
quit.c:47:49: warning: unknown escape sequence '¥|'
quit.c:47:49: warning: unknown escape sequence '¥|'
quit.c:47:49: warning: unknown escape sequence: '¥201'
quit.c:47:49: warning: unknown escape sequence '¥|'
quit.c:238: error: stray '¥26' in program
quit.c:238:2: warning: no newline at end of file
mingw32-make: *** [quit] Error 1
※ギコ猫版をビルドするには「-DJAPANESE=1」が必要です。
※コンパイルはMinGWで行っています。(pdcursesも必要)

で、これを修正するには「0x5C」が後続する文字の後に「¥」を付けてコンパイラがエスケープ開始文字と混同しないように教えてあげます。
例えば「―」(DASH)であれば「―¥」となります。
コンパイル結果から、一つずつ直していっても良いのですが、実はこれコンパイルエラーとなっていない場合もあります。
例えば「―t」という表示があった場合、コンパイラは変な1文字と「¥t」(タブ文字)があるかの様に振るいます。
実際の例で言えば #include <stdio.h>
#include <string.h>

    int
main(int argc, char* argv[]) {
    const char* s = "―t";
    printf("'%s' = %d\n", s, strlen(s));
    return 0;
}
のソース(Shift_JIS保存)は、Microsoft Visual C++では「―」の2バイトと「t」の1バイトで計3バイトという結果が返りますがMinGWでは「0x81」の1バイトと「¥t」の1バイトで計2バイトの結果となります。

まぁ、MSVCでコンパイルするからいいや...という方はこのままでも良いのですが、今回扱うquitはUNIX流のソース。ヘッダに「unistd.h」もincludeしてますからそのままのソースコードではビルド出来ません。
mingwならば例えば以下の様なフィルタプログラムを使うのが良いでしょう。
#include <stdio.h>
#include <string.h>

    int
main(argc, argv)
    int    argc;
    char   **argv;
{
    char buffer[BUFSIZ];
    char *p;

    while (fgets(buffer, BUFSIZ, stdin) != NULL) {
        for (p = buffer; *p != 0; p++) {
            if (*p & 0x80) {
                putchar(*p++);
                if (*p == '\\') putchar(*p);
            }
            putchar(*p);
        }
    }
    return 0;
}
このソース、実はvimのpoディレクトリにあるsjiscorr.cからパクってます。
(とは言ってもsjiscorr.cも元はと言えば私がbram氏に送ったソースですが...)

これを C:¥temp¥quit-1.2a>type quit.c | sjiscorr > esc_quit.c

C:¥temp¥quit-1.2a>type quit.h | sjiscorr > esc_quit.h

C:¥temp¥quit-1.2a>del quit.c quit.h

C:¥temp¥quit-1.2a>rename esc_quit.* quit.*
の様にすればOK。一連の流れを一回でやるならば C:\temp\quit-1.2a>nkf -EsX quit.c | sjiscorr > sjis_quit.c

C:\temp\quit-1.2a>nkf -EsX quit.h | sjiscorr > sjis_quit.h

C:\temp\quit-1.2a>del quit.c quit.h

C:\temp\quit-1.2a>rename sjis_quit.* quit.*
ってやった方が速いですね。

あとはビルド。GNUWin32にあるPDCursesのページにある「Developer files」、「Binaries」をダウンロードしてMinGW環境に入れた後、Makefile内にある「-lncurses」を「-lcurses」に修正し
C:\temp\quit-1.2a>mingw32-make CC=gcc CFLAGS="-O -DJAPANESE=1"
とすれば出来上がり。
かと思いきや、usleepがリンクエラー。こればっかりは仕方ありませんね。
--- quit.c.orig Fri Feb 22 12:14:24 2008
+++ quit.c  Fri Feb 22 12:14:53 2008
@@ -6,6 +6,10 @@
 #include <ncurses.h>
 #include <signal.h>
 #include <unistd.h>
+#ifdef _WIN32
+# include <windows.h>
+# define usleep(x) Sleep(x/1000)
+#endif
 
 #include "quit.h"
 
こんなパッチでusleepをSleep(1000分の1)で誤魔化してビルド。
見事以下の様にquitがネイティブ実行出来る様になりましたとさ。
※但しcurses2.dllは実行PATH環境に必要

quit-win32

で、何の話でしたっけ...

追記
いわたさんからご意見頂きました。
いわた @mattn_jp -finput-charset=cp932 -fexec-charset=cp932 への言及もおながいします。
って事でコンパイルを、いわたさんの言う様に「--finput-charset」指定でする事も重要ですね。特に「¥uXXXX」な文字列や「L"XXX"」なんかは上の方法では変換出来ませんしね。
また最近のUNIX環境ではオリジナルソースのまま「--finput-charset=euc-JP」、「--fexec-charset=utf-8」指定で(setlocaleもいるかな?)コンパイルする事が出来ますね。

今回の上の方は他の(MSVC以外の)Windows用コンパイラで有用だ...って事で。
Posted at by




はてなスクリーンショットに微笑むblosxomプラグイン作りました。
普段は微笑みませんが、はてなスクリーンショットに「ハイ!ポーズ!」と言われるとスマイルを振りまくのです。

以下証拠写真
はてなスクリーンショット - http://mattn.kaoriya.net
で、blosxomプラグインのソース package hatenascreenshot;
use strict;

use vars qw($smile);
my $photo = '<img src="お好きな画像">';

sub start {
  $smile = $photo if ($ENV{'HTTP_USER_AGENT'} =~ /HatenaScreenshot/);
}

1;
あとはフレーバに$hatenascreenshot::smileを入れるだけ。
お好きな画像で微笑んでみてはどうでしょうか?

決してウンコとかは辞めましょう。
※まぁネタですから...
Posted at by



2008/02/18


実はこのサイトは昔、私専用のローカル環境に用意した非公開なウェブサイトでした。
そして、そのサイトは今と同じくblosxomというブログツールで構築されていました。
私はその頃、今の様にvimで直接HTMLを書くのではなく、emacs環境から生まれた
howm: 一人お手軽 Wiki もどき
というメモ取り環境のvimクローン、「howm_mode.vim」を使い、その書き込みをblosxomに食わせる(拡張子をtxtでなくhowmにする)という方法で使用していました。

最近はhowm_mode.vimを使わなくなったのですが、先日howm_mode.vimを改造しておられる方を見つけ、ひっそりとウォッチしていました。
で、本日どうやら公開された様です。
48 howm-mode.vim (2-25) パッチの公開とそのあてかた
以前からぐずぐず言ってたパッチを公開。ついでに Windows 環境でのパッチのあてかたを書いた。
試しに本体をhowm_vimのサイトから、パッチをパッチ作者殿のページから落とし当ててみました。

始めは動きませんでしたが、少し弄った所すこぶる快適に動き出しました。
日本語での検索や、migemo/cmigemoを使った検索、空白を含んだhowm_dirディレクトリでも問題なく動作しました。
ありがとうございます。またしばらくはメモ取りとしてhowmを使ってみようかなぁ...って思いました。

で、今回修正したパッチはmatchendを使ってディレクトリを掘る部分。
matchendの第2引数は正規表現が入力可能なので、以下の様にescapeしました。 diff -cr howm_vim.orig\plugin\howm-mode.vim howm_vim\plugin\howm-mode.vim
*** howm_vim.orig\plugin\howm-mode.vim  Mon Feb 18 16:41:24 2008
--- howm_vim\plugin\howm-mode.vim   Mon Feb 18 16:15:40 2008
***************
*** 440,446 ****
  
    if editing != filepath
      let howm_dir = s:HowmExpand(g:howm_dir)
!     let idx = matchend(filepath, howm_dir)
      if idx != -1
        " 存在しないディレクトリ下のファイルを指定されたら,ディレクトリを作る
        call s:MakeDirectory(substitute(strpart(filepath, idx), '[^/]*$', '', ''))
--- 440,447 ----
  
    if editing != filepath
      let howm_dir = s:HowmExpand(g:howm_dir)
!     let g:hoge = howm_dir
!     let idx = matchend(filepath, escape(howm_dir, ' \~'))
      if idx != -1
        " 存在しないディレクトリ下のファイルを指定されたら,ディレクトリを作る
        call s:MakeDirectory(substitute(strpart(filepath, idx), '[^/]*$', '', ''))
注意:このパッチは、オリジナルからパッチ作者殿のパッチを当てた物からの差分です。

皆さんもメモ取り環境としてhowm_mode.vimを使ってみられてはどうでしょうか。
ちなみに、以前私が使っていた頃のtips。私はmigemoでなくcmigemoを使っているので
let howm_migemoprg='cmigemo'
let howm_migemoopt=' -q -d "'.globpath(&runtimepath,'dict/migemo-dict').'" | nkf -Ew'
としています。
また検索で全部出て欲しい場合もあったので、「¥,A」で検索かつ<c-n>,<c-p>で移動、さらに「o」で開ける様 nmap <silent> <leader>,A <leader>,g^=.*<cr>
autocmd BufEnter howm\ Search\ result nmap <buffer> <c-p> kp
autocmd BufEnter howm\ Search\ result nmap <buffer> <c-n> jp
autocmd BufEnter howm\ Search\ result nmap <buffer> o :exec "sp " . b:file{line('.')}<cr>
と設定しています。
vimぽいmapleaderや、GNUWin32ツールなんかを組み合わせていますので、howm関連だと全体で let g:howm_mapleader='\'
if has('win32')
  let howm_grepprg='grep'
  let howm_findprg='find'
  let howm_html2txtcmd='c:/cygwin/bin/w3m -dump -cols 78 %s'
endif
let howm_migemoprg='cmigemo'
let howm_migemoopt=' -q -d "'.globpath(&runtimepath,'dict/migemo-dict').'" | nkf -Ew'
let howm_quotemark='> '
nmap <silent> <leader>,A <leader>,g^=.*<cr>
autocmd BufEnter howm\ Search\ result nmap <buffer> <c-p> kp
autocmd BufEnter howm\ Search\ result nmap <buffer> <c-n> jp
autocmd BufEnter howm\ Search\ result nmap <buffer> o :exec "sp " . b:file{line('.')}<cr>
こんな感じに設定しています。(私はcygwinにはパスを通さない派です)
使い始めると、意外とやみつきになりますよ。

追記
パッチ作成者殿へのリンクが間違っていました。
Posted at by



2008/02/14


なんだかtwitterではハート祭りが繰り広げられている様で...
twitter-heart
どうやら「<3」でハートマークが付くようです。
今日はバレンタインデーって事で、こんなもん作ってみました。
WWW::Mechanize::AutoPagerでfriends全員をハッシュ一覧化し、Net::Twitterで全員にこのハートを送るperlスクリプトを作ってみました。
宜しければどうぞ!
って私はやりませんが...
※ちなみに本気でやるなら、ちゃんとsleep入れて下さいね。

#!/usr/bin/perl

use warnings;
use strict;
use WWW::Mechanize;
use WWW::Mechanize::AutoPager;
use Web::Scraper;
use Net::Twitter;

my $username = 'username';
my $password = 'password';

my $twitter_friends = scraper {
    process 'tr.vcard',
        'friends[]' => scraper {
            process 'td strong a', name => 'TEXT';
        };
    result 'friends';
};

my $u = 'http://twitter.com/friends/';

my $mech = WWW::Mechanize->new(timeout => 10);
$mech->add_header('Accept-Encoding', 'identity');
$mech->get('http://twitter.com/login');
$mech->submit_form(
    form_number => 2,
    fields    => {
        username_or_email  => $username,
        password           => $password,
    },
    button    => 'commit',
);

$mech->autopager->load_siteinfo();
my $friends;
while($u) {
    my $res = $twitter_friends->scrape( $mech->get($u)->content );
    eval { push @$friends, @{$res}; };
    last if ( $@ or !defined($mech->next_link) );
    $u = $mech->next_link;
}

my $twit = Net::Twitter->new( username => $username, password => $password );
foreach my $friend (@$friends) {
    my $name = $friend->{name};
    $result = $twit->update("\@$name <3");
}
って俺、男だ。
Posted at by




Flickr Uploadrがオープンソースで公開された模様です。
Flickr Uploadr: Open Source and Powered by XULRunner (Yahoo! Developer Network blog)
Flickr Uploadrは昔からあったのですが、XULを使ったオープンソースアプリケーションとして公開されました。
ライセンスはGPLv2で、ライセンスに従えば改造して公開する事も出来る様になりました。

ただし日本語ロケールがない。
中身を見るとそんなに量がなかったので、えいや!と翻訳してみました。
mattnが「日本だったらこういう言い回しだよなぁ」っていう感じで意訳しています。間違ってたら教えてください。
以下その翻訳物のインストール方法です。Windowsの場合のみ説明しますが、おそらく他の環境でも同じ様な作業になるかと思います。

続きを読む...

Posted at by



2008/02/13


ShareOnTumblr
「CShellExt::QueryContextMenu」でやれば出来なく無いけどコストがデカイし別のシェルエクステンション混ぜると落ちたりするんですよね。
Gyazowin tumblr for Windowsにファイルのアップロード機能を追加して名前をGyamblr for Windowsに変更 « ZeroMemory
Windowsのコンテキストメニューのコピーとかにcopy to tumblrみたいなのを追加できないかなと思ったんだけど、標準のやつはともかくアプリケーションの右クリックはアプリケーション管轄で制御されていてWindowsシロウト同然の自分には外からいじれなそうだったのであきらめました。
本当は「CShellExt::QueryContextMenu」でやるのがいいんだろうけど、今回はコマンドラインユーザでも使える様にコマンド形式で「送る」に登録する方法にした。

続きを読む...

Posted at by



2008/02/08


まぁ、id:tokuhirom氏が作ったwassr-todo.plがあるんですが...
(いいんです。C言語が書きたいんです!)

でっかく記事いっぱいにコードだけ...


ちなみに、json-cというライブラリとcURLを使っています。
昨日のパッチを使えばVC6でもビルド出来ます。
よろしければどうぞ。

最新ソースはCodeReposのコノ辺におきました。

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <curl/curl.h>
#include <json.h>

#define APP_NAME                   "wassr_todo"

#ifdef _WIN32
# ifndef snprintf
#  define snprintf _snprintf
# endif
#endif

#define TODO_MODIFY_URL "http://api.wassr.jp/todo/"
#define TODO_JSON_EXT ".json"

static char* response_cond = NULL;  /* response condition */
static char* response_mime = NULL;  /* response content-type. ex: "text/html" */
static char* response_data = NULL;  /* response data from server. */
static size_t response_size = 0;    /* response size of data */

static void initialize_http_response() {
    response_cond = NULL;
    response_mime = NULL;
    response_data = NULL;
    response_size = 0;
}

static void terminate_http_response() {
    if (response_cond) free(response_cond);
    if (response_mime) free(response_mime);
    if (response_data) free(response_data);
    response_cond = NULL;
    response_mime = NULL;
    response_data = NULL;
    response_size = 0;
}

static size_t handle_returned_data(char* ptr, size_t size, size_t nmemb, void* stream) {
    if (!response_data)
        response_data = (char*)malloc(size*nmemb);
    else
        response_data = (char*)realloc(response_data, response_size+size*nmemb);
    if (response_data) {
        memcpy(response_data+response_size, ptr, size*nmemb);
        response_size += size*nmemb;
    }
    return size*nmemb;
}

static size_t handle_returned_header(void* ptr, size_t size, size_t nmemb, void* stream) {
    char* header = NULL;

    header = (char*)malloc(size*nmemb + 1);
    memcpy(header, ptr, size*nmemb);
    header[size*nmemb] = 0;
    if (strncmp(header, "Content-Type: ", 14) == 0) {
        char* stop = header + 14;
        stop = strpbrk(header + 14, "\r\n;");
        if (stop) *stop = 0;
        if (response_mime) free(response_mime);
        response_mime = strdup(header + 14);
    }
    if (strncmp(header, "Last-Modified: ", 15) == 0) {
        char* stop = strpbrk(header, "\r\n;");
        if (stop) *stop = 0;
        if (response_cond) free(response_cond);
        response_cond = strdup(header);
    }
    if (strncmp(header, "ETag: ", 6) == 0) {
        char* stop = strpbrk(header, "\r\n;");
        if (stop) *stop = 0;
        if (response_cond) free(response_cond);
        response_cond = strdup(header);
    }
    free(header);
    return size*nmemb;
}

char* get_http_data(char* url, char* user, char* pass) {
    CURLcode res;
    CURL* curl;
    char* ret = NULL;
    int status = 0;
    char auth[512];

    initialize_http_response();

    memset(auth, 0, sizeof(auth));
    snprintf(auth, sizeof(auth)-1, "%s:%s", user, pass);

    curl = curl_easy_init();
    if (!curl) return NULL;
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handle_returned_data);
    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, handle_returned_header);
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, APP_NAME);
    res = curl_easy_perform(curl);
    res = res == CURLE_OK ? curl_easy_getinfo(curl, CURLINFO_HTTP_CODE, &status) : res;
    curl_easy_cleanup(curl);
    if (res == CURLE_OK && status == 200) {
        ret = (char*)malloc(response_size+1);
        memset(ret, 0, response_size+1);
        memcpy(ret, (char*)response_data, response_size);
    }

    terminate_http_response();

    return ret;
}

char* post_http_data(char* url, char* user, char* pass, char* data) {
    CURLcode res;
    CURL* curl;
    char* ret = NULL;
    int status = 0;
    char auth[512];

    initialize_http_response();

    memset(auth, 0, sizeof(auth));
    snprintf(auth, sizeof(auth)-1, "%s:%s", user, pass);

    curl = curl_easy_init();
    if (!curl) return NULL;
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handle_returned_data);
    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, handle_returned_header);
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, APP_NAME);
    curl_easy_setopt(curl, CURLOPT_POST, 1);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (void*)data);
    res = curl_easy_perform(curl);
    res = res == CURLE_OK ? curl_easy_getinfo(curl, CURLINFO_HTTP_CODE, &status) : res;
    curl_easy_cleanup(curl);
    if (res == CURLE_OK && status == 200) {
        ret = (char*)malloc(response_size+1);
        memset(ret, 0, response_size+1);
        memcpy(ret, (char*)response_data, response_size);
    }

    terminate_http_response();

    return ret;
}

char* string_to_utf8_alloc(const char* str) {
    if (!str) return NULL;
#ifdef WIN32
    UINT codePage;
    size_t in_len = strlen(str);
    codePage = GetACP();
    size_t wcssize = MultiByteToWideChar(codePage, 0, str, in_len,  NULL, 0);
    wchar_t* pszStrWC = (wchar_t*)malloc(sizeof(wchar_t)*(wcssize + 1));
    wcssize = MultiByteToWideChar(codePage, 0, str, in_len, pszStrWC, wcssize + 1);
    pszStrWC[wcssize] = '\0';

    codePage = CP_UTF8;
    size_t mbssize = WideCharToMultiByte(codePage, 0, pszStrWC, -1, NULL, 0, NULL, NULL);
    char* pszStrMB = (char*)malloc(mbssize + 1);
    mbssize = WideCharToMultiByte(codePage, 0, pszStrWC, -1, pszStrMB, mbssize, NULL, NULL);
    pszStrMB[mbssize] = '\0';

    free(pszStrWC);

    return pszStrMB;
#else
    return strdup(str);
#endif
}

char* utf8_to_string_alloc(const char* str) {
    if (!str) return NULL;
#ifdef WIN32
    UINT codePage = CP_UTF8;
    const char* ptr = str;
    if (str[0] == (char)0xef && str[1] == (char)0xbb && str[2] == (char)0xbf)
        ptr += 3;
    size_t wcssize = MultiByteToWideChar(codePage, 0, ptr, -1,  NULL, 0);
    wchar_t* pszStrWC = (wchar_t*)malloc(sizeof(wchar_t)*(wcssize + 1));
    wcssize = MultiByteToWideChar(codePage, 0, ptr, -1, pszStrWC, wcssize + 1);
    pszStrWC[wcssize] = '\0';

    codePage = GetACP();
    size_t mbssize = WideCharToMultiByte(codePage, 0, pszStrWC,-1, NULL, 0, NULL, NULL);
    char* pszStrMB = (char*)malloc(mbssize + 1);
    mbssize = WideCharToMultiByte(codePage, 0, pszStrWC, -1, pszStrMB, mbssize, NULL, NULL);
    pszStrMB[mbssize] = '\0';

    free(pszStrWC);

    return pszStrMB;
#else
    return strdup(str);
#endif
}

static char* url_encode_alloc(const char* str) {
    static const int force_encode_all = TRUE;
    const char* hex = "0123456789abcdef";

    char* buf = NULL;
    unsigned char* pbuf = NULL;
    int len = 0;

    if (!str) return NULL;
    len = strlen(str)*3;
    buf = (char*)malloc(len+1);
    memset(buf, 0, len+1);
    pbuf = (unsigned char*)buf;
    while(*str) {
        unsigned char c = (unsigned char)*str;
        if (c == ' ')
            *pbuf++ = '+';
        else if (c & 0x80 || force_encode_all) {
            *pbuf++ = '%';
            *pbuf++ = hex[c >> 4];
            *pbuf++ = hex[c & 0x0f];
        } else
            *pbuf++ = c;
        str++;
    }
    return buf;
}

char* add_todo(char* user, char* pass, char* body) {
    int size;
    char* body_utf8;
    char* body_data;
    char* post;
    char* data;

    body_utf8 = string_to_utf8_alloc(body);
    if (!body_utf8) return NULL;
    body_data = url_encode_alloc(body_utf8);
    free(body_utf8);
    size = 5 + strlen(body_data);
    post = (char*)malloc(size + 1);
    memset(post, 0, size + 1);
    strcpy(post, "body=");
    strcat(post, body_data);
    free(body_data);

    data = post_http_data("http://api.wassr.jp/todo/add.json", user, pass, post);
    free(post);
    return data;
}

char* modify_todo(char* user, char* pass, char* command, char* todo_rid) {
    int size;
    char* url;
    char* post;
    char* data;

    size = 9 + strlen(todo_rid);
    post = (char*)malloc(size + 1);
    memset(post, 0, size + 1);
    strcpy(post, "todo_rid=");
    strcat(post, todo_rid);

    size = strlen(TODO_MODIFY_URL) + strlen(command) + strlen(TODO_JSON_EXT);
    url = (char*)malloc(size + 1);
    memset(url, 0, size + 1);
    strcpy(url, TODO_MODIFY_URL);
    strcat(url, command);
    strcat(url, TODO_JSON_EXT);

    data = post_http_data(url, user, pass, post);
    free(post);
    free(url);
    return data;
}

char* list_todo(char* user, char* pass, int done_fg) {
    char* data;

    if (done_fg)
        data = get_http_data("http://api.wassr.jp/todo/list.json?done_fg=1", user, pass);
    else
        data = get_http_data("http://api.wassr.jp/todo/list.json?done_fg=0", user, pass);
    if (!data) {
        perror("Unknown server response(server down or invalid authenticate?)");
        return NULL;
    }
    return data;
}

int main(int argc, char* argv[]) {
    FILE* fp = NULL;
    char* home = NULL;
    char* user = NULL;
    char* pass = NULL;
    char conf[_MAX_PATH];
    char buff[BUFSIZ];
    char* data = NULL;
    struct json_object* obj = NULL;
    struct json_object* res = NULL;
    int i;

    home = getenv("HOME");
    if (!home) home = getenv("USERPROFILE");
    snprintf(conf, sizeof(conf), "%s/.wassr-todo", home);
    fp = fopen(conf, "rt");
    if (!fp) {
        perror("404 ~/.wassr-todo NOT FOUND");
        goto error;
    }
    while(fgets(buff, sizeof(buff), fp)) {
        char *ptr = strpbrk(buff, "\r\n");
        if (*ptr) *ptr = 0;
        if (!strncmp(buff, "username: ", 10))
            user = strdup(buff + 10);
        if (!strncmp(buff, "password: ", 10))
            pass = strdup(buff + 10);
    }
    fclose(fp);

    if (argc == 1)
        data = list_todo(user, pass, FALSE);
    else {
        if (argc == 2 && !strcmp(argv[1], "list"))
            data = list_todo(user, pass, FALSE);
        else
        if (argc == 2 && !strcmp(argv[1], "listdone"))
            data = list_todo(user, pass, TRUE);
        else
        if (argc == 3 && !strcmp(argv[1], "add"))
            data = add_todo(user, pass, argv[2]);
        else
        if (argc == 3 && (
                !strcmp(argv[1], "delete") ||
                !strcmp(argv[1], "start") ||
                !strcmp(argv[1], "stop") ||
                !strcmp(argv[1], "done")))
            data = modify_todo(user, pass, argv[1], argv[2]);
        else {
            puts("usage: wassr_todo [command] [argument]");
            puts("\t* command");
            puts("\t\tlist\t\tlist todo.");
            puts("\t\tlistdone\tlist todo which is done.");
            puts("\t\tadd [body]\tpost new todo to wassr.");
            puts("\t\tdelete [rid]\tdelete the todo.");
            puts("\t\tstart [rid]\tstart the todo.");
            puts("\t\tstop [rid]\tstop the todo.");
            puts("\t\tdone [rid]\tdone the todo.");
            goto error;
        }
    }

    if (!data) {
        perror("Unknown server response(server down or invalid authenticate?)");
        goto error;
    }
    obj = json_tokener_parse(data);
    free(data);

    if (is_error(obj)) {
        perror("Unknown server response(not json?)");
        goto error;
    }

    if (json_object_is_type(obj, json_type_object)) {
        res = json_object_object_get(obj, "error");
        if (!is_error(res)) {
            char* _res = utf8_to_string_alloc(json_object_get_string(res));
            if (_res) {
                printf("error: %s\n", _res);
                free(_res);
                goto error;
            }
        }
        res = json_object_object_get(obj, "message");
        if (!is_error(res)) {
            char* _res = utf8_to_string_alloc(json_object_get_string(res));
            if (_res) {
                printf("message: %s\n", _res);
                free(_res);
            }
        }
    }

    if (json_object_is_type(obj, json_type_array)) {
        for(i = 0; i < json_object_array_length(obj); i++) {
            struct json_object *item = json_object_array_get_idx(obj, i);
            struct json_object* todo_rid = json_object_object_get(item, "todo_rid");
            struct json_object* body = json_object_object_get(item, "body");
            if (!is_error(todo_rid) && !is_error(body)) {
                char* _todo_rid = utf8_to_string_alloc(json_object_get_string(todo_rid));
                char* _body = utf8_to_string_alloc(json_object_get_string(body));
                printf("TODO-%03d [%s] %s\n", i+1, _todo_rid, _body);
                free(_todo_rid);
                free(_body);
            }
        }
    } else
    if (json_object_is_type(obj, json_type_object)) {
        struct json_object* todo_rid = json_object_object_get(obj, "todo_rid");
        if (!is_error(todo_rid)) {
            char* _todo_rid = utf8_to_string_alloc(json_object_get_string(todo_rid));
            if (_todo_rid) {
                printf("%s\n", _todo_rid);
                free(_todo_rid);
            }
        }
    }
    return 0;

error:
    if (user) free(user);
    if (pass) free(pass);
    return -1;
}
Posted at by



2008/02/07


JSON-Cという、C言語からJSONを扱う為のライブラリを見つけました。
Index of /json-c
C言語って言いながら、実際もともと/TPオプションでCPPビルドしてるし、それJSON-CPPちゃうんかいという気もしなくないですが...。
foreachなんかもあり、結構使い勝手は良さそうです。

ただ、同梱されている物の中には*.vcprojしか入っておらず、Makefileでゴニョゴニョするのが好きな私には絶えられないものであったり...
最近はMakefileを書く習慣って無くなってきたんですかねぇ...
で、VC6でビルドしようと思ったけどエラーがワンサカ。
どうやら
  • キャスト漏れ
  • 変数名に「this」を使用
  • C99の可変個引数マクロを使用
とはっきり言ってVC6を捨てているコードだったりしました。
なにくそーとパッチを書いたので、公開しておきます。
ただ大したパッチでもないので、オフィシャルに送るのはやめときます。
しかもトリッキーな事やってます。
デバッグ用の可変個引数マクロ #define MC_DEBUG(x, ...) mc_debug(x, ##__VA_ARGS__)
#define MC_DEBUG if (0)
みたくして無理やり殺しています。
で、できたパッチは以下の通り。
Index: json_object.c
===================================================================
--- json_object.c   (revision 21)
+++ json_object.c   (working copy)
@@ -28,7 +28,9 @@
   char* strndup(const char* str, size_t n);
 #endif /* !HAVE_STRNDUP */
 
+#if _MSC_VER >= 1400
 #define REFCOUNT_DEBUG 1
+#endif
 
 char *json_number_chars = "0123456789.+-eE";
 char *json_hex_chars = "0123456789abcdef";
@@ -160,7 +162,7 @@
 
 static struct json_object* json_object_new(enum json_type o_type)
 {
-  struct json_object *this = calloc(sizeof(struct json_object), 1);
+  struct json_object *this = (struct json_object*)calloc(sizeof(struct json_object), 1);
   if(!this) return NULL;
   this->o_type = o_type;
   this->_ref_count = 1;
Index: json_tokener.c
===================================================================
--- json_tokener.c  (revision 21)
+++ json_tokener.c  (working copy)
@@ -57,7 +57,7 @@
 
 struct json_tokener* json_tokener_new()
 {
-  struct json_tokener *tok = calloc(1, sizeof(struct json_tokener));
+  struct json_tokener *tok = (struct json_tokener*)calloc(1, sizeof(struct json_tokener));
   tok->pb = printbuf_new();
   json_tokener_reset(tok);
   return tok;
@@ -97,7 +97,7 @@
   tok = json_tokener_new();
   obj = json_tokener_parse_ex(tok, str, -1);
   if(tok->err != json_tokener_success)
-    obj = error_ptr(-tok->err);
+    obj = (struct json_object*)error_ptr(-tok->err);
   json_tokener_free(tok);
   return obj;
 }
Index: linkhash.c
===================================================================
--- linkhash.c  (revision 21)
+++ linkhash.c  (working copy)
@@ -42,7 +42,7 @@
 unsigned long lh_char_hash(void *k)
 {
    unsigned int h = 0;
-   const char* data = k;
+   const char* data = (const char*)k;
  
    while( *data!=0 ) h = h*129 + (unsigned int)(*data++) + LH_PRIME;
 
@@ -62,12 +62,12 @@
    int i;
    struct lh_table *t;
 
-   t = calloc(1, sizeof(struct lh_table));
+   t = (struct lh_table*)calloc(1, sizeof(struct lh_table));
    if(!t) lh_abort("lh_table_new: calloc failed\n");
    t->count = 0;
    t->size = size;
    t->name = name;
-   t->table = calloc(size, sizeof(struct lh_entry));
+   t->table = (struct lh_entry*)calloc(size, sizeof(struct lh_entry));
    if(!t->table) lh_abort("lh_table_new: calloc failed\n");
    t->free_fn = free_fn;
    t->hash_fn = hash_fn;
Index: json_util.c
===================================================================
--- json_util.c (revision 21)
+++ json_util.c (working copy)
@@ -62,11 +62,11 @@
   if((fd = open(filename, O_RDONLY)) < 0) {
     MC_ERROR("json_object_from_file: error reading file %s: %s\n",
         filename, strerror(errno));
-    return error_ptr(-1);
+    return (struct json_object*)error_ptr(-1);
   }
   if(!(pb = printbuf_new())) {
     MC_ERROR("json_object_from_file: printbuf_new failed\n");
-    return error_ptr(-1);
+    return (struct json_object*)error_ptr(-1);
   }
   while((ret = read(fd, buf, JSON_FILE_BUF_SIZE)) > 0) {
     printbuf_memappend(pb, buf, ret);
@@ -76,7 +76,7 @@
     MC_ABORT("json_object_from_file: error reading file %s: %s\n",
         filename, strerror(errno));
     printbuf_free(pb);
-    return error_ptr(-1);
+    return (struct json_object*)error_ptr(-1);
   }
   obj = json_tokener_parse(pb->buf);
   printbuf_free(pb);
Index: debug.h
===================================================================
--- debug.h (revision 21)
+++ debug.h (working copy)
@@ -21,6 +21,7 @@
 extern void mc_error(const char *msg, ...);
 extern void mc_info(const char *msg, ...);
 
+#if _MSC_VER >= 1400
 #ifdef MC_MAINTAINER_MODE
 #define MC_SET_DEBUG(x) mc_set_debug(x)
 #define MC_GET_DEBUG() mc_get_debug()
@@ -38,5 +39,14 @@
 #define MC_ERROR(x, ...) if (0) mc_error(x, ##__VA_ARGS__)
 #define MC_INFO(x, ...) if (0) mc_info(x, ##__VA_ARGS__)
 #endif
+#else
+#define MC_SET_DEBUG(x)
+#define MC_GET_DEBUG()
+#define MC_SET_SYSLOG(x)
+#define MC_ABORT if (0)
+#define MC_DEBUG if (0)
+#define MC_ERROR if (0)
+#define MC_INFO if (0)
+#endif
 
 #endif
Index: arraylist.c
===================================================================
--- arraylist.c (revision 21)
+++ arraylist.c (working copy)
@@ -28,11 +28,11 @@
 {
   struct array_list *this;
 
-  if(!(this = calloc(1, sizeof(struct array_list)))) return NULL;
+  if(!(this = (struct array_list*)calloc(1, sizeof(struct array_list)))) return NULL;
   this->size = ARRAY_LIST_DEFAULT_SIZE;
   this->length = 0;
   this->free_fn = free_fn;
-  if(!(this->array = calloc(sizeof(void*), this->size))) {
+  if(!(this->array = (void**)calloc(sizeof(void*), this->size))) {
     free(this);
     return NULL;
   }
@@ -64,7 +64,7 @@
   if(max < this->size) return 0;
   new_size = max(this->size << 1, max);
   if(!(t = realloc(this->array, new_size*sizeof(void*)))) return -1;
-  this->array = t;
+  this->array = (void**)t;
   (void)memset(this->array + this->size, 0, (new_size-this->size)*sizeof(void*));
   this->size = new_size;
   return 0;
Index: json.h
===================================================================
--- json.h  (revision 21)
+++ json.h  (working copy)
@@ -13,8 +13,10 @@
 #define _json_h_
 
 #ifdef __cplusplus
+#if _MSC_VER >= 1400
 extern "C" {
 #endif
+#endif
 
 #include "bits.h"
 #include "debug.h"
@@ -25,7 +27,9 @@
 #include "json_tokener.h"
 
 #ifdef __cplusplus
+#if _MSC_VER >= 1400
 }
 #endif
+#endif
 
 #endif
Index: printbuf.c
===================================================================
--- printbuf.c  (revision 21)
+++ printbuf.c  (working copy)
@@ -29,10 +29,10 @@
 {
   struct printbuf *p;
 
-  if(!(p = calloc(1, sizeof(struct printbuf)))) return NULL;
+  if(!(p = (struct printbuf*)calloc(1, sizeof(struct printbuf)))) return NULL;
   p->size = 32;
   p->bpos = 0;
-  if(!(p->buf = malloc(p->size))) {
+  if(!(p->buf = (char*)malloc(p->size))) {
     free(p);
     return NULL;
   }
@@ -50,7 +50,7 @@
         "bpos=%d wrsize=%d old_size=%d new_size=%d\n",
         p->bpos, size, p->size, new_size);
 #endif /* PRINTBUF_DEBUG */
-    if(!(t = realloc(p->buf, new_size))) return -1;
+    if(!(t = (char*)realloc(p->buf, new_size))) return -1;
     p->size = new_size;
     p->buf = t;
   }
@@ -70,7 +70,7 @@
 /* CAW: compliant version of vasprintf */
 static int vasprintf(char **buf, const char *fmt, va_list ap)
 {
-#ifndef WIN32
+#if !defined(WIN32) || _MSC_VER < 1400
    static char _T_emptybuffer = '\0';
 #endif /* !defined(WIN32) */
    int chars;
@@ -78,7 +78,7 @@
 
    if(!buf) { return -1; }
 
-#ifdef WIN32
+#if defined(WIN32) && _MSC_VER >= 1400
    chars = _vscprintf(fmt, ap)+1;
 #else /* !defined(WIN32) */
    /* CAW: RAWR! We have to hope to god here that vsnprintf doesn't overwrite
さらにVC6でビルドする為のMakefile CFLAGS = -DWIN32 -Dthis=o_this -O2 -MT -W3 -nologo -TP -EHsc
SRC = arraylist.c json_object.c json_tokener.c json_util.c linkhash.c printbuf.c
OBJ = $(SRC:.c=.obj)

all : json.lib

json.lib : $(OBJ)
    lib /out:json.lib $(OBJ)

hoge.exe : hoge.obj
    cl /Fohoge.exe hoge.obj json.lib

clean :
    del /Q *.obj *.lib
あと、サンプル「hoge.c」 #include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>

#include "json.h"

int main(int argc, char **argv)
{
    struct json_object *obj;
    int i;

    obj = json_tokener_parse("[1,2,3]");
    for(i=0; i < json_object_array_length(obj); i++) {
        struct json_object *a = json_object_array_get_idx(obj, i);
        printf("\t[%d]=%s\n", i, json_object_to_json_string(a));
    }
    obj = json_tokener_parse("{'a':1,'b':2,'c':3}");
    json_object_object_foreach(obj, key, val) {
        printf("\t%s: %s\n", key, json_object_to_json_string(val));
    }

    return 0;
}
「hoge.c」の実行結果は     [0]=1
    [1]=2
    [2]=3
    a: 1
    b: 2
    c: 3
こんな感じ。
うむ。これでgtkwassrにTODO機能が付けれるかも...
Posted at by




あわせて読みたいがRSSとJSONを出されている様です。
試しに使ってみました。
コードはこんな感じ。
<script type="text/javascript"><!--
function awasete_yomitai(data) {
    var d = document;
    var container = d.getElementById('awasete_content');
    while(container.hasChildNodes())
        container.removeChild(container.firstChild);

    var ol = d.createElement('ol');
    for(var n = 0; n < data.length; n++) {
        var li = d.createElement('li');
        var favicon = d.createElement('img');
        favicon.src = data[n].favicon;
        li.appendChild(favicon);
        li.appendChild(d.createTextNode(' '));
        var anchor = d.createElement('a');
        anchor.appendChild(d.createTextNode(data[n].title));
        anchor.href = data[n].url;
        li.appendChild(anchor);
        li.appendChild(d.createTextNode(' '));
        var awasete = d.createElement('a');
        awasete.appendChild(d.createTextNode('[ナビゲーション]'));
        awasete.href = data[n].navigation;
        li.appendChild(awasete);
        li.appendChild(d.createTextNode(' '));
        var more = d.createElement('a');
        more.appendChild(d.createTextNode('[もっと見る]'));
        more.href = data[n].more;
        li.appendChild(more);
        ol.appendChild(li);
    }
    container.appendChild(ol);
}
function awasete_yomitai_do(url) {
    var s = document.createElement('script');
    s.charset = 'utf-8';
    s.src = 'http://api.awasete.com/showjson.phtml?u=' + encodeURIComponent(url);
    document.body.appendChild(s);
}
--></script>
<a href="javascript:awasete_yomitai_do('http://mattn.kaoriya.net');void 0">あわせて読みたい</a>
<div id="awasete_content"></div>
実行確認。
[あわせて読みたい]を表示

callback指定出来ないのが少し悲しい。
Posted at by



2008/02/06


たぶんこんなんでいけるはず。
Control-C - ロックスターになりたい
あと日本語文字化けする。VBからSJISで送ってサーバでutf8で読んでるとかだろうか。
パッチ
diff -u winsource.orig/modUrlEncode.bas winsource/modUrlEncode.bas
--- winsource.orig/modUrlEncode.bas Tue Jan 08 02:14:04 2008
+++ winsource/modUrlEncode.bas  Wed Feb 06 18:12:35 2008
@@ -1,6 +1,57 @@
 Attribute VB_Name = "modUrlEncode"
 Option Explicit
 
+Private Declare Function WideCharToMultiByte Lib "kernel32" ( _
+    ByVal CodePage As Long, _
+    ByVal dwFlags As Long, _
+    ByVal lpWideCharStr As Long, _
+    ByVal cchWideChar As Long, _
+    ByRef lpMultiByteStr As Any, _
+    ByVal cchMultiByte As Long, _
+    ByVal lpDefaultChar As String, _
+    ByVal lpUsedDefaultChar As Long) As Long
+    
+Private Const CP_UTF8 = 65001
+
+Private Function EncodeUTF8(ByRef strUni As String) As Byte()
+    On Error GoTo ErrHandler
+
+    Dim lngUniLen As Long
+    Dim lngBufLen As Long
+    Dim lngRtn As Long
+    Dim bytOut() As Byte
+
+    lngUniLen = Len(strUni)
+    If lngUniLen = 0 Then
+        Exit Function
+    End If
+    lngBufLen = lngUniLen * 5
+    ReDim bytOut(lngBufLen - 1)
+    lngRtn = WideCharToMultiByte( _
+        CP_UTF8, _
+        0, _
+        StrPtr(strUni), _
+        lngUniLen, _
+        bytOut(0), _
+        lngBufLen, _
+        vbNullString, _
+        0)
+    If lngRtn Then
+        ReDim Preserve bytOut(lngRtn - 1)
+        EncodeUTF8 = bytOut
+    End If
+    
+    Exit Function
+ErrHandler:
+End Function
+
+Private Function SafeUBound(ByRef arr() As Byte) As Long
+    On Error Resume Next
+    Dim lngLen As Long
+    lngLen = UBound(arr) - LBound(arr) + 1
+    SafeUBound = lngLen
+End Function
+
 Public Function UrlEncode(ByRef strSource As String) As String
 
  Dim lngLength As Long                                          '???????(S-JIS ???)?????
@@ -11,10 +62,13 @@
  Dim lngReadCount As Long                                       'bytSource ??????????
  Dim lngWriteCount As Long                                      'strBuffer ??????????
  
-    lngLength = LenB(StrConv(strSource, vbFromUnicode))         'ANSI/S-JIS ???????????
-    If Not CBool(lngLength) Then Exit Function                  '0 ????????????
-    ReDim bytSource(lngLength - 1)                              'ANSI/S-JIS ???????????????
-    bytSource = StrConv(strSource, vbFromUnicode)               'ANSI/S-JIS ???? bytSource ???
+    'lngLength = LenB(StrConv(strSource, vbFromUnicode))         'ANSI/S-JIS ???????????
+    'If Not CBool(lngLength) Then Exit Function                  '0 ????????????
+    'ReDim bytSource(lngLength - 1)                              'ANSI/S-JIS ???????????????
+    'bytSource = StrConv(strSource, vbFromUnicode)               'ANSI/S-JIS ???? bytSource ???
+    bytSource = EncodeUTF8(strSource)
+    lngLength = SafeUBound(bytSource)
+    If Not CBool(lngLength) Then Exit Function
    
     strBuffer = String$(lngLength * 3, vbNullChar)              'URL ???????????????????
     strSingleHex = "%00"                                        '16 ?????????????????????

あ、Control-Cのアカウント持ってなかった
Posted at by




なかなか1日2個のリリースはキツイすね。苦笑
他のgtkXXXシリーズと違い、WassrにはXMLフォーマットが無いので少し困りましたがRSSパースする方法で解決しました。
アイコンはRSSのlink要素からユーザ領域までを分割し
http://wassr.jp/user/mattn/profile_img.png.32
という画像URLを作っています。
途中、このURLで「.32」が無かったり「:32」としていた為、オフィシャルのπなんちゃらさんに
http://wassr.jp/user/tokuhirom/statuses/id5xrHj4jd
tokuhirom @mattn gtkwassr が妙な URL をたたいている様子

http://wassr.jp/user/tokuhirom/statuses/0ZYaWVhBtM
tokuhirom @mattn プロフィール画像のパスがまちがってます。profile_img.png ではなく profile_img.png.32 等と画像のサイズを指定していただきたく。
とご指摘を受けながら完成。
http://wassr.jp/user/tokuhirom/statuses/6n4YjNSdux
tokuhirom @mattn ktkr!
とktkrを頂きました。
コードはCodeReposに置いてあります。
Revision 6229: /lang/c/gtkwassr
Posted at by



2008/02/05


まぁ、こういうのは鮮度が命って事で...
nowa開発ブログ - twitter互換のAPIを公開しました
ぶっちゃけ、gtktwitterのソースちょっと弄っただけなんですけどね...

gtknowa-20080205
使うには、nowaの設定画面でAPIパスワードを設定する必要があります。
nowa-api-setting
14:00ちょうど頃にos0xさんのブクマを見て、作ってこの記事を書くまでなので、実質30分!

鮮度としてはピチピチかと...
gtknowa - Google Code
Posted at by



2008/01/31


これはすごい

speeddating.vim - Use CTRL-A/X to increment dates, times, and more : vim online

なんと

  • 2008-01-31 11:51:31
  • I,II,III,IV,V
  • 3rd,4th

等いった日付、時刻、数字表記に対して<C-A>や<C-X>で値をインクリメント/デクリメント出来る様にするという凄いスクリプト。

試しに
2008-01-31 11:51:31
の51の所で30分繰り上げる為に「30<C-A>」とした所、
2008-01-31 12:21:31

と時間まで動くじゃないですか!!!

スバラシス...

そのままでも素晴らしいのですが、このスクリプトの素晴らしいのは、拡張出来る所。例えば

~/.vim/after/plugin/speeddating_japanese.vim
scriptencoding utf-8

SpeedDatingFormat %Y年%m月%d日 %H時%M分%S秒

let s:japanese_number = '0123456789'
function! s:japanized_number(string,offset,increment)
    let n = tr(a:string, s:japanese_number, '0123456789') + a:increment
    return [tr(n, '0123456789', s:japanese_number), -1]
endfunction
function! s:function(name)
    return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
endfunction
let g:speeddating_handlers += [{'regexp': '-\=\<[1234567890]\+\>', 'increment': s:function('s:japanized_number')}]

こんなファイル用意すれば、先ほどと同様に

2008年01月31日 11時51分31秒

の51の所で「30<C-A>」とすれば

2008年01月31日 12時21分31秒

となってくれるのです。

さらに↑の拡張スクリプトでは、全角数字の上で<C-A>/<C-X>するとインクリメント/デクリメント出来る様にする処理が入っていますので、「550」<C-A>を押下すると「551」とインクリメントされます。

素晴らしい。

あー、豚まん食べたくなってきた。

Posted at by




もごもごにURLをポストするツールを作りました。
なかみはTumblrから少し、いや幾分、いやかなりパクってます。

以下がブックマークレットです。
HTML側のソースは以下の様になっています。
ここのサーバにも一応HTMLを置いてはおきますが、いずれ削除するかもしれないので、出来る事ならば適当なサーバに以下のHTMLだけ置いて、ブックマークレットのURLをそこに書き換えて使って下さい。
もちろん、もごもごはSBMではありませんから、たんなるURLお知らせツールにしかなりませんけども...
再配布、改造、などは勝手ながら自由にさせて頂きます。

続きを読む...

Posted at by




はてな匿名ダイアリー:
「某大手国立大学卒業、25才、大手IT系企業に勤める優秀な社員が今感じてる閉塞感をリアルな言葉にしてやるよ」


明日休むつもりで、今日頑張ってみようよ。明日まだ倒れて無かったら、も少しだけやってみようよ。

私生活犠牲にしてまで働く事に、どんな意味があるのか教えろと言われれば、そりゃ君が選んだ仕事がそういう仕事なんだから仕方がない、としか言えないけど。

数年後、絶対笑える話になるんだよ。「あん時はキツかったー」って。

続きを読む...

Posted at by




C言語を使える奴は凄いのか?


C Language

数年前ならYesだ。でも今は「間違なくYesだ」とは言えない。昨今業務で求められているスキルはC言語使いだけではない。

求められているのはJava使いであり、VB.NET/C#使いであり、LL(javascript、php、python、perl, ruby)使いなのだ。

最近ではエンドユーザも賢くなってきて、単にパフォーマンス性や万が一に備えた柔軟性よりも、保守性やメンテナンス性、二次利用という利点、はたまたバグ侵入率を下げるにはどうしたら良いかを勉強してきている。

まれにC言語使いは、C言語を使えない人達から崇められる事があるが、はたして本当に凄いのだろうか?

ポインタや演算子オーバロードを知っている人が、MFCやATL/WTLを知っている人が、socketをCで組める人が、kernelソースを読める人が、本当に今後も凄いと言われ続けるのだろうか。

例えばGUIを作るとしよう。pythonとGTKを使えばウインドウを出すのに10行あれば十分でしょう。

ソケットで通信してXMLを送受信するならばperlで10数行でしょう。

SHA1で長々とコードを書くまでもなく、.NET Frameworkならば、予めクラスライブラリが用意されているでしょう。

昨今ではアプリケーションのプラグインもスクリプト化しつつある。

mallocとfreeを追っかけ徹夜する開発者と、コーディングと数回のデバッグで仕事を終え、定時で帰ってしまう開発者のどちらがカッコいいのでしょう。

「言ってるその言語自体はC言語で出来ているんだ」
そうでしょうね。でもあなたが作った訳じゃないですね。
「C言語じゃないと出来ない処理だ」
そうですね。ただ、それが必要な処理ならば、いずれ誰かがクラスライブラリを作ってくれるでしょうね。
「他の言語は今後仕様が変わってしまう可能性がある」
そうかも知れませんね。きっとその頃には、そのC言語で作られたシステムも作り変えですね。

確かにハードやkernelまわり、組み込み系ならばC言語でしょう。でも今後デスクトップアプリは他の言語に置き換わって行ってしまうかもしれない。

C言語はハードの制御と新しい言語の開発だけに使われ、デスクトップアプリケーション開発で御飯を食べるのが難しくなってしまいC言語使いの仕事と言えば組み込みだけになってしまう近未来が本当にやって来てしまうかも知れない。

C言語使いの凄いところってなんだ?

あるとすればクラッシュの仕方一つで、なんとなく原因が想像出来てしまう事。

時間が掛かる処理で、なんとなく無駄なループのソースが見えてしまう事。

そんな知識がいったい何時まで「凄い」と言われ続けるんだろうか…

「C言語だけでは御飯が食べられない」そんな将来がやってくるのだろうか。


そして近い未来、こんなネタで釣りな記事が@ITやITmediaに掲載されてしまう日が来るんだろうか。

Posted at by




あまり事情も詳しくなく、業種もWebエンジニアではないのでソースは貼らないでおこうと思います。

私はWebエンジニアではありません。確かに仕事でWebもやりますしデザイナさんが書いた画面で製造した事もあります。

色々なブクマを拝見させて頂いて、少しだけ話がそれ始めているんじゃ?と思う事があり、この記事を書いています。

エンジニアとデザイナの対立


一件デザイナさんの世界はチャラけててオシャレな物に見えますし、実際にそうだったりするかもしれない。イベント/パーティ開いて今後のWebをマーケティングと絡め、Webをリードしていくのはデザイナだ!と言ってる風に見えるかも知れない。

それに比べてエンジニアはデザイナの書いた画面を、いかにデータベースと結び付け、ハッキングし、速度劣化を防ぎ、セキュリティホールを塞ぎ、効率良く開発およびテストするかを考え、リードというよりはフィードバックで貢献しているように見える。
私は今回の件を傍観者としてこんな感想を抱いた。

デザイナさんとはエンジニアの事なんて気にせずWebのマーケティングについて模索し続け、業界人と呼ばれてリッチな生活を送るのもある意味良いと思う。イベント/パーティで儲けてもいいと思う。もちろんエンジニアを卑下する事は許されないけど。
ああいったイベント/パーティは、内面的にもWebをビジュアル的に感じられ、いわば「俺達がWebをリードしてやるんだ」くらいの気持ちになれ、興奮出来る要素が必要なのかもしれない。
デザイナさんの中には「エンジニアは俺たちが描いた構図をただ実装してりゃいいんだよ」なんて言う人も居るかもしれない。

私は、あながち間違ってないと感じる。

専門は専門家に任せるべきであって、デザイナは表面を作りエンジニアは内面を作るのが良いと思う。デザイナとエンジニアの間で摩擦が起こる事は、良い物を作る上で不可欠だと思う。それがWebの発展へ繋がると信じてます。

イベント料金について


イベント料が高いという意見は企業としてではなく個人の意見なら全く問題ないと思う。
人によっては安いと感じるかもしれないし、参加してよかったという意見もあるでしょう。
そんな意味で今回、開催者側が収支を公開されたのは私にとってある意味ショックな事でした。できれば「有料イベントですから...」くらいで返すべきではなかったかと思います。

また今回、幾らかの方が不信の念を抱いた件と、amachang氏が不信の念を抱いた件は明確に別件で、混同する事は両者にとってマイナスになり得ると思いました。どちらの件も別途議論すべきです。
もちろんエンジニアがWeb界をリードしていないという訳でなく、amachang氏を始めとする多くの技術力の高いエンジニアがWebを、また開発手法をリードしていって下さっています。

今回のイベント/パーティが明確に招待客のターゲットを決めておられたならば...
Webの新しい世界を精神的の感じたい人、デザインでマーケティングが生まれる事を認識したい方をターゲットにしていたならば...
このイベント/パーティは元々ギークな人間には向かなかったんじゃないかと感じました。


少なくとも私は、ボタンの位置を数ピクセル移動しようか一日迷う仕事よりは、一日中javascriptを書いている方が興奮を得られる方の人間です。
Posted at by




ちょっとポップな感じに変更しました。

実は、blosxomを捨て、MTに浮気しようかと思ってました。
理由は、管理画面。
確かに誰にも見られない、秘密の管理画面ってなんとなく魅力的に見えてしまったのですが、よくよく考えたら存在意義が無い事に気づきました...orz
Posted at by




最近、すごく感じている事。

「調整って言葉すごいな」って事


どの職業にもある言葉なんですが、ことIT業界においてはこの「調整」って言葉、結構頻繁にやり取りされるんですよね。
まず良く出てくるのが...

続きを読む...

Posted at by




最近、ネットを見ていると「人の話を上手に聞くコツ」とか「相手を納得させる上手な説明方法」なんて記事を良く見る。

確かに参考になるし、なるほどねと思う事がある。まだ実践した事はないが、使って見る価値のありそうな物もある。

もちろんこうした記事には「他者と差別化を図るには?」をテーマに自己啓発する術が書かれていて、それ自身良い読み物だと思うし、アクセス数があがるのも分かる。


でもこの類の話って本来、会社の上司から教わったりするものだったんじゃないかと。


客先で上手く説明出来なかった時に先輩から「こういうとお客さんに納得してもらえるよ」てな具合に、教えてもらったり質問したりするものだったんじゃなかろうか。

最近では上司の絶対度は薄れ、部下からすると上司のアドバイスは耳の横を通り過ぎる蚊ほどでしかなく。フリーランスや転職が当たり前になってきた昨今では、半永続的にな信頼を抱く上司よりもネット…な風潮になって来ているのかも知れない。

もしこの流れが真実だとしたら本当に淋しい事だし、はなから失敗を恐れ教科書通りの行動しかしない部下を持つ上司の苦労が想像出来てしまう。

もちろん度合いにもよるが、許される内ならば自分のやり方で失敗して見ても良いと思う。最近はそこまで寛大な会社も無いかも知れないけと、良かれと考えての事なら教科書と違ったやり方を貫いても良いと思し上司に聞いてもいいと思う。


もしくは、そんな事が許された頃に新人だった私は、運が良かったのかもしれない。

Posted at by




突然ですが、サーバを解約しようと思います。
2006年の1月からですから、ほぼ2年ここで色んな事を書いた気がします。

最近は、はてなブックマークdel.icio.us等でもブックマークして下さる方も増え、このサイトを消してしまうのは惜しい気もしますが、また新たな場所で始めたいと思っています。

つきましては現在、移転先を探しています。
これまで、blosxomというblogツールと自己拡張でやってきましたが、次のサイトにそれほどこだわりはありません。
選択肢に、はてなダイアリーや、Voxも考えています。
もちろんCGIが動けば尚の事良いのですが...
ちなみ私、小遣い制度のためお金はありません。自鯖が持てる程の環境もありません。
これまで書いた資料性のある記事は、再度アップしたいとも思います。

皆さん、良い所知りませんか?

コメント、ブックマークコメントお待ちしております。

Posted at by




GIGAZINE - 死んだマリオはいったいどこへ行くのか?

アンパンマンの顔も何処に行くんだ?あれ、生ものだぞ...
Posted at by




コードを晒け出して下さい。
プログラミングに自信があろうと、無かろうと。
貴方の書いたコードを見たいと思う人がいなかったとしても、晒け出して下さい。

私はオープンソースであることは、素晴らしい事だと信じています。たとえそれが、ちっぽけなサンプルプログラムであっても、人に見せる事で自分の書くコードに「見られる」という観点が入り込み、色んな見えかたが出来るようになります。さらに運が良ければ人からレビューしてもらえるんです。
私は、会社の部下、他社様の若手の方々によく「オープンソースコミュニティに参加しましょう。英語が分からなかったら日本のコミュニティでいいので入りましょう。」と言ってきました。これからも変わりません。
これは、単に「勉強しなさい」と言っている訳でなく、オープンソースコミュニティの特性がコーディング能力を養うには非常に効力があると信じているからです。
オープンソースコミュニティでは、修正ソースを送る際にはパッチ(patch)と呼ばれるソース差分を送る事になります。受け取った側は、そのパッチを一度適応してみて、間違いが無いかを確認します。そのコミュニティがMLを持っているならば、MLに参加している皆にそのソース差分が送付されます。
そして皆が自分の書いたコードに目を通すんです。

「恥しい...」そんな事を思ったのならば勿体ない話です。もちろん腐ったソースコードを人に見せるのはどうか、という議論もありますが、せっかく人に見てもらえるんだから見てもらわなくては損ですよ。

「自信がない...」そんなもの、みんなそうなんです。私だって自分の書いたコードに100点なんかあげられません。でも私は晒け出すんです。間違いを指摘してもらえるんなら、ありがたい話です。
自分で書いたプログラムの間違いを見つけるのは一苦労です。人に客観的に見てもらった方が間違いには気づきやすかったりします。そして人のソースも見ることで自分のソースコードの貧弱さに気づいたり、時には自信につながったりする物なんです。
#海外のコミュニティで自分が書いたソース差分が初めて取り込まれた...なんて事あればドキドキしますよ。


それはそうと、ここ最近「どう書く?org」というサイトを時間を見つけては覗いています。
ここでは、プログラミングに関する出題に対して、色々な言語の使い手が色んな手法で回答していきます。面白いのでfeedも登録しています。
電車で帰宅する際にも携帯から「へぇ...こんな書き方もあるんだ」と眺めてます。
一見コード書きに自信のある人達の集まりに見えますが、おそらく皆自分のコードに100点を付けて回答している訳ではないと思います。(HelloWorldは除きます)
「もしかしたら間違ってるかも」と思いながら回答している方も多いと思います。でも晒け出すんです。「俺こんなコード書けるんだぜ」なんて自慢大会じゃないんです。プログラミングを楽しんでいるんです。自分の書いたコードを見てもらい、たった一人でも「へぇ」と思ってくれる人が、もしかしたらいるんじゃないか?そんな気持ちだと思います。

コードを見せる人によっては、時には「そんな書き方、2億年前からあったよ。」とか「それ実行すると10秒後にはホームディレクトリが無くなってるよ」なんて手厳しいコメントを貰うかもしれません。でもそれって有り難い話。人に見せない限りそんなコメントは貰えません。


人に見せてなかったら、今頃貴方のハードディスクからホームディレクトリが無くなっていたかも知れませんよ...
Posted at by




私のRSSリーダ(Firefox/Sage)には、サイトの生死確認の意味で、ここのページのRSSが登録してあります。
ココ最近、なんか重たいなぁ...

と思ってました所、めったに見もしないアクセスログ(テキスト形式)が、原因しておりました。

ページアクセスに対して1行程の追加なのですが、しばらく見ない間に13MB程にまで、パンパンに太り上がっておられました。
しかたないので、ロデオボーイに...別名にリネームした所、なんとなくスイスイ動くようになったような気がしました...
Posted at by




最近ずっと、携帯からGoogle Readerを使ってます。

他の携帯向けリーダーと違い、記事を読んだ後「戻る」を押すと記事一覧からは既読記事が消え、次の記事が読みやすくなっています。

記事を未読状態に設定したり、スターと呼ばれるマークを設定し後からPCで確認しなおす事も出来ます。

ただ、まだGoogle Labsを卒業してない事もあり、色々と不都合な点もあります。以下私が気になっている問題です。

●既読記事が参照出来ない

記事に未読設定しないまま「戻る」を押してしまうと、以降PCでログインするまでその記事が読めなくなります。夜中にこれをやってしまうと朝までその記事が気になって眠れなくなります。

●携帯許容サイズを超えて表示する

画像たっぷりの記事を読むとエラーが発生します。GIGAZINEのように「詳細は以下の通り」といった感じでフル記事を飛ばしてあると、Google Reader Mobileのリンククリックで開くGoogle Mobile Proxyで閲覧出来ます。まぁこれは好き嫌いもありますし、フル記事にはドでかいバナー広告が出たりレイアウトが崩れたりする事もありますから、できればGoogle Reader側でなんとかして欲しいですね。ちなみにこのサイトは携帯閲覧可能です。

●出来れば携帯からURL指定でヒィード登録したい

これは単なるわがまま。最近出張が多くて携帯からのニュース&ブログ閲覧が多いので、出来ればURL指定で登録させて欲しい。まぁ携帯からだと、ブックマーク登録する際のURLをコピーして貼り付けるしか術がないので大変ではありますが…

以上3点の気になる問題がクリアされれば、PC版とほぼ同じインタフェースになり、ヘビーユーザにはもって来いなヒィードリーダーになる事は間違ないと思っています。

皆さん試しに使ってみてはどうでしょう?

http://www.google.com/reader/m/





Posted at by




アルファって何だ?

アルファブロガーって何だ?

アルファギークって何だ?

アルファブックマーカーって何だ?


はてなダイアリーキーワードによると
アルファブロガー あるふぁぶろがー
多くの読者に読まれている、影響力のあるブロガー。
しかし英語圏ではこの言葉は定着せず、代わりに「Aリストブロガー(A-list blogger)」という表現が用いられるようになっている。

アルファギーク あるふぁぎーく
「産業を変化させる力を持つ新しい技術に早いうちに飛びつき、ああでもないこうでもないといじくっているうちに、技術が進むべき方向性を示し始める、先鋭的で飽きっぽいエンジニア」(Tim O'Reillyの定義による)。

アルファブックマーカー あるふぁぶっくまーかー
SBMを用いていろいろやってる人たち。
との事。
なんか最近「アルファ」の使い方が、あちこちでずれてる気がする。
「すごい」とか「定評のある」とかで「アルファ」を使ってる人も見る。これってちゃんと定義された物なんじゃなかろうか...

だったら...

昨日、家に帰ってからウチの近所の中じゃぁ「アルファ女房」でもある嫁が作ってくれた、アルファ手料理を頂いた。
家族にも定評のある、「アルファ肉じゃが」で晩酌してると、横で子供がウルトラマンアルファのフィギュアで遊んでた。
なんだかアルファ懐かしくなった。

テレビでアルファ芸人「大島よしお」のギャグ「タンポポ関係ねぇ」を見てたら、眠くなってきたので風呂に入った。
その後仕事してたら、アルファネットウォーカーでもある友人からメールが来て
「お前のブログ、無断リンク禁止だったよな?なんかアルファブックマークされてるぞ」
と教えてもらった。
急いで見たら、アルファブックマークの他に、アルファコメンテーターから複数のコメント、しかもアルファスターが付いてる。
アルファブックマークなんかされたら、もうおしまいだ。明日にもブログを閉じよう。

こんな使い方を肯定している事になる。いかんな。こりゃ。


#なんかアルファ無意味な記事だな、コレ

Posted at by




なんだかShibuya.pmとやらで、みんなスライド使っててカッコイイな...
と思ってたら、ついカッとなって自分もスライド作ってみたくなった。

続きを読む...

Posted at by




最近、私になにかと降り掛かってくる、天気雨と言いますか、なんといいますか...

先日、関東のとある場所に出張に行きました。

その会社では、50才を越える人たちが現役でバリバリとプログラミングをしており、かなりテクニカルな質問を私にぶつけ、技術に没頭し、いまもなおプログラミングを愛したまま仕事をされておられる方の集まりでした。

ある意味、カルチャーショックを受けると共に、「うらやましい」と正直に思いました。

私の今置かれている立場や役職では、会社から既にを決められてしまっており、私はその線路の上を走り、知らない間に「営業職」となっていく。そんなが見えている気がするんです。

私は、既に役職的には管理職、ですが未だ現場で開発もします。そしてこの役職になるまでに何度も

「まだ開発をさせておいて下さい」

と会社にお願いして来ました。

しかし会社が私に求めている何かと、私が望んでいる何かは、決して同じ線路ではなかったりするのですよ...ハイ


先日、部下が「僕、技術を辞めて営業になります」と口に出しました。

私は何をおもったか、その彼に

「技術が嫌になったのか?それとも見切りを付けたのか?」

と聞いてしまいました。その彼は「いや、実は昔から営業がやりたかっただけです」と答えました。

この時自分が、「見切り」や「定年説」みたいな、境界線と言うものを、知らずと意識してるのかな...と気づきました。

もちろん自分の技術に、有る程度は自信があります。

未だ、自社の後輩には誰にも負けていないつもりです。

ですが今後、何かを諦めて、人の引いた線路の上を走らなければならないかもしれないと、少しでも考えた自分が、イヤーーーーーな気分になりました。


もう少し。
もう少し。

開発を続けさせてください。
Posted at by




コメント欄まだやってる...
佐藤秀の徒然\{?。?}/ワカリマシェン:小飼弾氏が赤木智弘氏を嗤える本当の理由
日本人にとってソフトウェアが日本語で使えるかどうかは、そのソフトウェア自身の価値を左右する尺度であり、商用ソフトウェアならば直接マーケティングにも影響する。
例えばソフトウェア自身でなくとも、ダウンロードページやチュートリアルが英語というだけでダウンロードを辞めてしまう事もある。
大手企業では日本法人や日本人社員にl10n化やi18n化を任せる場合もある。こういう点で言えばmiyagawaさんも言うように日本人にとってi18n化はJob Securityになり得る。
企業としての立ち位置ならば、それは完全な業務だろう。

最近はあまり活動してないですが、私もこれまで色んなソフトウェアのi18n patchをオフィシャルに提供して来た。もちろん無償で...

例えばvim。私がvimのオフィシャルにpatchを送り始めたのはvimのバージョンが5.6だった頃。それまではMLで英語に苦労しながらvim/cvsheadの追っかけをやってました。それまでのvimは各文字コード毎に実装を個別に対応した処理になっており、今から考えるとそれはひどいコードでした。
私が最初に送ったpatchは「set guifont」で設定したフォントをIMEキャレットにも設定するといった物。Bram Moolenaar氏は適当な英語にも快く答えてくれ、extra patchながら取り込んでくれました。ぜんぜん大したpatchでは無いがftpサーバに「5.6.004」というファイルで置かれたパッチを見た時は飛び跳ねる程嬉しかった。
それからvimには数多くのi18n patchが適応され、現在では内部でutf-8を処理出来る素晴らしいエディタとなった。1文字毎をメモリに持つのではなく全てバイト列で処理すると言った方法の為、他のアプリケーションとは違うi18n化の方法であり特殊ではあるが、逆に言えばvimは壊れたutf-8でも編集出来る様になっている。
最近はBram Moolenaar氏自身が何か新しい機能を取り込む際、マルチバイト文字列に非常に気を使っていてくれて、私がpatchを送る事は殆ど無くなった。でもそれが私の望んだ事だった。

私は、forkが嫌いだ。
私はl10nやi18n化した物をforkとして公開する形が好きではない。出来る事ならばオフィシャルに統合させるべきだと思っている。これはvimのcontribute authorでもあるKoRoN氏も同じ考え。私はl10n化やi18n化はオフィシャルを説得して取り込ませるべきものであって、そうでなければ公開する価値はあまり意味のない物だと信じている。
軽量GUIライブラリ、FLTKには以前、fltk version1をベースととしたfltk-utf8というforkがあったが、私はその頃まだベータ版であったfltk2をutf-8化するpatchを書いて送った。
fltk-utf8の作者には少し申し訳ない気もしたが、このpatchにより現在では当たり前かのようにfltk2で日本語が表示/入力出来る様になった。
良いものは良いものとして後押しするのがベターだと思った。
fltkに限らず多くのソフトウェアがボランティアで作成されており、素晴らしい事だと思う。その多くのエンジニアは見返り等求めない方であって、それをネタに自分のJobを生み出そうなんだ思ってもいない。
dankogai氏のEncodeもperlを後押しする為の物。例えばdankogai氏がperlに手を加えforkとしてEncode対応のperlをリリースしたならば、反論を受ける対象となり得るかもしれない。
しかしそれどころかEncodeはCodeReposにオープン開発という意味で公開されている。つまりdankogai氏はEncodeを独り占めしようとは思っていないと言う事だ。
外国人プログラマがEncodeにpatchを送ったならば、dankogai氏はきっとwelcomeメールを送るだろう。

私のようなちっぽけな開発者にはperlでEncode程のライブラリを作ってオフィシャルからforkさせられる程の実力は無いので大した事は言えないが、ボランティアでpatchや拡張を作っておられる方々に対して"「日本語」という最大最強の非関税障壁に守られた既得権益者"という言葉をあびせるのは明らかに間違っている。

もし「dankogai氏はEncodeをネタに名声を売っている」と言いたかったならば、それは貴方も「dankogai氏をネタに名声を売ろうとした」事になるのではないかな...。

Posted at by




freenodeのsubtech系IRCチャネルはutf8を使う事!

http://subtech.g.hatena.ne.jp/miyagawa/20071024/1193192377
http://d.hatena.ne.jp/yappo/20071024/1193197297
http://d.hatena.ne.jp/tokuhirom/20071024/1193197797
周知。
私が覗いているsubtech系チャネルは、#codereposか#plagger-jaくらいですが...

先日紹介したbitlbeeでは
set charset utf-8
save
でおけ。
周知だけでは面白くもないので今日はIRCにまつわる昔話の適当訳を...


原文:Worlds worst hacker. IRC transcript
IRCにて起きた、ハッカーbitchcheckerの悲しい結末」
現在リンク切れの模様。探してます。
追記
オリジナルが消えている模様。別の所で同じようなものを見つけたので...
http://themostboringblogintheworld.wordpress.com/2006/09/13/worlds-worst-hacker-irc-transcript/

用語
ban:
  IRC用語で、迷惑な発言などを理由に
  部屋からはじき出す事を指す。
  ※kickと同語

IP:
  ネットワーク上のコンピュータを指し示す
  番地を意味し、127.0.0.1は自分の
  コンピュータを意味する。

pingタイムアウト:
  ネットワークが切断された事を意味する。


とあるIRCチャットでの話...

* bitchchecker (~java@....) がチャットを抜けました(タイムアウト)
* bitchchecker (~java@....) がチャット#stopHipHopに参加しました
<bitchchecker> なんで俺をkickしたんだ!
<bitchchecker> お前はまともに討論もできないのか?
<bitchchecker> 答えろ!
<Elch> いや、kickなんかしてないよ

<Elch> 君はpingタイムアウトしたんだ
* bitchchecker (~java@...) がチャットを抜けました(タイムアウト)
<bitchchecker> pingなんちゃらって何の事だ!
<bitchchecker> 俺のPCの時刻は正常だ!
<bitchchecker> サマータイムさえ設定してるぜ

<bitchchecker> お前が俺をbanしたんだ
<bitchchecker> 仲良くしようぜ、このゲス共
<HopperHunter|afk>
<HopperHunter|afk> サマータイムって...ぷぷっ
<bitchchecker> 黙れ!サマータイムを設定してるんだ

<bitchchecker> もう2週間だ
<bitchchecker> Windowsを起動したらサマータイムを設定しろとメッセージが出るだろ!
<Elch> ホントにコンピュータのエキスパートなの?
<bitchchecker> うるさい!お前をハックしてやる!
<Elch> 分かったよ。黙るよ。君がどんなにスゴいハッカーか、私達に見せない為にも...ぷっ

<bitchchecker> お前のIPアドレスを教えろ!お前をオシマイにしてやる
<Elch> え~っと...そう、129.0.0.1
<Elch> あ、いや...たしか 127.0.0.1 だ
<Elch> よし 127.0.0.1 だな。今からスゴい攻撃を仕掛けてやるから待ってろ
<bitchchecker> 5分以内にお前のハードディスクは全部削除されるぞ

<Elch> うわ!やられる~
<bitchchecker> 黙れ!お前はもうすぐオシマイだ
<bitchchecker> お前のIPをプログラムに入力したぞ。お前はもう死んでいる...
<bitchchecker> じゃぁな
<Elch> 誰に言ってるの?

<bitchchecker> ってにお前だよ
<bitchchecker> バイバイ
<Elch> うわ~...君がそんなスゴいハッカーだったなんて...ガクガク(((( ;゚Д゚))))ブルブル
* bitchchecker (~java@...) がチャットを抜けました(タイムアウト)
数分後...
* bitchchecker (~java@...) がチャット#stopHipHopに参加しました

<bitchchecker> 俺のPCがたまたまクラッシュしたから、命拾いしたな
<Metanot> ぷゲラwww
<Elch> じゃぁ、もっかいハックしてみろよ。IPは同じ127.0.0.1だ。
<bitchchecker> バカが...
<bitchchecker> バイバイ!

<Metanot> あ~、[閲覧禁止文字]
<bitchchecker> バイバイelch
* bitchchecker (~java@..) がチャットを抜けました(タイムアウト)
* bitchchecker (~java@..) がチャット#stopHipHopに参加しました
<bitchchecker> elch、このクソ野郎!
<Metanot> bitchchecker、君幾つ?

<Elch> ご機嫌いかがbitchchecker君?
<bitchchecker> ファイラーウォールなんか使いやがって
<bitchchecker> ちがう、ファイアーウォールだ
<Elch> あ~良く知らないけど、たしか
<bitchchecker> 俺は26歳だ

<Metanot> 26でそれかよwww
<Elch> どうやって僕がファイアーウォールを使ってると?
<Metanot> 僕ちゃんお行儀が悪いよ
<bitchchecker> 俺の送った電源強制終了信号を、お前のファイアーウォールが俺側に跳ね返したんだ
<bitchchecker> その糞ファイアーウォールをoffにしやがれ!

<Elch> カッコイイ...そんな事できるなんて知らんかったよ
<bitchchecker> ふっ...俺のウイルスでお前のPCを破壊してやる
<Metanot> 君をハックするの?
<Elch> あ...bitchchecker君は僕をハックしようとしてるんだ
<Metanot> bitchchecker君、あのさ、もしハッカーなんだったらファイアーウォールなんかすり抜けられるよね。って僕でも出来るよ。

<bitchchecker> あぁelchをハックしてるんだが、この糞野郎がファイアーウォールを...
<Metanot> どんなファイアーウォール?
<bitchchecker> 女々しい奴め
<Metanot> 普通の、ごくごくありふれたハッカーだったら、ファイアーウォールぐらい突破するもんだが...あんたが女々しいのか...
<He> 僕~、自分のでもしゃぶって、まぁ落ち着け。挑発にのってネタにされてるだけだぞ。

<bitchchecker> ファイアーウォールをoffにしろ。そしたら今すぐ俺様がウイルスを送って[閲覧禁止文字]してやる
<Elch> ヤダ
<Metanot> ってかなんでoffにさせんの、君がoffにすべきだろ
<bitchchecker> お前は怖いんだ
<bitchchecker> んなファイアーウォールの陰に女々しく隠れている奴なんか俺様はハックしないさ

<bitchchecker> elch、今すぐoffにしろ!
<Metanot> 色々言いたいことはあるんだが...まずハッキングの意味わかってる???彼がoffしたらそれは侵入を招待してる事になるんだぞ?その時点でハッキングじゃないだろ
<bitchchecker> うるさい!
<Metanot> www
<bitchchecker> 俺の婆ちゃんはファイアーウォールでネットサーフしてる

<bitchchecker> お前らは自分らがクールだと思ってるだろうから、あえてファイアウォールなしでインターネットに出るなんてしないだろけどな
<Elch> bitchchecker君、友達がファイアーウォールをoffにする方法を教えてくれたよ。もっかいやってみて
<Metanot> bitchcheckerでなくbitchhackerだからハックなんかできないだろうけどね
<Black<TdV>> ワロタwww

<bitchchecker> うるさい
<Elch> bitchchecker君、アタックを待ってるんだけど...
<Metanot> 何度も言うけど彼はハッカーじゃない...
<bitchchecker> お前ウイルスが欲しいのか?
<bitchchecker> お前のIPを教えろ お前のハードディスクを消去してやる

<Metanot> 笑。私がハッカーだったら諦めないな。僕はハッカーってどういうものか分かってるけど、君は100%ハッカーじゃないよ
<Elch> 127.0.0.1
<Elch> 簡単な事だ
<bitchchecker> www ふざけた奴らだ。もうオシマイにしてやる
<bitchchecker> もう最初のファイルが削除され始めたぞ

<Elch> ママーン...
<Elch> ちょっと待ってみよう
<bitchchecker> レスキューしようたって無駄だぜ。ゲス
<Elch> そりゃマズイ
<bitchchecker> elch、お前のGドライブは削除されたぞ

<Elch> うは、太刀打ち出来ない
<bitchchecker> そして20秒以内にFドライブもオサラバだ

<bitchchecker> tupac rules (mattn:ここ良く分からん)
<bitchchecker> elch、お前のFドライブは無くなった。Eドライブもだ


<bitchchecker> そしてDドライブも45%削除されたぞ!ガハハハハ
<He> Metanotは何も言うことないの?
<Elch> きっと床で転がりまくって笑ってるのかと...
<Black<TdV>> ^^

<bitchchecker> Dドライブ削除だ
<He> 勝手にやってろバカ(BITCH)

<bitchchecker> elch、インターネットにIPを晒すなんてバカな事、2度としない事だ...
<bitchchecker> Cドライブも30%だ

* bitchchecker (~java@....) がチャットを抜けました(タイムアウト)
Posted at by




AddClipsを付けてブクマ数激増!!!

続きを読む...

Posted at by




わさび鉄火で...
さて仕事しよう...

wasabitekka
Posted at by




いつもボーと見てるGoogle Readerで、「PR:」と先頭につく広告記事はほぼ読まないのですが、なんとなくこの記事が目に入りました。
第7回 ずっとフリーランスでいられますか?
私はフリーランスではありません。会社で1社員として働いています。
ですが今までフリーランスについて考えた事が無いとは言えず
自分の力でお金を稼いで見たい
と考えた事もあります。もちろん、フリーランスで頑張ってらっしゃる方からすれば、「そんな甘いもんじゃない」と言われるかもしれませんし、「そんな良いもんじゃない」とも言われるかもしれません。
ただ、リンクの記事に目が止まったということは、少なからず私の中で「フリーランス」という仕事の仕方に対して「興味」「期待」「憧れ」「不安」といった葛藤みたいなものが働いていると自分ながら感じています。
私の周りにも、契約社員の方や、自分で会社を起こした方がいらっしゃいます。時に意識したりもします。組織人としては、抜け出す事への不安もありますし、岐路として「フリーランス」、「起業」が同じものにも見える程、自分にとってリスクのあるものに見えます。逆に、組織に属している安心感を得ていたいという気持ちもあります。
もちろん明日からもし「フリーランス」になったとしたら、まず誰を頼ったらいいのかも分からなくなると思います。
この記事を読んで記事内に出てきた「鎌田さん」が本当に実在するならば、「フリーランス」の方で不安を抱いている方が少なからずともいらっしゃるんだ...と少し感慨深くなりました。

岐路点は、今後どのような場面でやってくるかもしれないですが、もしその時が来たらもう一度この件について考えてみたいと思います。
Posted at by




まさか、そんな直接的に告げられるとは思ってませんでした。

続きを読む...

Posted at by




最近、ネット上のトラブル記事や非難記事をよく目にする。

どれもネット上で、人がキーボードを叩き、当人が「自分の言いたい事」を、さも独り言かのように、かつその独り言を注目させたいかのごとく、過激な脚色を付けて発言したばっかりに起こった物ばかりだ。
もちろん最近のネットでは多少内容に関係無く、皆から注目された記事が、ランキングや注目度として扱われ、時に人を傷付け、言い放った人間だけがストレス発散し、傍観者の数で注目度が決まり、「自分の気分が悪いなら言ってしまっても良い」という空気だけを残す。

メディアでは、少し過激に、時には偽り、感情を煽る事でフィードバックを期待する事がある。ブログで注目を浴びたり、アフィリエイトで集客したい人もいるだろう。結構結構。ただブログにしてもソーシャルブックマークコメントにしても、喋るんじゃなくて、タイプするんだから、一回その言葉が誰に対して発信しているもので、相手や傍観者が読んだらどう意識するかぐらい考えようよ。

所詮ネット上に散らばるテキストかもしれないけど、何処にも向けたつもりは無いと思っていた矢印が、時には人の懐をかすめる事もある。
そう考えてしまうと私も怖いし、何も書けなくなってしまうかもしれない。


それでも…
インターネットは楽しむもの

そう思いたい。





Posted at by




狐の王国 はてなスターで甦る高橋名人の想い出。
実は私、最盛期には16.5連射を放つ事が出来ました。シュウォッチと呼ばれる連射測定機で16.5連射を記録した事があります。その頃高橋名人が16連射を誇る頃、小学生だった私はヒーローだったのです。

もちろん、顔は画面等見れずプルプルした状態の連射なので、実用出来る程では無かったのですが...

皆から「スゲー」の歓声を浴びて数日、毛利名人が紙面に登場しました。毛利名人は人差し指、中指、薬指を八手のようにコントローラに当て、左右に反復運動する事でほぼ2倍の連射をあみ出しました。そして私の「ヒーロー」という肩書きは消え去りました。
しかしながら「連射が出来る」事は、その頃の子供にはステータスであり、それは名人との距離を測るものでもありました。
特に燃えたのが、ハイパーオリンピック。物差しを使い、机と専用コントローラに対して斜めに置き、下から物差しを弾く事で驚異的な連射を実現できる技は、その頃の子供達にとってセンセーショナルでした。

ここで、これだけで飽き足りなかったのが、私。
その頃流行っていたミニ四区「ファイヤードラゴン」のモーターを取り出し、先に付いているギヤを1枚おきに削り落とし、ファミコンコントローラのボタンに斜めに当てたのです。
単2電池をセロハンテープで止めたモーターは、けたたましい音と共に回転し、画面の「スターフォース」ではほぼ無敵の連射能力でした。
周りにいたオーディエンスも、スーパーマシンの繰り出す連射にただただ感動し、泣き出す子供も...(ウソ)

そしてボスを手前にした頃、部屋中にほんのりとした焦げ臭い匂いが...

コントローラを見た私の目に入って来たのは、グリグリにえぐり取られたボタンと、飛び散ったプラスチック片でした。
Posted at by




物事には、許せる物と、許せない物がある。
私にもそれはある。
普段は温厚な私で、多少の間違い位なら見逃す方だが、これに限っては許せなかった。
いくらなんでも、使っていいソースと、使っちゃいけないソースがあることくらい、常識的に分かっていると思ってた。

続きを読む...

Posted at by




amachangがやってたので私も
typing-test
まぁ、徹夜明けで一睡もしてないしこんなもんか...orz
Posted at by




昨年お世話になった方々、ありがとうございました。
今年も例年通り、「自分は自分らしくありたい」と思います。

さて、正月はゆったり過ごしながら、はてなブックマークで「コード書きがコード書きに対して非難するコメントやブクマ」の一連を見てました。
一貫してテーマとなっているのがSTFUAWAC(shut the fuck up and write some code)という言葉。
この言葉に対して各人色々な捕らえ方はあるだろうが、私にとってこの単語から連想出来るのは
アレコレ言ってる暇あったらコード書こうよ
って雰囲気。自前のコードに文句付けられて、言い訳してる暇があるならコード書こうよ。ってレベル。
どっちかっていうと非難に使うものじゃなくて、叱咤激励かな。
会社で上司がダベってる社員の後ろに来て
ダベっとらんと、さっさと仕事しろよ苦笑
くらいの一言。もちろん前提で上下関係なんか無いんだけど。
もちろん私は数多くのほったらかしプロジェクトの持ち主でもあるので、「さっさと仕上げろや」という意味でSTFUAWACと言われるかもしれないけど...。もしSTFUAWACって言われたら、それは「使いたいのにマズいコードがあるから直してよ」って読み取れるんじゃないかな。

しかしながら私は生粋のPerlerじゃないし、時にはスーツ的な振る舞いもする立ち位置の仕事をしているので、geekと呼ばれる資格は無いのかもしれないけどやっぱり...
geekやIRCではDISるのは当然...
とかをあまり接頭語(片付ける言葉)にしたくないし、誰しもがそんな行為は建設的ではないと思っている事を信じたいものです。
特にコード添削という行為はどうしても水平より下な目線に見られがち。だから私は気を使っているつもりです。たとえIRCやgeekだから、ブログ上だから…だったとしても礼儀は忘れたくないものです。
そしてそんな考えがスーツ的と言われるならば私は「mattnはgeekでない」と言われても全くかまわないと思っています。

そういう意味でも今年も自分らしくありたいと思っています。

本年もよろしくお願い致します。
Posted at by




以下個人的な勝手な見解。そんなに熱く語ってる訳ではありませんので...
昨今色々なSNSが出回り、個々のSNSでそれぞれの色を出してアピールしています。
その中でもmixiがアクティブユーザ数はダントツで誇っており、これから数年も変わらないであろうと思われます。
mixiを代表とするSNSを色づけるSNS特有の機能として「足跡」があります。足跡とはそのSNSユーザ(もしくはゲスト)が対象のユーザページにアクセスした際、アクセスされた側に残されるログを指します。
otsuneさんも言うとおり、自前のブログを運営されている方であればHTTPアクセスログは一度は目を通した事があるでしょうし、知らない人が閲覧しているのは当たり前だと認識していると思います。
よく「足跡機能の付いてるSNSは嫌いだ」という人が稀にいますが、私が思うに
「足跡機能が必要ない」ではなく「足跡機能が嫌いだ」という人は、"自分が踏まれる事"よりも"自分が踏んでしまう事"で、踏まれた側から"勝手な先入観を持たれる"のが嫌いなのであって、単に見なければいいはずの足跡を"嫌い"という理由を明示しない。
という事。

私は、何個かSNSを掛け持ちしています。足跡機能があるSNSにも参加しています。時には足跡も見ますし、このサイトのアクセスログも見ています。
時にはtrackerfeedを使って、リファラにアクセスしたりもしますし、ハンドルネームで検索したりする事もあります。

ネットでコミュニケーションしたいなら普通の行動だと思っていますし、私のようにソフトウェアを開発している人間にとっては、作ったアプリケーションやプラグイン、パッチがまわりでどんな風に使われているのか知りたいと思うことは当然の事だと信じています。
まさか自分の為だけに作ったソフトウェアを自分だけで動かしてウヒヒヒヒ...なんてつもりはないですし、もちろんこの考え方も個々の勝手でいいとも思っています。

閲覧者は、客観的に人を判断している訳ですから、どう判断されたって致し方ないし、そもそも足跡をつけてしまっただけで勝手な先入観を持たれるコミュニケーションなんて成立しないんじゃ...と。

別に
貴方に関する記事書いたのにリアクション無いし、足跡見たら踏んでるのにスルーですか?
なんて可愛い事を言うなと言ってる訳でもないですし、平然とクールに足跡は閲覧すべきだ!と「SNSのルール」を語ってる訳でもないです。

ただただ足跡の有無でSNSの良し悪しを公言したり、足跡なんか気にするのは××だと公言するのはどうかと...

その「足跡拒否反応のおしつけ」こそが逆に
熱いはずの熱湯風呂に"熱い"と絶叫しながら入れない人間はおかしい
と言ってるように私には見えてしまうのです。

まぁ、その個々の考え方の不一致がSNSを面白くしているんでしょうけどね。

以上、独り言でした。
Posted at by




先日ようやくPownceのAPIが公開されました。
APIはGoogle Groupで公開されています。
また、Python, Perl, ColdFusionを使ったライブラリも公開されています。ただし07/31時点では...
  • ColdFusionのディレクトリは空っぽ
  • PerlはMessageとFileしか対応していない
  • Pythonは全て対応しているけども中身が×××
って事で、MixiAPI.pyのようなものを作ってみました。
フィードの取得はPownceAPI.Session(userid, passwd)でセッションオブジェクトを作成し、get_notes()でfeedparserオブジェクトが返ります。また、ポストは
  • PownceAPI.Message(message)
  • PownceAPI.Link(message, url)
  • PownceAPI.File(message, file)
  • PownceAPI.Event(message, location, name, datetime)
で作成したオブジェクトをsession.send_item(to, item)で送信します。
簡単な例を以下に示します。
import PownceAPI

# セッションを作成
session = PownceAPI.Session("example""password")

# フィードを取得
print session.get_notes().entries[0].summary

# メッセージを送信
item = PownceAPI.Message("do you have a time?")
session.send_item("public", item)

# ファイルを送信
item = PownceAPI.File("my location""map.png")
session.send_item("all", item)

# リンクを送信
item = PownceAPI.Link("gugure!""http://www.google.com/")
session.send_item("public", item)

# イベントを送信
item = PownceAPI.Event("this is a party!""My Home""Yakiniku")
session.send_item("all", item)

今のところ、send_itemで指定出来るtoに制限があるのか、File等をallではなくpublicで送信すると500 Internal Server Errorが発生します。
また、APIを使ってもマルチバイト文字は表示されません。
とりあえず、動いている...といったところです。
もしPownceが使いたいが招待状がないという方は、コメントなりで連絡下さい。招待状を送らせて頂きます。

続きを読む...

Posted at by




注意:os0xさんからの指摘で、ちゃんと動かないらしいです。

javascript:var d=document,t,i,l,m=[];l=d.getElementsByTagName('span');for(i=0;i<l.length;i++){if(l[i].className=='hatena-star-star-container')m.push(l[i]);}t=d.createElement('input');t.type='text';t.onblur=function(){var v=d.createElement('div');v.innerHTML=t.value;m[0].appendChild(v);m[0].removeChild(t);};m[0].appendChild(t);void(0);
  • ↑のコードをアドレスバーにゴニョゴニョゴニョ
  • 出てきたテキストボックスにゴニョゴニョゴニョ
  • フォーカスを外す為に、アドレスバーを選ぶ
  • 出来た文字列を選ぶ
  • ゴニョゴニョボタンを押す!
たまーに上手くいかない場合もある
追記1
最後に余計な文字を1個作って、それを選択せずにゴニョゴニョするとよさげ

追記2
ちょっと改良
javascript:var d=document,t,i,l,m=[];l=d.getElementsByTagName('span');for(i=0;i<l.length;i++){if(l[i].className=='hatena-star-star-container')m.push(l[i]);}t=d.createElement('input');t.type='text';t.onblur=function(){var v=d.createElement('div');v.innerHTML=' '+t.value+' ←ここまで選択';m[0].appendChild(v);m[0].removeChild(t);};m[0].appendChild(t);void(0);

追記3
あ、それでもダメな時があるね...
パターンがつかめない...orz
Posted at by




お星様。ごめんね。

「このメッセージは最初の☆が付けられた際に消えます。」が消えないから...

自分のサイトに、はてなスターがカウントされないから...

はてなに問い合わせを出したら
「消えます。」のメッセージが消えると共に

なくなっちゃった。

lost_hatena_star

僕の心の中には、そして個別の記事にはちゃんといるから...

お星様。ごめんね。
Posted at by




はてなのお気に入りユーザーの情報を取得できる「お気に入りAPI」(Favorites API)を公開しました」って事でさっそく...


ソースはこんな感じ...
追記:このソースはFirefoxでしか動きません。IEで動くものはこのページのHTMLを直接参照下さい。(Firefox限定時の残骸あり) <script type="text/javascript">
<!--
function hatenaFavorites(data) {
  var container = document.getElementById('hatenaFavorites');
  Array.slice(container.childNodes).forEach(container.removeChild, container);
  data.favorites.forEach(function(i) {
    var div = document.createElement('div');
    var img = document.createElement('img');
    img.src='http://www.hatena.ne.jp/users/'+i.name.substring(0,2)+'/'+i.name+'/profile_s.gif'
    div.appendChild(img);
    div.appendChild(document.createTextNode(' ' + i.name));
    container.appendChild(div);
  });
}
function loadHatenaFavorites() {
  var container = document.getElementById('hatenaFavorites');
  Array.slice(container.childNodes).forEach(container.removeChild, container);

  var img = document.createElement('img');
  img.src='http://mattn.kaoriya.net/images/ajax-loader.gif';
  container.appendChild(img);

  var script = document.createElement('script');
  script.charset = 'utf-8';
  script.src = 'http://www.hatena.ne.jp/mattn/favorites.json?callback=hatenaFavorites';
  document.lastChild.appendChild(script);
}
--></script>

追記
os0xさんからの指摘でIEで動かないのを直しました。
↑のソースは張り替えませんので、直接ソースを閲覧して下さい。(残骸が残ってますが...)
Posted at by




はてなハイクが面白すぎる。
もう、いままでの様に「はてなスターは気に入った記事にしか付けない」なんて考えの人は、一度行ってみるべき。
スピード感、一発ネタ、五臓六腑に染み渡るダジャレ。
どれもこれもたまりません。
あと、手前味噌ですが昨日作った「LDRizeでpin付けたノードにMinibufferから「はてなスター」を付けるグリモン」がめちゃ便利。
LDRizeとMinibufferがあれば「j/k」で上下移動し「H S」で、はてなスターを付けられます。
※pinを付けて無くてもスターを打てる様に改良してあります。
見てるだけでも面白いですよ。
Posted at by




擬似GM_xmlhttpRequestを使ってたので、はてなリソースしか動いてなかった。
擬似GM_xmlhttpRequestでは、はてなスターの場合だけJSONで動かすようにしたので、きっと行けると思う。
JSONデータから文字列に戻す部分はこれを使わせて頂いた。
前とそれほど変わらないけど、unsafeWindowの宣言場所を上にもってったので前回のような簡単なパッチの当て方は出来なくなった。

--- HatenaStarEverywhere.user.js.orig   Mon Oct 01 10:37:41 2007
+++ HatenaStarEverywhere.user.js    Fri Oct 05 23:08:10 2007
@@ -92,6 +92,9 @@
     ensure(window.Hatena, 'Star');
 }
 
+if (typeof unsafeWindow == "undefined") {
+    var unsafeWindow = window;
+}
 if (typeof unsafeWindow.Hatena == 'undefined'
         || typeof unsafeWindow.Hatena.Star == 'undefined'
         || !unsafeWindow.Hatena.Star.loaded) {
@@ -138,4 +141,137 @@
             GM_setValue('configExpire', '');
         }
     });
+}
+
+if (typeof(GM_setValue) != 'function') {
+  function GM_setValue(key, value) {
+    document.cookie = [
+      name, '=', escape(value),
+      ';expires=', (new Date(new Date() + 365 * 1000 * 60 * 60 * 24)).toGMTString()
+    ].join('');
+  }
+}
+if (typeof(GM_getValue) != 'function') {
+  function GM_getValue(key) {
+    var r = new RegExp('/' + name + '=([^;]*)/'), m;
+    if (m = document.cookie.match(r)) return unescape(m[1]);
+    return null;
+  }
+}
+if (typeof(GM_xmlhttpRequest) != 'function') {
+  var GM_xmlhttpRequest_Data = null;
+  function GM_xmlhttpRequest_Handler(data) {
+     GM_xmlhttpRequest_Data = toJsonString(data);
+  }
+  function GM_xmlhttpRequest(opt) {
+    if (opt.url == 'http://s.hatena.ne.jp/siteconfig.json') {
+       var s = document.createElement('script');
+       s.charset = 'utf-8';
+       s.onload = function(e) {
+          s.responseText = GM_xmlhttpRequest_Data;
+          opt.onload(s);
+         GM_xmlhttpRequest_Data = null;
+       }
+       s.src = opt.url + '?callback=GM_xmlhttpRequest_Handler';
+       document.body.appendChild(s);
+   }
+    var x=new XMLHttpRequest();
+    x.onreadystatechange=function() {
+      switch(x.readyState) {
+        case 4:
+          opt.onload(x);
+          break;
+      }
+    };
+    x.open(opt.method,opt.url,true);
+    x.setRequestHeader('Content-Type',opt.mime);
+    x.send(null);
+  }
+}
+
+/*
+ * include http://code.google.com/p/trimpath/wiki/JsonLibrary
+ * with few modify for opera.
+ */
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+function toJsonString(arg) {
+    return toJsonStringArray(arg).join('');
+}
+
+function toJsonStringArray(arg, out) {
+    out = out || new Array();
+    var u; // undefined
+
+    switch (typeof arg) {
+    case 'object':
+        if (arg) {
+            if (arg.constructor == Array) {
+                out.push('[');
+                for (var i = 0; i < arg.length; ++i) {
+                    if (i > 0)
+                        out.push(',\n');
+                    toJsonStringArray(arg[i], out);
+                }
+                out.push(']');
+                return out;
+            } else if (typeof arg.toString != 'undefined') {
+                out.push('{');
+                var first = true;
+                for (var i in arg) {
+                    var curr = out.length; // Record position to allow undo when arg[i] is undefined.
+                    if (!first)
+                        out.push(',\n');
+                    toJsonStringArray(i, out);
+                    out.push(':');                    
+                    toJsonStringArray(arg[i], out);
+                    if (out[out.length - 1] == u)
+                        out.splice(curr, out.length - curr);
+                    else
+                        first = false;
+                }
+                out.push('}');
+                return out;
+            }
+            return out;
+        }
+        out.push('null');
+        return out;
+    case 'unknown':
+    case 'undefined':
+    case 'function':
+        out.push(u);
+        return out;
+    case 'string':
+        out.push('"')
+        out.push(arg.replace(/(["\\])/g, '\\$1').replace(/\r/g, '').replace(/\n/g, '\\n'));
+        out.push('"');
+        return out;
+    default:
+        out.push(String(arg));
+        return out;
+    }
 }


追記
[はてな][ネット]Twitterとはてなスターは相性がいい」こちらで紹介して頂きました。
一応、私の手元で動いている物をアップします。
はてな事務局さん、問題があればご連絡下さい。
HatenaStarEverywhere.user.js
Posted at by




はてなブックマークは、はてなブックマークだからしょうがないのです。
はてブ批判をすると、何故か釣りと言われる
よく考えてみよう。書き逃げのネガティブコメントを行った場合、その人は発言に責任を負わなくなってしまう。それはよくない。電車での痴漢と同じである。

idも明記されているしpermalinkもあるはてなブックマークコメントを「書き逃げ」と感じる理由がよく分からない
公開の場にテキストを書くという意味ではblogと何ら変わらない責任が発生すると思っています。

普通の人にとっては書き逃げだと思います
管理人とコメンテーターとは、とても対等とはいえないと思います。管理人はコメントを承認制にしたり、削除したりできるし。逆にコメンテーターは、邪魔臭いコメントを大量に書いて嫌がらせすることも可能です。

続きを読む...

Posted at by




はてなスターの☆をクリックする度に画面を鮮やかに彩るグリモン書いた」は好評で、はてなスター日記でも取り上げて頂きました。
ただ、はてなブックマークでは、「Internet Explorerの為見れない」といったコメントを頂きました。

現在、Hatena Beautiful Starは
  • Firefox
  • Opera
  • Safari
にて動作確認をさせて頂いておりますが、実はInternet Explorerでも動かせない訳ではありません。

続きを読む...

Posted at by




どこでもスターグリースモンキーがSafariとfubに対応しました
Operaのuser.jsに対応してみました。
たぶん行けそう
patchの適応の仕方が分からない人は、下の「+」が付いてる行だけ抜き取って先頭の「+」を全部削除、その後「HatenaStarEverywhere.user.js」の一番おしりに貼り付けて保存するか、はてな側の対応を待ちましょう。
--- HatenaStarEverywhere.user.js.orig   Mon Oct 01 10:37:41 2007
+++ HatenaStarEverywhere.user.js    Wed Oct 03 12:44:20 2007
@@ -139,3 +139,37 @@
         }
     });
 }
+
+if (typeof unsafeWindow == "undefined") {
+    var unsafeWindow = window;
+}
+if (typeof(GM_setValue) != 'function') {
+  function GM_setValue(key, value) {
+    document.cookie = [
+      name, '=', escape(value),
+      ';expires=', (new Date(new Date() + 365 * 1000 * 60 * 60 * 24)).toGMTString()
+    ].join('');
+  }
+}
+if (typeof(GM_getValue) != 'function') {
+  function GM_getValue(key) {
+    var r = new RegExp('/' + name + '=([^;]*)/'), m;
+    if (m = document.cookie.match(r)) return unescape(m[1]);
+    return value;
+  }
+}
+if (typeof(GM_setValue) != 'function') {
+  function GM_xmlhttpRequest(opt) {
+    var x=new XMLHttpRequest();
+    x.onreadystatechange=function() {
+      switch(x.readyState) {
+        case 4:
+          opt.onload(x);
+          break;
+      }
+    };
+    x.open(opt.method,opt.url,true);
+    x.setRequestHeader('Content-Type',opt.mime);
+    x.send(null);
+  }
+}
追記1
すみません。はてな内リソースでしか有効にならないようです。もう少し考えてみます。(_ _;)
追記2
修正はこちら
Posted at by




クリックすると、お星様がいっぱい!
その名も「Hatena Beautiful Star」

続きを読む...

Posted at by




はてなブックマークのコメントをニコニコ動画コメント風に流すスクリプト書いてみた...
この記事にはてなブックマークコメントを付けると、下部に流れ出します。
もしかしたらJSONに反映されるまで流れないかもしれません。
Firefoxではjavascriptからのmarqueeが生成出来ないので、こちらのサイトのスクリプトを使わせて頂きました。

使い方は簡単。上記JavaScript Marquee Demoからmarquee.jsを、また下のリンクからhatebu_ahhhhh.jsをダウンロードし、scriptタグで読み込みます。あとはHTML本体に"HatebuAhhhhh"というIDをもったDIVを一つ用意します。
注意:はてなスターのように複数設置する事は出来ません。パーマリンクに一つとお考え下さい。
注意:はてブはパーマリンクにお願いします。

続きを読む...

Posted at by




リンク先のはてなブックマーク数を表示するブックマークレットは、del.icio.usで使うと面白いのが分かった。
ただそんだけ...

delicious_hatena
Posted at by




もう、いっそ...
掲示板...[作成]
アドレス帳...[作成]
こんな風にして、「1クリックで出来るWebアプリケーション」...
とか「3秒で出来るWebアプリケーション」...ってのは駄目?

それか注目度を逆利用して

「30日掛けて作り上げる、高品質Webアプリケーション」...なんて本があったら、売れるでしょうか...苦笑

さ、アフォな事考えとらんと、さっさと寝よう...
Posted at by




既に記事タイトルに効力がなくなってます...汗

昨日は、C++でmixiの足跡APIを叩いてみましたが今日はpythonと行きましょう。
ただ、そのままpythonライブラリ...というのも面白くないので、本日リリースされたIronPython2.0アルファ2に合わせて、IronPythonでやってみようと思います。
基本的には、もうVisual Basic.NETです。pythonらしきソースが無くなってます。

続きを読む...

Posted at by




IMAPが来ない来ないとわめいてましたが...
Gmailの言語設定画面で「English(US)」に設定したらIMAPの設定項目が現れました。
gmail_lang_en
まだかまだかとお悩みの方、一度試して見られてはどうでしょう?
gmail_setting_imap
今はGoogle独特のポートに接続出来ない環境なので実際に試せませんが、後で試してみたいと思います。
Posted at by




もうタイトルは無視して頂いてかまいません。

さて、先日からC++版IronPython版CPython版とmixi APIを使ったコードを書いてきましたが、今日は実際にアプケーションを作ってみたいと思います。ただ先日も書いた通り、現状のmixi APIは足跡くらいしかWebAPIとして使える物がなく、POST(PUSH)やDELETEなどの更新系メソッドも無い為、純粋に、ただ純粋に、足跡をXMLデータとして参照するくらいしか出来ない状況にあります。

さっさと他のAPI出してくれなきゃブログネタが持たないよ...

唯一できそうなものとして、足跡監視ツールくらいですか...
先日作ったCPython用のMixiAPI.pyを使って足跡をポーリングし、新しい足跡があったらお知らせするってシステムを作ってみましょう。
作り方は簡単。get_footstamps()でfootstampディクショナリのリストを受け取り、前回値と比較します。日付ソートした状態でIDが異り始めれば、最初のレコードから違ったレコードまでが新規足跡になる訳です。
以下のソースでは、相異点毎にコンソール出力しています。コンソール出力部分の直下に、「firefox-remote」等を引数「link」で起動するよう修正すれば、「自動足跡踏み返し機」が出来上がる訳です。これを使えば貴方のたるんだ腹筋もどんどん引き締まっていきます。

はぁ...こんなものしか作れないAPIって...
もう...寝ます...

#しかしこのAPIサーバ、よく失敗を返しますね...

MixiAPI.pyにHTTPステータス判定を追加しました。
以下のソースと合わせ、下のダウンロードリンクからダウンロード願います。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import time
import MixiAPI

if len(sys.argv) < 3:
    sys.exit()

service = MixiAPI.Service(sys.argv[1], sys.argv[2])
oldstamps = []
while 1:
    try:
        footstamps = service.get_footstamps()
        # 比較対象がある場合のみ処理
        if len(oldstamps) > 0:
            # 更新日付でソートする(降順)
            footstamps.sort(lambda x, y: cmp(y['updated'], x['updated']))
            n = len(footstamps)
            for i in range(n):
                # idが異なり始めるインデックスを取得する
                if footstamps[i]['id'] == oldstamps[0]['id']:
                    break
            # 全てが異なる場合は全項目検知とする
            i -= 1
            while i >= 0:
                print footstamps[i]['title']
                # ここに footstamps[i]['link'] を引数に持った
                # firefox の起動コマンドを入れておけば、
                # 足跡踏み返しシステムが出来上がる。
                i -= 1
        oldstamps = footstamps
        # del oldstamps[0]
    except:
        pass
    time.sleep(3)

ダウンロード:,
Posted at by




その1:RSSからstrfile形式のファイルを作る

まず、XML::RSSが入ってなかったのでcpanから入れた。
その後、anontwit/twitterのRSSフィードを適当にstrfile形式にするperlを書く。
#!/usr/local/bin/perl

use encoding 'utf-8';
use strict;
use LWP::Simple;
use XML::RSS;

my $content = get('http://twitter.com/statuses/user_timeline/anontwit.rss');

my $rss = new XML::RSS;
eval {
$rss->parse($content);
};

print "%\n";
for my $item (@{$rss->{'items'}}) {
print "$item->{title}\n%\n";
}

exit;

その2:fortune形式に変換して設置する

なんでstrfileって、/usr/sbinに入ってるんだろ...
# mkdir ~/.anontwit
# ./anontwit.pl > ~/.anontwit/anontwit
# /usr/sbin/strfile ~/.anontwit/anontwit ~/.anontwit/anontwit.dat

その3:メーラに仕込む

愛用のメーラ「mutt」の設定ファイル、~/.muttrcにシグネチャ設定を入れる send-hook '~A' 'set signature="fortune ~/.anontwit|"'

その4:メールを書く

anontwit

その5:殺伐とした空気を味わう

殺伐とした空気を味わいながら、ただただ返信を待つ。





良く考えたら、メール書く際に最新取ってくるってのもいいかも。あとtwitterって20件までしかデータ取れないのね...orz。
Posted at by




mixiの足跡APIをC++、libxml2、libcurlで書くと、どんなに長いソースになるかを実証する。
AtomPP/WSSEなんかやめちゃえ...

perlなら20数行だし、pythonで書いても大した事にはならないだろう...。
Basic認証ならまだしも、WSSEなんか使ったら敷居も高いし、派生アプリケーションが出てこなくなるのはもう分かってるはず。

C++で書くと、こんな事になるんだ...
#以下ソース
#例によって適当クオリティなので添削し放題です。

続きを読む...

Posted at by




IronPythonでのmixi API操作は既に出来てますので、CPythonで動く物を作ってみました。
とは言っても、まだ足跡APIしか出ていませんので、これからAPIの公開に従い順次追加していく予定です。

メソッドは今のところ、get_footstamps()だけ。
いずれ
  • post_diary
  • edit_diary
  • delete_diary
  • get_friends
等といったメソッドが増えていくのではないか...と思います。
とりあえず、get_footstamps()の使い方は、以下のようなイメージです。
import MixiAPI

service = MixiAPI.Service("user@example.com", "password")
footstamps = service.get_footstamps()
for footstamp in footstamps:
    print footstamp['id']
    print footstamp['title']
    print footstamp['link']
    print footstamp['updated']
    print footstamp['author']['tracks:image']
    print footstamp['author']['tracks:relation']
    print

あれ?mixi APIのAtomPP/WSSE批判はどこいった?
ダウンロード:
Posted at by




MixiAPI.pyで現状出来ていたのは足跡一覧取得だけでしたが、photoアルバムに対応してみました。
現在動作するAPIは以下の通り
  • get_footstamps: 足跡一覧取得
  • create_album: アルバム作成
  • get_albums: アルバム一覧取得
  • upload_photo: 画像アップロード
なんか、このAtomAPIしっくりきません。
以下、疑問に感じたもの
  • 全てのサービスを束ねるルートエントリポイントがない
  • http://mixi.jp/atomはHTTP/404。足跡も、日記も、photoも全てを束ねたルートエントリポイントがない。
  • アルバムを作成出来るが、削除できない
  • photoアルバムのURLにDELETEメソッド、X-Http-Method-OverrideヘッダでDELETE等送信してみたが、HTTP/405やHTTP/400が返る。
  • アルバムのURLにGETでアクセスしてもphoto一覧が取得出来ない
  • Atomならば普通、一覧が返ってきて欲しいところ...
まぁ、誰もまだ「API公開」と公に言った訳じゃないから、仕方ないのかもしれないけど...
こっそりmixi stationなんかで公開すr

とりあえず、以下からダウンロード

ダウンロード:

#アルバム作成、画像アップロードのサンプルはMixiAPI.py本体に記述してあります。
Posted at by




PLAYLOGMashUp Awardが開催されています。
適当にYahoo Pipes!でパイプってJSONで取得するものを作ってみました。
URL:PLAYLOG Playlist(JSON)
ただ、Yahoo Pipes!側が悪いのか、日本語を含むアーティストが文字化けを起こしています。だれか解決方法知ってたら教えて下さい。
使用は適当にどうぞ... <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script type="text/javascript"><!--
if (window.addEventListener) window.addEventListener("load", loadScript, false);
if (window.attachEvent) window.attachEvent("onload", loadScript);
function loadScript(){
  var script = document.createElement("script");
  script.charset = "utf-8";
  script.src = "http://pipes.yahoo.com/pipes/pipe.run?...";
  document.body.appendChild(script);
  document.getElementById('result').innerHTML = "<em>reading...</em>";
}
function callback(data){
  var html = "";
  for(n = 0; n < data.count; n++) {
    link = data.value.items[n]['playlog:artistlink'];
    artist = data.value.items[n]['playlog:artist'];
    title = data.value.items[n]['title'];
    html += "<a href=\"" + (link ? link : "javascript:void(0)") + "\">";
    html += (artist ? artist : "unknown") + " - " + (title ? title : "unknown");
    html += "</a><br />";
  }
  document.getElementById('result').innerHTML = html;
}
// --></script>
<title>Sample</title>
</head>
<body>
<h1>PLAYLOG</h1>
<div id="result"></div>
</body>
</html>
Posted at by




たんなる独り言。
mixiのあしあとAPI発掘 - ZeroMemory
先日mixi stationで足跡APIが出ている事が判明したらしいのですが、結局mixiがすんなりAPIとして公開しないのは...
  • 公開したAPI保守にどれだけ時間を取られるか想定出来ない
  • アタックでサーバダウンした時のユーザ対応が想定出来ない
  • リスクが想定出来ない
  • メリットも想定出来ない
  • 不正に情報が漏れてしまった場合のユーザ対応が想定出来ない
こう見えてしまう。

そして時間が経つにつれて、どんどん公開し辛くなって行くんでしょうね。
今回の足跡APIは、「足跡ならば...」と思ったのか、「今のところ足跡しか...」と判断したのかは分かりませんが、ちゃっちゃと...^H^H^H
もし日記APIを公開するとなったら、AtomPP/WSSEなんだろうなぁ...。公開されたら自前のブログエディタで対応しようかなぁ...。

さぁ、これをキッカケに開けチャッカー!...じゃなく開けAPI!
Posted at by




コトバコAPIが公開されています。
RESTを使って記事投稿出来ます。iframeを隠して使えばWeb上にも投稿画面を貼り付けられそうです。
とりあえず、動くものとしてHTAアプリにしてみました。
アクセスキーは、ログイン後に「ツール」を開くと確認出来ます。

cotobacohta

ダウンロード:
Posted at by




データだけ乗っけてscriptソース載せず...ってのは好きじゃないなぁ...
otsuneさんあたりに、「元ソース記事載せないのは好きじゃないなぁ...」と言われそうだけど

use strict;
use Web::Scraper;
use URI;
use YAML;

my $emoji = scraper {
  process '//table[@width="100%" and @cellpadding="2"]//tr/td/font/../..',
    'emoji[]' => scraper {
      process '//td[2]/font', code => 'TEXT';
      process '//td[3]/font', char => 'TEXT';
    };
  result 'emoji';
};

my @urls = (
  'http://72.14.253.104/search?q=cache:http%3A//developers.softbankmobile.co.jp/dp/tool_dl/web/picword_01.php',
  'http://72.14.253.104/search?q=cache:http%3A//developers.softbankmobile.co.jp/dp/tool_dl/web/picword_02.php',
  'http://72.14.253.104/search?q=cache:http%3A//developers.softbankmobile.co.jp/dp/tool_dl/web/picword_03.php',
  'http://72.14.253.104/search?q=cache:http%3A//developers.softbankmobile.co.jp/dp/tool_dl/web/picword_04.php',
  'http://72.14.253.104/search?q=cache:http%3A//developers.softbankmobile.co.jp/dp/tool_dl/web/picword_05.php',
  'http://72.14.253.104/search?q=cache:http%3A//developers.softbankmobile.co.jp/dp/tool_dl/web/picword_06.php',
);
my $res;
foreach my $url (@urls) { push @$res, @{$emoji->scrape(URI->new($url))} };
warn Dump $res;
Posted at by




以前書いた、「ブラウザを全く使わずにustream.tvを楽しむ方法」という記事、なかなか好評だったのですが、 IRCチャットはともかくスタンドアロンFLASHプレーヤがLinuxしかないので、Windowsの方にはそれ程ありがたい記事では無かったかもしれません。

erogeek-con
※Linux版スタンドアロンFLASHプレーヤ

って事でWindows版のUstreamプレーヤを作りました。
  • GUIツールキットはFLTK2
  • FLASHはブラウザが使用しているActiveXだけを表示
  • 入力エリアにチャネル名を入力するだけで再生
名前はFLTK2アプリケーションには必ずと言っていい程、先頭に「FL」が付くので、「flUstPlayer」としました。
実行画面はこんな感じです。

続きを読む...

Posted at by




私が自宅で使っているPC(ノート)はそれほど良いスペックではありません。それでもやっぱりC言語のソースをコンパイルしたり、重たいスクリプトを走らせたりする事があります。そんな時、横で淡々と流れていて欲しいustream.tvがブラウザ内をデカデカを乗っ取って、しかもリソースをどんどん食っていくのはとても耐え切れなかったりします。
ただもう最近は、ブラウザを使わずustream.tvを楽しんでいます。
今日はその方法をご紹介。

映像/音声ですが、Linux版に用意されているスタンドアローン版flashplayerを使っています。ネット上にあるFLVもコマンドラインから起動して閲覧出来ます。
# gflashplayer http://www.example.com/path/to/flv/example.flv
ustream.tvでは、flashを全画面で開く「Open in larger window」というリンクがありますが、このリンクはustream.tvのFLVプレーヤへの直接リンクとなっています。ですからこれをgflashplayerで起動すれば動画が見れます。
もちろんこのflashは、コントロールパネル付属のプレーヤでしか無く、このflash自身から実際のストリームを再生していますから再生していて中断しちゃった...なんて場合は再接続ボタンを押せばうまく再生し直してくれる場合があります。
この「Open in larger window」というリンクを取得し、gflashplayerに渡して再生する以下のシェルスクリプトを私は用意しています。
#!/bin/sh

GFLASHPLAYER=/usr/bin/gflashplayer

if [ "x$1" == "x" ]; then
  echo "usage: `basename $0` [channel]"
  exit
fi

USC=`curl -s "http://ustream.tv/channel/$1" | grep 'Open in larger window' | sed -e 's!^.*<a href="/\([^"]*\)".*$!\1!'`
if [ "x$USC" == "x" ]; then
  echo "currently offline?"
  exit
fi
URL="http://ustream.tv/$USC"
echo playing $URL
$GFLASHPLAYER "$URL"
このシェルスクリプト「ustplayer」をコマンドラインから # ustplayer erogeek-con と実行しています。
twitterで、followerさんが「http://ustream.tv/channel/xxxxx 見てる」と言えば、この"channel"の後の部分、"xxxxx"を実行引数に渡します。
またchatですが、私が自宅で使っているOSはLinuxですからxchatというX Window/GTK2で動くIRCソフトウェアがインストール出来ます。ustream.tvでは「chat1.ustream.tv/6667」というIRCサーバが公開されていますから、わざわざブラウザを上げるまでもないのです。charsetを"utf-8"にさえしておけばchat出来ます。ただしustream.tvのIRCサーバはustream.tvアカウントの認証が必要になりますから、設定画面等でログインパスワードを設定しておく必要があります。

で、昨日開催された「エロギークカンファレンス」を見ていた時のキャプチャがこれ...

erogeek-con

余談ですが、xchat等PC-UNIX上でIRCソフトウェアを使われる方でお薦めしたいのがbitlbee。xinetdデーモンとしても動作するIRCサーバで、MSN,Jabber,ICQ等各種IMプロトコルを中継してくれます。Jabberを使えばGTalkとおしゃべり出来ますから、xchat一つ起動しておけばtwitterも出来るし、jaikuも出来るし、MSNチャットも出来るし余分なリソースを取られなくて嬉しい限りです。

2007/10/21 追記
ustream.tvのHTMLフォーマットが変更された模様。それにあわせシェルスクリプトを修正
#!/bin/sh

GFLASHPLAYER=/usr/bin/gflashplayer

if [ "x$1" == "x" ]; then
  echo "usage: `basename $0` [channel]"
  exit
fi

USC=`curl -s "http://ustream.tv/channel/$1" | grep 'ustream\.tv/.*\.usc' | sed -e 's!.* src=\&quot;http://ustream\.tv/\([^&]*\)&.*$!\1!'`
if [ "x$USC" == "x" ]; then
  echo "currently offline?"
  exit
fi
URL="http://ustream.tv/$USC"
echo playing $URL
$GFLASHPLAYER "$URL"
Posted at by




virtualearth

Microsoftの地図サービス、Live Search Mapsがいつまでたっても使えません。
空へはばたけるのは、いつの日か...
Posted at by






そう...我々がwebなのだ
Posted at by




携帯電話でブラウズしている時に突然、はてなブックマークに登録しておきたくなったりしませんか?
はてなブックマークから画面遷移している時は、はてな専用携帯変換ページが表示されブックマークへのリンクも付けられている為「このページはてブしたい」と思った時にも簡単にはてなブックマーク出来るでしょう。
でも直接そのページを見ている時には携帯では術がありません。
私はよく
  1. 携帯でブラウズ
  2. 携帯のブックマークに登録
  3. PCでブックマーク登録
という煩わしい作業をしてたりします。
例えば、専用ページを作ってリファラを見ても画面遷移では無いので登録しようとしているブックマークURLは取得出来ません。

これを何とかしようと言うのが今日のお話。
殆どの携帯電話には、「URLでメールを送信」という機能が備わっています。
これを使い、サーバ側に登録させるのです。
ニワンゴ開発サイトに「hatebu」というコマンドを作りました。
ニワンゴ開発サイトは、簡単な形式で記述されたメールからコマンドとしてWebAPIをキック、その応答をメールで返すという機能を提供してくれています。
この「hatebu」コマンドから呼ばれるサーバ側CGIとして以下の様なphpのコードを置きました。
※phpなんか久しぶりに書いたのでかなり適当
※何故phpなのかというと、無料で使えてソケット系APIが動くサーバで見つけたのがphpしか動作しなかったから...

<?php
header('content-Type: text/plain; charset="Shift_JIS"');

# URLかどうか...(適当)
function isUrl($str) {
  return (preg_match('/^(https?)(:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)$/', $str) && $str!="");
}

# コマンドとパラメータ
$c="";
$p2="";
$subject="";

if(isset($_REQUEST['c'])){
    $c = $_REQUEST['c'];
}
if(isset($_REQUEST['p2'])){
    $p2 = $_REQUEST['p2'];
}
if(isset($_REQUEST['subject'])){
    $subject = $_REQUEST['subject'];
}
$c = mb_convert_encoding($c, "SJIS");
$pi = mb_convert_encoding($pi, "SJIS");
$subject = mb_convert_encoding($subject, "SJIS");

# コマンドのチェック
if ($c != 'hatebu|| $p2 == "" || !isUrl($p2)){
  echo "response=ERROR\n";
  echo "subject=".$c." ".$p2."\n";
  echo "body=Failed\nInvalid parameter \"$c\" \"$p2\" \"$subject\"";
  exit;
}

# AtomPubで送信
error_reporting(E_ALL);
require_once('class.atomapi.php');
require_once('class.wsse.php');

$username = 'xxxxxxxxxxxxxxxx';
$password = 'xxxxxxxxxxxxxxxx';
$endpoint = 'http://b.hatena.ne.jp/atom/post';
$auth     = 'WSSE';

$entry = new AtomEntry();

$entry->set_title('HATENA');
$entry->set_content('BOOKMARK');
$entry->set_summary($subject);
$entry->add_link($p2, 'related', '', 'text/html');
$auth_obj = new WSSE($username, $password);
$post = new AtomRequest('POST', $endpoint, $auth_obj, $entry->to_xml('POST'));
$post->exec();
if ($post->error()) {
  echo "response=ERROR\n";
  echo "subject=".$c." ".$p2."\n";
  echo "body=Failed\nInvalid parameter \"$c\" \"$p2\" \"$subject\"";
} else {
  echo "response=SUCCESS\n";
  echo "subject=".$c." ".$p2."\n";
  echo "body=Succeeded";
}
exit;
?>
この状態で、ニワンゴ開発サーバからコマンドを発行する為に
m at open dot niwango dot jp
※「at」は「@」に、「dot」は「.」に置き換えます
というアドレスへ hatebu [URL] ※[URL]はとりあえず、http/httpsのみにしました。
とうい本文のメールを送信すると、はてなの「i_am_not_mattn」というアカウントのはてなブックマークに[URL]で指定したサイトがブックマークされます。メールの題名がブックマークコメントになっています。
適当に使ってみて下さい。
※なお、ニワンゴ開発OpenAPIの仕様で私にメールアドレスがばれる事はありません。
※如何わしいサイトが登録された場合はアカウント停止します。

アプリとして言うならば「i_am_not_mattn」のアカウントだけでしか動作出来ないのが苦しい所...
まさかメール本文にパスワード書くってのも嫌だし...
専用サーバでqmailからキックさせるか、sidebar.jpでXMLRPCをキックさせるのが良いんでしょうね。
Posted at by




microsummaryという言葉をご存知でしょうか。microsummaryはMozilla Groupが提案しているWebの要約表記方法の事を指し、ブラウザのブックマーク等で動的に更新されるコンテンツを生成出来る機能です。
Microsummaries - MozillaWiki
例えば、貴方が任意のWebサイト一覧を作り、そのWebサイト毎の最新サマリを日々更新したと思った場合どうするでしょうか。 Web::Scraperを使って、そのサイトの本文もしくはタイトルらしき辺りをスクレイピングするでしょうか。 もしくはmicrofomatsを使ってエントリを一覧し、その先頭を最新と信じて出力するでしょうか。

これらはコンテンツ製作者の意図とそれを二次利用する者とで事前に
  • サマリは一番最初のコンテンツのタイトルである
  • サマリは<title>タグに含まれている
  • サマリとして使用して良いサイトである(もしくはない)
  • サマリは2時間おきに更新される可能性がある
などと言った情報が交換されていない事が原因で、現状コンテンツ制作側からは何も公開していない事になります。
それを実現する機能がmicrosummaryです。

microformatsは既存のコンテンツに対するrel属性およびclass属性を定義する事で、二次利用者側に欲しいコンテンツの場所を示しています。しかしこれでは複数の情報を結合したサマリを生成したり、不要な情報を省く事が難しくなる。つまりプログラマブルでは無い。microsummaryは、link要素に指定したURLに格納されるmicrosummary定義ドキュメントに埋め込まれるXSLT(XML Stylesheet Language Transformations)をコンテンツ自身に適応する事により、microformatsよりもより柔軟なサマリを提供出来るようになっています。

まず、このサイトのHTMLを見て下さい。
<link rel="microsummary" href="http://mattn.kaoriya.net/microsummary/entry_title.xml" />
という部分に、microsummary定義ドキュメントへのパスが記述されています。
そしてそのドキュメントの中身には <?xml version="1.0" encoding="UTF-8"?>
<generator xmlns="http://www.mozilla.org/microsummaries/0.1" name="Big Sky - Entry Title">
  <template>
    <transform xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0">
      <output method="text"/>
      <template match="/">
        <value-of select="html/head/title"/>
        <text> - </text>
        <value-of select="//div[@class='xfolkentry'][1]/h3"/>
      </template>
    </transform>
  </template>
  <update interval="240"/>
  <pages>
    <include>^http://mattn\.kaoriya\.net</include>
  </pages>
</generator>
となっています。
この定義ドキュメントのpages要素を見て下さい。
pages/includeには、そのサイトでmicrosummaryを適応すべきURLパターンが記述されています。
つまりコンテンツ製作者側が、サイトページに応じてmicrosummaryを出力するかしないかを定義出来る事になります。
次にtransform要素。ここはXSLTスタイルシートと同じ構成になっている。上の例ではこのページのHTMLにある、html/head/title要素と、xfolkentryというクラス名の付いたdiv要素直下にあるh3要素を、結合しサマリとして扱うと定義しています。
もちろん、XSLTなのでもっと複雑なサマリを生成する事も出来ます。
また、update要素ではサマリの更新間隔を指定する事もでき、通常30分である更新時間を任意に指定する事も出来ます。

microsummaryは、はてなダイアリーにおいても既に導入されておりhttp://d.hatena.ne.jp/microsummary/entry_title.xmlに定義ドキュメントが格納されています。これにより、例えば「はてなスター日記」をFirefoxでブックマークすると、最新記事のタイトルがサマリーとして生成され、上の記述にもある様に240分毎(update要素)にサマリが更新されるブックマークをコンテンツ製作者側から提供する事が出来ます。
microsummary-hatenastar
利用方法では、ニュースのヘッドラインを流す事も可能です。
参考:FirefoxのブックマークツールバーでYahooヘッドラインニュースを流す方法
さらに、ライブドアリーダのフィード詳細画面では、購読者数とレートがmicrosummaryとして配信されており、動的なブックマーク(ライブタイトル)を実現出来ます。 例えば私のサイトのフィード詳細でもmicrosummaryが配信されています。以下のブックマークレットで確認出来ます。
参考:しげふみメモ:livedoor Reader購読者数のライブタイトル
この他、定義ドキュメントに記述出来る要素については
Microsummary XML grammar reference - MDC
に日本語訳化された物があるので興味のある方は参照してみて下さい。
なお、MozillaのドキュメントWikiにあるMicrosummaries - MozillaWiki Standardizationという項目では、microsummaryが記述されるlink要素はmicroformatsとして標準化されるべきだ、との文章もあります。いずれmicroformatsのDraftに入ってくるのかもしれません。

さて、このエントリを作成するにあたって、以前microsummaryの記事でご紹介したbookmarkletを改造し、microsummaryを表示するブックマークレットを作ってみました。
※IE6、Safariでは動作しません。動作確認は、FirefoxとOperaにて行っています。
bookmarlet:microsummary
表示しているドキュメントのlink要素の内、ref="microsummary"の物を検索し、定義ドキュメントをXHRで取得しXMLDOMとしてパース。その後pages/includeにURLがマッチしている事を確認した後にXSLTプロセッサでサマリを生成する。といった仕組みです。
IE6で動作しない理由は、XSLTプロセッサに渡すドキュメントエレメントはXMLである必要があり、document.documentElement.innerHTMLを渡してもパースエラーになってしまう為です。また、SafariではtransformToDocumentにdocument自身を渡すとエラーとなっています。

ちなみに、HTMLを無理やりXHTMLに変換し、MSXML2.DOMDocumentで処理するコードの残骸が入っていますがお気になさらず...

microsummaryはおそらくブラウザのブックマークに限られた話ではありません。
冒頭で述べた様に、コンテンツサマリ一覧を作成する様な二次利用者に対しても有用で、CGI等も必要とせず、かつ記事のエントリに付与されたclass属性を壊す事無くサマリを集約出来ます。おおよそGRDDLのサマリ版といった所だと認識して頂けると思います。
ただし、現状のmicrosummaryでは1つの定義ドキュメント内に1つのスタイルシートしか記述出来ず、URLパターンに応じたスタイルシートを選ばせる事が出来ない上、出力する内容も1つに限られてしまいます。
これはブックマークのサマリとして登場したmicrosummaryの性でしょうか...。

上で述べた様に、膨大なコンテンツを扱う二次利用者側からすると、microformatsで得られる情報は大きすぎ、かつコンテンツ提供者側が意図しない内容になってしまう可能性もあります。これを解決出来るmicrosummaryに、少し期待出来るのでは?と考えています。
Posted at by




最近、「今何してる?」でつながり合うTwitterというサービスで遊んだりしてます。

http://twitter.com/

基本的にはチャット、しかも「垂れ流し」系のチャットで、どちらかというと「カテゴリ無し」で「オーナー無し」の掲示板とも言えます。

SNSを歌うだけあってか、friendsと呼ばれる、お気に入りユーザを互いに登録する機能もあります。

ただfriendsといっても「垂れ流し」ですから、どちらかと言うと各ユーザがラジオのパーソナリティのようで、friends登録はラジオのチャンネルを登録しているって感じです。

あくまで、その時に、あの人が、何をしているかを互いに晒し合って楽しみます。

いわば束縛の「ゆるい」、SNSと言って良いかもしれません。

最近のSNSのように日記記事1件に対して、複数のコメントやレスが繋がる形式だと、「読み逃げ」等といった、「一文にも値しない価値観」に悩まされたりしますが、Twitterの場合は「垂れ流し」ですから、書く側は「見られてなくてもいい」、見る側も「コメントなんか要らない」といった割り切りが最初から存在するんです。
前に書いた記事のコメントにレスしなきゃ新しい記事が書けない!そんな心配も要りません。

チャットだから見逃したら、そこで話題はお終いって所が、書く側も、見る側も認識出来るのです。

最近、mixi等SNSに疲れを感じる人には良い保養所になりそうな気がします。

ちなみに私はココにいます。

http://twitter.com/mattn_jp

「垂れ流し」ですから、レスもないかもしれませんけど、気軽にfriends登録して頂いても構いません。
Posted at by




# telnet baka-kyoudai.com 80
GET / HTTP/1.0
Host: baka-kyoudai.com:80
User-Agent: Anchan/0.1 (compatible; IKARIYA 6.0; DRIFTERS 5.1)

兄:ドンドンドン!ドンドンドン!
弟:「誰だ?」
兄:「あんちゃんだよ。お前のあんちゃんだよ」
HTTP/1.1 401 Unauthorized
Server: Apache-Drifters/1.1
Pragma: No-cache
Cache-Control: no-cache
Expires: Thu, 01 Jan 1970 09:00:00 JST
WWW-Authenticate: Basic realm="Resource Of Brothers"
Content-Type: text/html;charset=utf-8
Content-Length: 979
Date: Thu, 27 Sep 2007 12:48:36 GMT
Connection: close
...
弟:「ほんとに俺のあんちゃんか?」
兄:「ほんとにあんちゃんだよ」
弟:「それじゃあ、これに答えてみろ。電車に必要なのは乗車券、飛行機に必要なのは搭乗券、じゃあ映画に必要なのは?」
GET / HTTP/1.0
Host: baka-kyoudai.com:80
User-Agent: Anchan/0.1 (compatible; IKARIYA 6.0; DRIFTERS 5.1)
Authorization: Basic dGFrYWt1cmE6a2Vu

兄:「高倉健」
HTTP/1.1 200 OK
Date: Thu, 27 Sep 2007 12:55:21 GMT
Server: Apache-Drifters/1.1
Content-Length: 176 Content-Type: text/html;charset=utf-8

...

弟:「あっ、あんちゃんだ! 入れよ入れよ」
Posted at by




Share a Tab from Your iGoogle Page

iGoogleでタブが共有出来るようになったようです。

igoogle_shared_tab1

試しに共有するタブを作成し、「Share this tab」を選ぶと

igoogle_shared_tab2

共有したい人にメールを送信出来ます。
メールを受け取ると
igoogle_shared_tab3

こんなメールが来て

igoogle_shared_tab4

iGoogleへの追加画面が表示されます。

別のアカウントにタブを持っていったり、便利なタブ出来たから見てよ!って場合にはつかえそうです。
Posted at by




ブログ引退表明サービスRetiredAPIをpythonから使うサンプルです。
URLを指定すると引退しているかどうかがTrue/Falseで戻ります。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import httplib

def isRetired(url):
    conn = httplib.HTTPConnection("retired.at")
    conn.request('GET', '/api/badge/%s' % url)
    response = conn.getresponse()
    return response.getheader("Location") == "http://retired.at/image/retired.gif"
ちなみに、hkn?さんの日記で試すと、Trueが戻ります。
>> print isRetired("http://d.hatena.ne.jp/hkn/20070829/1188318885")
True
Posted at by




とうとうやってくれました。
PLAYLOGでブログパーツが誕生しました。

自分のブログに再生履歴が表示されるようになりました。バチパチ

さっそく入れて見ました。ここの画面右下あたりに出てるのが、そのブログパーツです。

おそらく私が1番に違いない。笑

なんせ発表されてから、1分以内に作業しましたからね...苦笑
Posted at by




きょうじんAPI
ごめんなさい。「きょうじん」が何を意味してるかも知らないんですが、とりあえず...

アナタの「きょうじん」発言は...
Posted at by




Blogging APIは何処に行こうとしているのか...

先日は、XMLRPCについてのお話をさせて頂きました。
今日はその中で出てきたBlogging APIについて。
著名なブログツールの多くは、リモートからブログが更新出来る仕組みを提供しています。
その中でも、一般的な物が先日お話した「XMLRPC」をベースにした「Blogger API」や「metaWeblog API」、「MovableType API」があります。
※現在ではBloggerはXMLRPCではなく、Atompubを使用しています。
それとは別に、Atomフィードを使用したAtomPub APIがあります。
XMLRPCの場合は、リモートメソッドとしてXMLを生成してブログの投稿、削除等を行いますが、AtomPubの場合は送受信されるXMLの単位自身が文書になります。
この文書をHTTPのGET/POST(PUT)およびDELETEメソッドを使用して文書(ブログ)を更新します。
AtomPubではAtomフィードにひもづけられた登録用URIに対して文書をPOST(PUT)する事で新規エントリ、既存エントリにひもづけられた編集用URIに対してPOST(PUT)する事でエントリ更新、そして既存エントリの編集用URIに対してDELETEメソッドを送信する事でエントリ削除という動作になります。
一部のサーバではDELETEメソッドを受け付けない物もあるので、「X-Http-Method-Override: DELETE」というヘッダでDELETEメソッドと同じ処理が行える様になっているサーバもあります。

この2つのAPIの大きな違いとして、文書形式と認証方法が挙げられます。

XMLRPCの場合は、各メソッドに対してユーザIDおよびパスワードを渡す事になります。
またXMLRPCでは送信されるXML自身はメソッドパラメータが含まれ、その中にはユーザIDやパスワードも含まれてしまいます。二次利用する事は出来ません。

AtomPubの場合は、上で説明した通り送受信されるXML自身が文書である為に二次利用も可能です。
また認証方法は一般的にはBasic認証か、WSSE認証が用いられています。

巷のブログサービスは以下の様なAPI実装を行っています。
ブログサービス提供APIフォーマットエントリポイント
teacupXMLRPCHTMLhttp://white.ap.teacup.com/applet/[username]/postmsgrpc
EGOISTブログXMLRPCHTMLhttp://[blogid].ebsystems.jp/xmlrpc.php
自分のブログサイトの先頭に付いているblogid
FC2ブログXMLRPCHTMLhttp://blog.fc2.com/xmlrpc.php
JUGEMXMLRPCHTMLhttp://[blogid].jugem.jp/admin/xmlrpc.php
JustBlogAtompubHTMLhttp://app.justblog.jp/t/atom/weblog/blog_id=[blogid]
Livedoor BlogAtompubHTMLhttp://cms.blog.livedoor.com/atom/blog_id=[blogid]
MSN SpaceXMLRPCHTMLhttps://storage.msn.com/storageservice/MetaWeblog.rpc
NetLaputaXMLRPCHTMLhttp://blog.netlaputa.ne.jp/rpc/mt-xmlrpc.cgi
News HandlerXMLRPCHTMLhttp://blog.nettribe.org/xmlrpc.php
SeesaaブログXMLRPCHTMLhttp://blog.seesaa.jp/rpc/
WordPressXMLRPCHTMLhttp://faq.wordpress.com/xmlrpc.php
Yahoo!ブログXMLRPCHTMLhttp://api.my.yahoo.co.jp/RPC2
BloggerAtompubHTMLhttp://[blogid].blogspot.com/feeds/posts/default
BloggerXMLRPCHTMLhttp://blog.goo.ne.jp/xmlrpc.php
pwBlogXMLRPCHTMLhttp://www.pwblog.com/xmlrpc
VoxAtompubHTMLhttp://[blogid].vox.com/library/posts/atom.xml
はてなブックマークAtomPubTEXThttp://b.hatena.ne.jp/[username]/atom/
はてなダイアリーAtompubはてな記法http://d.hatena.ne.jp/[blogid]/edit
アメーバブログAtompubHTMLhttp://ameblo.jp/servlet/_atom/blog/[blogid]
ココログXMLRPCHTMLhttp://app.f.cocolog-nifty.com/t/api
ドリコムXMLRPCHTMLhttp://blog.drecom.jp/api/xmlrpc
ブログ人XMLRPCHTMLhttp://app.blog.ocn.ne.jp/t/api/
PLAYLOGXMLRPCWikihttp://playlog.jp/_atom/blog/[blogid]
ちなみに、アメーバブログはWSSEヘッダを作成する際、パスワードをMD5した値でSHA1/NONCEを作成する必要があります。
これは正直言ってしまうと、バグとしか思えません...汗
Atompubの場合、実際には上記エントリポイントを直接参照するのではなく、ブログページからオートディスカバリする事が推奨されます。
例えば、私のVoxのサイトのHTMLを参照してみると <link rel="EditURI" type="application/rsd+xml" href="http://mattn.vox.com/rsd.xml" title="RSD" />
というヘッダがあるのが分かるかと思います。
次にこのhrefを参照すると <?xml version="1.0"?>
<rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">
    <service>
        <engineName>Vox</engineName>
        <engineLink>http://www.vox.com/</engineLink>
        <homePageLink>http://mattn.vox.com/</homePageLink>
        <apis>
            <api name="Atom" preferred="true"
                apiLink="http://www.vox.com/services/atom" />
        </apis>
    </service>
</rsd>
このようなXMLが戻ります。ここまでは認証無しに参照出来ます。
このapiノードに記述されているapiLinkにX-WSSEヘッダを付けて要求すると、以下の様な登録/編集用URIが記述されたXMLが返されます。
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://purl.org/atom/ns#">
  <link rel="service.post" href="http://www.vox.com/services/atom/svc=post/collection_id=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" title="matt'n roll" type="application/x.atom+xml"/>
  <link rel="alternate" href="http://mattn.vox.com/" title="matt'n roll" type="text/html"/>
  <link rel="service.feed" href="http://www.vox.com/services/atom/svc=asset/XXXXXXXXXXXXXXXXXX" title="matt'n roll" type="application/atom+xml"/>
  <link rel="service.upload" href="http://www.vox.com/services/atom/svc=asset" title="matt'n roll" type="application/atom+xml"/>
  <link rel="replies" href="http://www.vox.com/services/atom/svc=asset/XXXXXXXXXXXXXXXXXX/type=Comment" title="matt'n roll" type="application/atom+xml"/>
</feed>
service.feedにはAtomフィードURIが、service.postは新規エントリポスト用のURIが格納されています。
Voxの場合は画像ファイル等のアップロード(service.upload)に対応しているようですね。
また、service.feedから取得したフィード自身にはrel属性とtype属性で指定されたlink要素も付与されていますから <entry xmlns:default="http://www.sixapart.com/ns/atom/privacy" xmlns:default1="http://www.w3.org/1999/xhtml">
  <id>tag:vox.com,2007-11-03:asset-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</id>
  <title>スケスケ</title>
  <published>2007-11-03T09:25:46Z</published>
  <updated>2007-11-03T09:25:46Z</updated>
  <link rel="alternate" href="http://mattn.vox.com/library/post/%E3%82%B9%E3%82%B1%E3%82%B9%E3%82%B1.html" title="スケスケ" type="text/html"/>
  <privacy xmlns="http://www.sixapart.com/ns/atom/privacy">
    <allow policy="http://www.sixapart.com/ns/atom/permissions#read" name="Everyone" ref="http://www.sixapart.com/ns/atom/groups#everyone"/>
  </privacy>
  <link rel="alternate" href="http://www.vox.com/services/atom/svc=asset/XXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" type="application/atom+xml"/>
  <link rel="replies" href="http://www.vox.com/services/atom/svc=comment/xid=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" type="application/atom+xml"/>
  <author>
    <name>mattn</name>
    <uri>http://mattn.vox.com/</uri>
  </author>
  <category term="携帯から" label="携帯から"/>
  <content xmlns:default="http://www.w3.org/1999/xhtml" type="xhtml">
    <div xmlns="http://www.w3.org/1999/xhtml">
      <p>今日、とある遊園地の横を通り過ぎた時に嫁が「この遊園地スケスケやなぁ」って言って来た。少し考えた後「うん」と答えた。どうやら嫁は「この遊園地スカスカやなぁ」と言いたかったらしい。ところで「スケスケ遊園地」って、ちょっとエッチ…</p>
    </div>
  </content>
</entry>
このエントリフィードの、type="application/atom+xml"であるlink要素のhrefに対してPOST(PUT)すれば更新、DELETEすれば削除となります。
POST時に送信する文書は、feedからentryのみを抜き取った、丁度上のようなXMLを送信する事になります。
前回、今回とブログサービスが提供するAPIについてお話して来ましたが、最近はAtomPubの方が活発だったりします。
XMLRPCの場合は特に決められたXMLネームスペースが存在していない為、貧祖なXMLパーサでもクライアントを実装でき、それ程敷居は高くないかと思います。
※但し、XMLRPCのパラメータは各サービスによって型が統一されていない場合があります。汎用的に作るのならば苦労するかもしれません。

それに比べAtomPubでは仕様が結構キッチリと決められている為、各サービス毎に異なる仕様で苦しめられる事はあまりありませんが、XMLネームスペース/スキーマを意識してXMLを操作しなければならなくなります。
幾分AtomPubを使ったクライアント実装の方が敷居が高い気もします。
※参考にするならばVoxが良いと思います。

今後、ブログAPIの主流としては、AtomPubがリードして行くと私は思っていますが、クライアントアプリがXMLRPCもAtomPubも対応しなければならない時代は、早く消え去った方が良いのでは?と切に願うばかりです。

ちなみに...
私のサイトではblosxomというブログツールを使用しています。blosxomをXMLRPCで操作しようというライブラリは既に存在しており私も使ってはいるのですが、AtomPubで実装した物はありませんでした。
昨日からですが、少しずつ作り始める事にしました。
ある程度出来上がったらCodeReposに上げる予定です。

追記
id:teahutさんから、ご指摘頂きました。
確かにAtomPub(app)という点で、Voxは参考にならないかもしれません。
app要素の入った新しいAtomPubについては、こちらに仕様書の日本語訳があります。
Posted at by




pythonからSumibiWebAPIを扱うサンプルです。
SOAPpy使うと楽だこと...
#!/usr/bin/python
# -*- coding: utf-8 -*-

from SOAPpy import WSDL

wsdlobject = WSDL.Proxy("http://www.sumibi.org/sumibi/Sumibi_stable.wsdl")
result = wsdlobject.doSumibiConvert(query="sumibi")

for r in result.resultElements:
    print "%s:%s" % (r.type, r.word)

結果は j:炭火
h:すみび
k:スミビ
l:sumibi
となります。
queryが「hanasu」だと j:話す
j:離す
j:食なす
j:放す
j:跳なす
j:撥なす
j:刎なす
j:貼なす
j:張なす
j:はなす
h:はなす
k:ハナス
l:hanasu
となります。
SKKのように送り仮名開始位置が取得出来ればIMEに使えて便利かな...と思いました。
Posted at by




w.google.co.jp -> www.google.co.jp

ww.google.co.jp -> www.google.co.jp

さすがに

wwww.google.co.jp -> エラー

ちなみに

w.yahoo.co.jp -> エラー

ww.yahoo.co.jp -> エラー

Posted at by




otsuneさんがやってたので、まねしー。

なるべくblosxom.cgi本体を弄りたくなかったのもあって、本来はETagとかを返す為に用意されたlastmodified2プラグインを使う事にした。otsuneさんと同じく、もそもろの編集をした後 「Posted at」の部分を Posted at <a href="$url$path/$fn.htm"><abbr class="updated" title="$lastmodified2::story_iso8601">$ti</abbr></a>
とした。とりあえずコノ辺で確かめてみたり、microformat Operatorで行けてそうだから、しばらく様子見。

ところで、microformat Operatorのプラグイン入れすぎで、ツールバーからはみ出してしまってるの、なんとかなりませんか...
↓コレ
microformat-operator-too-long
Posted at by




ソーシャルブックマークサービスと言えども数多くの物が登場してきている。代表的な物ならば、Wikipediaに挙がっている物でリストアップされているので、色々試して見てもいいだろう。
私の知識だけで言うならば、日本人が使ってる有名どころといえば
といったところだろうか。 上記の中でも私が使っている物は となる。なぜこれに絞っているか。それは人の集まりとAPI。

del.icio.us

del.icio.usは基本的にユーザインタフェースが英語の為、日本人向きではないかもしれない。しかし使用者は多い。これは使用者間でのコミュニケーションがあまりない事で逆に、コメントをソーシャルコメントではなくメモとして使う人が多い為。つまり個人の情報収集に向いているからと思われる。基本的にブックマーク登録は個人用の登録エリアか、ブラウザ拡張で行う為、ユーザは既にあるブックマークエントリページをほぼ見ない。これを見るのは被ブックマークエントリ投稿者。だからブックマークエントリページはほとんど検索エンジンからはヒットしない。さらに、はてなブックマークのようにユーザのブックマークひとつひとつにpermalinkが張られる訳では無いのでメタブクマも無く、ブックマークエントリを見て個々のユーザが周りを意識したコメントをする...という事も滅多に無い。
さらにブックマークエントリ毎に公開/非公開を設定出来るのが良い。私個人は技術情報等も合わせ全てdel.icio.usに登録してしまっており、実はブラウザ自身のブックマークにはBookmarkletしか入っていない。
※del.icio.usが死んでたらどうすんの?
なお、del.icio.usはマルチバイト文字が通るが、ブックマークエントリページにてIMEの確定ENTERキーを拾ってしまい、書きかけのエントリがポストされてしまうという不具合がある。これはFirefoxの拡張や、GreasemonkeyでENTERキーの動作を強制するスクリプト等で対応出来る。
(IMEの確定をENTERでなくCTRL+Mでやるのもアリ)
タグはスペース区切りで、ユーザの中にはdel.icio.usでマルチバイトのタグ名を付ける事を敬遠する人もいる。
また、モバイル端末で使うにはオフィシャルではないMobilicio.usを使う方法がある。
※ただし私は未だにMobilicio.usでブックマークを登録した事はない。

del.icio.usは登録照会系APIも提供しており、ブログパーツとしてもWidgetやJSONでエントリを出力するAPIもあれば、現在permalinkに幾らブックマークされているかを画像で返すAPIもある。このサイトも実際に使用しており
ブックマーク追加画面: http://del.icio.us/post?url=[perlmalnk]&title=[title]
ブックマークエントリ: http://del.icio.us/url/[md5_hash(permalink)]
被ブックマーク数画像: http://del.icio.us/feeds/img/savedcount/[md5_hash(permalink)]?aggregate
というリンク/画像をエントリ下部に表示しているので気になる方は試して頂きたい。
※ただし被ブックマーク数画像は他のサービスに比べ画像が数ピクセル大きい為、デザインしづらい。

はてなブックマーク

日本では最大級のソーシャルブックマークサービス。ユーザの集まりも多ければ、いざ人気が出るとブックマークされるスピードも半端ではない。私は以前、まぐれでホットエントリに入ってしまった記事を書いた事があるが、ブックマーク数が2日から3日で200まで到達した。500超えしている人等はもっとすごいんだろうな。
はてなブックマークはソーシャル性が強く、コメントひとつひとつにpermalinkが張られている事もあり、周りを意識したコメントが目立ったり、メタブクマも多く見られる。タグは[タグ名]という形式。[後で読む]タグを使った別サービスもある。
機能としては申し分無いが、公開/非公開が全体でしか設定出来ない事が唯一の難点だと思っている。しかしながら、はてなユーザ間でのメッセージ送信機能(IDコール)や、はてなスターという機能により、はてなの「はてなブックマークはソーシャルを極める」という意識が見えて良い。
時折、このソーシャル性がユーザに対して非難を受ける対象となるが、私個人としては「はてな」は好きだし、ユーザが固まり易いというのは「はてな」というサービスを使っている以上致し方ないと思っている。

私個人は、はてなブックマークをソーシャルな用途で使用している。
はてなブックマークはモバイル対応出来ており、はてなブックマークと連携したモバイル版ブラウザも使える。個人的にはdel.icio.usがモバイルに対応仕切れていない為、携帯からは「はてなブックマーク」に登録しておき、後でブックマーク同期を行う事にしている。
APIも豊富で、登録照会系APIに合わせ、IDコールやはてなスターとの連携機能も提供されている。このサイトでは
ブックマーク登録画面: http://b.hatena.ne.jp/append?[permalink]&h=[title]
被ブックマーク数画像: http://b.hatena.ne.jp/entry/image/[permalink]
というリンクが記事毎に張られている。なお、はてなブックマークはモバイル対応している為、本サイトでは携帯端末の場合だけ
ブックマーク登録画面: http://b.hatena.ne.jp/addmobile?mode=confirm&title=[title]&url=[permalink]
というリンクに変更するようになっている。

livedoorクリップ

個人的にはGoogle Readerを使っているので良く知らないが、livedoor readerと併用する方が多いらしい。ただし私のサイトにブックマークを置いていってくれる人を見る限り、livedoorクリップを使う方は大概他のサービスのミラーとして使っておられる事が多い。私個人はブログの被ブックマーク数表示にのみ使用している。
APIは登録照会系の他、被ブックマーク数画像も提供しており本サイトでも
ブックマーク登録画面: http://clip.livedoor.com/clip/add?jump=myclip&link=[permalink]&title=[title]
被ブックマーク数画像: http://image.clip.livedoor.com/counter/[permalink]
というリンクが記事毎に張られている。ただしモバイルアクセスの場合には
ブックマークエントリ: http://clip.m.livedoor.com/page/detail?link=[permalink]
というリンクに差し替えている。

なお、本サイトではBuzzurlもブックマークツールバーとして表示しているが、Buzzurlのブックマークエントリや登録画面はモバイルからアクセスするとモバイル専用ページが表示される為、上記の様な小細工は必要ない。
ブックマーク登録画面: http://buzzurl.jp/config/add/confirm?url=[permalink]&title=[title]
被ブックマーク数画像: http://api.buzzurl.jp/api/counter/[perlmalnk]

POOKMARK Airlines

個人的には、POOKMARK Airlinesを個人資料用途としてもソーシャル用途としても使っていない。POOKMARK Airlinesの設定でtwitterにも登録する機能があり、これだけの為に使っている。実際は使い込んでいない為、良し悪しが判断出来ていない。



以上が、私個人のソーシャルブックマークサービスの使い方。
結論から言ってしまうと、私はdel.icio.usと、はてなブックマークの2つしか本格的には使っていない。
本当は、他にも手を出して見たいがどうしても人のあつまる所に行き着いてしまっており、食わず嫌いな状態にある。
実際2つしか使っていないので、基本的に手作業で同期し、量が多い時のみPlaggerを使って同期している。

上記、私の様な使い方で他に良い物があれば、コメントやブックマークでも良いので教えて欲しい。
Posted at by




最近、Javaのお仕事をしているのですが、ドキュメントの生成にはもちろんjavadocを使ってます。

javadocではコメントに記述している引数が、実際のメソッド引数にない場合は警告を出してくれます。

ただ、開発者がよくやるのは、メソッドの引数を増やし、その引数のコメントを書き忘れるって行動。

この場合、javadocは警告してくれません。
(実はオプションであったりして...)


で、先日気づいたのですが、ドキュメント生成ツールであるDoxygenがこの記入忘れのコメントを警告で出してくれるという事実。

これは使える...
Posted at by




私はこの業界に入ってから10年以上となるが、10年前といえば

C言語だけ学んでおけば食いっぱぐれる事は無い

と教えられてきた。

だが世の中の情勢も変わり、リッチなコンテンツがインターネットを介して配信され、Webサービスと呼ばれる形となって世の中に普及した。

プログラマとして求められるスキルも昨今はWeb開発経験のスキルが問われ、perl,php,python,ASP.NET等といったライトウェイトなスクリプト言語を使用しているプロジェクトも全く珍しくなくなった。

その昔、「API」といえば誰しもが「Win32 API」と答えたが、いまではすっかりWebアプリケーションに対するリモートプロシージャコールや、サーバアプリケーション操作用のインタフェース郡を意味するのが一般的になりつつある。

ブラウザもしかり、XMLHttpRequestやJSONという技術でこれまでの限界を、そしてドメインを越え、新たな幕を開けようとしている。

Web開発者は何かを得た事になるんだろうか。

市民権?
永住権?

まぁ数年の間は、Web開発で御飯が食べられる、良い時代が過ぎ行くのでしょうね。

Web開発だけで会社を起こしたり
Web開発だけで家を建てられたり
Web開発だけで嫁さんを貰えたりと...(ないか)


でも、私にはそろそろWeb2.0の底(天井?)が見えてきてしまった気がしてならない。

そろそろ、Web3.0への足音が鳴り出してもいいんじゃないか?
そんな気がする。

最近、mixiなんかのSNSよりも、twitterを代表とするゆる系ソーシャルと、それらが提供するAPIによってマッシュアップされた連携サービスが、巷では大流行しているようです。表面はチャットとして位置付け、とてもログ的で、いうなればストリームを意味し、そのストリームをマッシュアップさせて別のアプリケーションとして生み出して行くのです。
もしかしたら、これが未知なる足音への導き?いや違う。

サービスとサービスが、そしてローカルとリモートが隔たり無く、ユーザが行き来すること無く繋がり合える。そんなのがWeb3.0になるんじゃないかと、なんとなく思った、午前3時半でした。


やばい。寝よう。
Posted at by




PyGTKを使ってクライアントを作ってみた。
とりあえずテスト

#うまくいったら近々公開
Posted at by




もっかいテスト
Posted at by




ClickCommentsのようなものを作ってしまったよ!

このページの下(Writebacks)に動くものがあるよ!
クリックすると「なると」が増えるよ!
なんで「なると」なのかは聞かないでね!
使うときは、フレーバに $naruto::link
と書くといいよ!
あくまでblosxomとJSONで何か作ってみたかっただけだから、実用性ないよ!
もしかしたらWeb拍手みたいに使えるかもしれないよ!

blosxom大好きっ子は、これを改造してバラまいてClickCommentsのシェアをどんどん下げちゃえばいいと思うよ!!!

元ネタ
http://d.hatena.ne.jp/amachang/20070807/1186485054
http://d.hatena.ne.jp/Hamachiya2/20070804/browser_crasher
amachang、はまちちゃん、勝手にテンプレート使ってごめんね!><
ダウンロード:

Posted at by




ちょこっと時間が取れましたので、ず〜っと宿題のまま残ってて気になってた件を片付けました。
blosxom-xmlrpc.cgiで、幾らかのブログクライアントを使うと、フォルダ名称(ファイルの存在場所)をカテゴリとして保持するblosxomの制約事項により、setPostCategoriesメソッドを呼び出された事で変わってしまった記事ID(ファイル名)が参照出来なくなる問題です。

申し訳ないですが、適当に解決させていただきました。苦笑

setPostCategoriesが呼ばれたタイミングで、元の記事ファイルを指定のカテゴリ(フォルダ)に移動し、「元のファイル名.post」というファイル名で、どこにリネームされたのかを出力しておく。

もし、getPost等で古い記事IDが参照される場合は、「元のファイル名.post」から、実際の記事ファイルを参照し処理するといった仕組みです。
なにぶん適当すぎて、使用価値があるかどうか分かりませんが、一応ソース差分を添付しておきます。
#「元のファイル名.post」というファイルをいつ消すのかについては、今後の宿題にします。

ダウンロード:
Posted at by




このサイトはPerl/CGIで出来たブログツール、blosxomで構築しています。
個人的には
テキストファイルがデータ本体であり、フレーバと呼ばれるレンダラによってブログエントリ化する。バックアップしたいなら、テキストファイルをアーカイブすればいいし、記事を消したいならば、そのテキストファイルを消せばいい。
って所が好きで、実はこのサイトを作る前から、自分専用のメモシステムとして使ってました。

続きを読む...

Posted at by




blosxomでモブログをするには、2つの機能が必要となります。
  1. XMLRPCでのポスト
  2. メール受信でXMLRPCをキックするサーバ
前者については、「BXR: Blosxom XML-RPC Interface>」というスバラシイものがあるので、これを利用します。
ただ、後者については、サーバを用意出来ない方もいらっしゃると思います。そこで登場するのが「サイドバー.jp」です。このサービスでは、モブログ支援ということで、各アカウント毎に用意されたメールアドレスに対して記事を投稿すると、設定していたXMLRPCサーバをコールしてくれる機能を持っています。

続きを読む...

Posted at by




このサイトのfeedは、Feedburnerを使って焼いていません。いずれdel.icio.us等と一緒に配信させて頂くことになるやもしれませんが、なんとなくブログのフィード購読者数をカウントするCGI作ってみました。
例によって2〜3分で出来た代物なので(ブログ書いてる時間のほうが長いやろ)、中身のクオリティは適当です。
大体の著明なFeed-Fetcherは、ユーザエージェントに「XXX subscriber」という文字列を埋め込んできます。このXXXにそのフィードリーダのsubscriber数が格納されています。
blosxomのloggerプラグイン(もしくはココ)が出力したログファイルから、このsubscriber部を抜き取り著明なフィードリーダ毎購読者数をカウントします。

livedoor FeedFetcher/0.01 (http://reader.livedoor.com/; 23 subscribers) ...
#!/usr/bin/perl

use strict;
my $logfile = "/blosxom/logs/access.log";
#my $logfile = "access.log";
my %agents;
my %bot_ua = (
  'Hatena' => 'Hatena\x20RSS',
  'NewsAlloy' => 'NewsAlloy',
  'FreshReader' => 'FreshReader',
  'Feedpath' => 'Feedpath',
  'Google Reader' => 'Feedfetcher-Google',
  'Rojo' => 'Rojo',
  'Bloglines' => 'Bloglines',
  'Livedoor Reader' => 'livedoor\x20FeedFetcher',
);
open(FILE"<$logfile");
while(<FILE>) {
  my $line = $_;
  $line =~ s/^([^\t]+\t){2}([^\t]+)\t.*/\2/;
  if ((my $ua) = grep {$line =~ /$bot_ua{$_}/} keys(%bot_ua)) {
    $line =~ s/^.* ([0-9]+) subscriber.*\n$/\1/;
    $agents{$ua} = $line if ($agents{$ua} < $line);
  }
}
close(FILE);

print <<EOF
Content-Type: text/html;

<html>
<head><title>Your Feeds Subscriber</title><head>
<body>
<table border=\"1\">
EOF
;
my $sum = 0;
while(my ($key$value) = each(%agents)) {
  print "<tr><td>$key</td><td>$value</td></tr>\n";
  $sum += $value;
}
print <<EOF
<tr><td>total</td><td>$sum</td></tr>
</table>
</body>
</html>
EOF
;

いや、つまらない事しか書いてないのに、購読者がいらっしゃるとは...
※実際にはクライアントサイドで閲覧していらしゃるでしょうから、正しい値とは限りません。
Posted at by




実は、ここのサーバのJcode.pmがちゃんと動いてなかった事に気づいた。

最近トラックバックスパムがやたらと文字化けを起こすなぁ...って思ってたんです。まぁ、相手のサーバの文字コードなんかお構い無しに放り投げてくるのがスパム!な訳ですから、化けてあたりまえ...
いや待てよ、blosxomのwritebackプラグインでJcode::convert変換してたはず。しかもutf8でだ。
で、ようやく重い腰をあげる。

調べてる内に、ここのサーバではJcode.pmがutf8で全滅している事に気づいた。
このサーバではXSは置けないのでJcode::Unicode::NoXSが動いているものとばかり思っていたのだが、いやいや...

うんちゃらかんちゃら、弄ってる内にメンドクサクなってきたので、ついいつもの適当ハック癖...。
Jcode/Unicode/NoXS.pm内のメソッドをJcode::XXXからJcode::_Classic::XXXに一括痴漢^H^H置換して、お茶を濁す。

--- Jcode/Unicode/NoXS.pm.orig  2007-06-26 00:31:47.000000000 +0900
+++ Jcode/Unicode/NoXS.pm   2007-06-26 03:23:49.000000000 +0900
@@ -53,7 +53,7 @@
 # instead of being 'use'd (No package export done) subs below
 # belong to Jcode, not Jcode::Unicode

-sub Jcode::ucs2_euc{
+sub Jcode::_Classic::ucs2_euc{
     my $thingy = shift;
     my $r_str = ref $thingy ? $thingy : \$thingy;
     _init_u2e();
@@ -68,7 +68,7 @@
     $$r_str;
 }

-sub Jcode::euc_ucs2{
+sub Jcode::_Classic::euc_ucs2{
     my $thingy = shift;
     my $r_str = ref $thingy ? $thingy : \$thingy;
     _init_e2u();
@@ -84,21 +84,21 @@
     $$r_str;
 }

-sub Jcode::euc_utf8{
+sub Jcode::_Classic::euc_utf8{
     my $thingy = shift;
     my $r_str = ref $thingy ? $thingy : \$thingy;
-    &Jcode::euc_ucs2($r_str);
-    &Jcode::ucs2_utf8($r_str);
+    &Jcode::_Classic::euc_ucs2($r_str);
+    &Jcode::_Classic::ucs2_utf8($r_str);
 }

-sub Jcode::utf8_euc{
+sub Jcode::_Classic::utf8_euc{
     my $thingy = shift;
     my $r_str = ref $thingy ? $thingy : \$thingy;
-    &Jcode::utf8_ucs2($r_str);
-    &Jcode::ucs2_euc($r_str);
+    &Jcode::_Classic::utf8_ucs2($r_str);
+    &Jcode::_Classic::ucs2_euc($r_str);
 }

-sub Jcode::ucs2_utf8{
+sub Jcode::_Classic::ucs2_utf8{
     my $thingy = shift;
     my $r_str = ref $thingy ? $thingy : \$thingy;
     my $result;
@@ -121,7 +121,7 @@
     $$r_str = $result;
 }

-sub Jcode::utf8_ucs2{
+sub Jcode::_Classic::utf8_ucs2{
     my $thingy = shift;
     my $r_str = ref $thingy ? $thingy : \$thingy;
     my $result;

とりあえず、動いてそげ。
Posted at by




最近は技術情報ばかり書く機会が多く、このブログにメールから投稿する事はあまりなくなりましたが、もしかすると需要はあるかもしれないので...
blosxomをXMLRC経由でポストするスクリプトBXR(blosxom-xmlrpc.cgi)をWindows Live Writerからポストするとサーバ側でパースに失敗していました。
調べた結果、Windows Live WriterはXMLの先頭にBOMを付けてくれるらしく、XMLRPC::Transport::HTTP::CGIがパースエラーを起こしてました。
とりあえず私の所は応急処置で、User-Agentを見て先頭3バイトを削るワークアラウンドで逃げる事にしました。
またWindows Live WriterはcategoryNameとcategoryIdを正しく区別出来ていないようなので、こちらもUser-Agentを見てcategoryIdのみ処理するよう修正しました。
※Windows用にbinmodeしたほうがいいかもしれない。

これで外部XMLRPCキッカー(sidebar.jp等)を使用したモブログ、ブログエディタ等でポスト出来るようになります。

ダウンロード:
Posted at by




Livedoor Readerの海外版、「FastLadder」が公開されました。
FeedFetcherのユーザエージェントは、LDRとそっくり「Fastladder FeedFetcher/0.01」でした。
さて、弄り倒しますか。

fastladder-20070703

あわせて「フィード購読者数カウンタ for blosxom」も修正しました。
ダウンロード:
Posted at by




XMLRPC(XML Remote Procedure Call)は、HTTPを介してXMLを扱い透過的にサーバ側のメソッドを実行するAPI実装です。

最近のミニブログ界ではJaikuがGoogleに買収されてからJaikuに人が入り始め、賑わってから少し経ちます。ミニブログで代表的なTwitterとJaikuは一見同じミニブログに見えて、APIとしては違うものを採用しています。

TwitterのAPIは基本的にRESTとBasic認証および出力フォーマット指定というAPIを採用しており、クライアントを作成する開発者はURLに対してGET/POST出来るライブラリと、XML/JSON/RSS/ATOMの中から自分の使いたいフォーマットを処理出来るライブラリを選ぶ事が出来ます。
またJaikuのAPIはBasic認証ではなくpersonal_keyと呼ばれるAPIKEYとユーザ名で認証します。取得系はTwitterとそれ程変わりませんが更新系は以前にも書いた通りJSONもしくはXMLRPCに限定されてしまっています。つまりJSONで更新したらJSONのレスポンスが、XMLPRCで更新したらXMLRPCのレスポンスが返されます。JSONが扱いにくい言語を使用する場合にはXMLRPCを強制されてしまう事になります。

さて今日はJaikuのAPIに限った話ではなく、Jaikuを使ってXMLRPCのクライアント実装と、XMLRPCサーバについての話をしたいと思います。
まず送受信されるデータについて。

XMLRPCは、決められたXML構成にメソッド名および構造化可能なパラメータをリクエストとして送信し、同じく構造化可能なレスポンスを受け取るXML送受信APIです。
リクエストフォーマットは以下の様な記述になります。
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
  <methodName>メソッド名</methodName>
  <params>
    <param><value><string>パラメータ1</string></value></param>
    <param><value><string>パラメータ2</string></value></param>
  </params>
</methodCall>
またレスポンスフォーマットは以下の様になります。 <?xml version="1.0" encoding="utf-8"?>
<methodResponse>
  <params>
    <param>
      <value>
        <string>結果</string>
      </value>
    </param>
  </params>
</methodResponse>
リクエストパラメータは「params」というノードを括られており、配列になっているのが分かるかと思います。また値が格納される「value」ノードには「<string>」や「<int>」といった、その値の型が定義されています。
上で書いた通り、リクエストパラメータとレスポンスは構造化出来ますので、例えばレスポンスで構造体を表すならば <?xml version="1.0" encoding="utf-8"?>
<methodResponse>
  <params>
    <param>
      <value>
        <struct>
          <member>
            <name>メンバ1</name>
            <value><int>157</int></value>
          </member>
          <member>
            <name>メンバ2</name>
            <value><string>Sample</string></value>
          </member>
        </struct>
      </value>
    </param>
  </params>
</methodResponse>
と書く事が出来ます。
詳しくはXMLRPCのオフィシャルサイトで確認出来ます。またオフィシャルサイトではXMLRPCを使った処理系毎の実装一覧XMLRPCが使えるサービス一覧も確認出来ます。

ところで、Windows Live WriterBlogWriteubicast Blogger等といったブログ投稿ツールはXMLRPCを使用してブログ記事を操作しています。それらAPIは以下の様に分類されています。
  • Blogger API: Bloggerが提供してるAPI
  • metaWeblog API: Blogger APIに足らない部分を補足する形で登場したAPI
  • MovableType API: Movable Typeが提供してるAPI
  • LiveJournal API: Bloggerが提供してる独自API
一般的に汎用的なブログ投稿ツールでは上記の内、上3点を標準実装しています。
さてJaikuの場合、XMLRPCのインタフェースは一つしか公開されておらず(2007/11/01時点)、そのスペックは presence.send({
    user => 'your username as string',
    personal_key => 'your personal key as string',
    message => 'message as string',
    optional icon => 'icon number as int',
    optional location => 'location as string',
    optional generated => 'generat flag as boolean'
})
こんなイメージとして表現出来ます。
簡単な物をPerlで書くならば

jaiku.pl
#!/usr/bin/perl
use strict;
use Encode qw(from_to);
use XMLRPC::Lite;
use Data::Dumper;

my ($user,$personal_key,$message,$icon) = @ARGV;
from_to($message, "shiftjis", "utf8") if $^O eq "MSWin32";

print Dumper(XMLRPC::Lite
    ->proxy('http://api.jaiku.com/xmlrpc')
    ->call('presence.send', {
            user => $user,
            personal_key => $personal_key,
            message => $message,
            icon => ($icon || 300),
        })->result);
と書く事が出来ますね。これをコマンドラインから
./jaiku.pl username xxxxxxx 本日は晴天なり ※「xxxxxxx」はpersonal_keyです。APIドキュメントの右下あたりに記述されています。
と実行すればJaikuのステータスが更新されます。例では最終引数に省略可能なアイコン番号を付けていますので ./jaiku.pl username xxxxxxx 曇り 398 と実行すれば
jaiku-icon-cloudy
といった感じにアイコンを付ける事が出来ます。
なお、Jaikuのアイコン番号に対する実際のアイコン対応表を作りました。
JavaScriptをOnにして頂いて、以下のリンクをクリックして下さい。
アイコンリストを表示
さて話をXMLRPCに戻して、Perlでは「XMLRPC::Transport::HTTP」というXMLRPCを透過的にPerlのパッケージおよびサブルーチンとバインドしてくれるモジュールがあり、XMLRPCサーバを簡単に実装する事が出来ます。
例えば数値パラメータ2つ貰い足した結果を返すXMLRPCサーバならば以下の様に実装出来ます。 #!/usr/bin/perl

use strict;
use XMLRPC::Transport::HTTP;

package calc;
sub plus {
    shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ );
    my $lhs = shift;
    my $rhs = shift;
    my $res = $lhs + $rhs;
    return {result=>SOAP::Data->type('int' => $res)};
}

XMLRPC::Transport::HTTP::CGI
    ->dispatch_to('calc')->handle;
モジュール名がpackage「calc」に、メソッド名がサブルーチン「plus」にバインドされている事が分かるかと思います。
これを実行するには #!/usr/bin/perl
use strict;
use warnings;
use Encode qw(from_to);
use XMLRPC::Lite;
use Data::Dumper;

my $url = 'http://example.com/xmlrpc/calc.cgi';
warn Dumper(XMLRPC::Lite
    ->proxy($url)
    ->call('calc.plus', 1, 2)->result);
というクライアント側のコードで実行出来ます。
勿論、XMLRPCを扱うならばクライアント/サーバはPerlでなくても良い訳です。pythonで実装するならば from xmlrpclib import ServerProxy
proxy = ServerProxy("http://example.com/xmlrpc/calc.cgi")
print proxy.calc.plus(1, 2)
という実装になります。
XMLRPCは既に色んな言語で実装されており、それぞれに使い方は異なりますが扱う仕様が決められている為に、移植に戸惑う事はそれ程ないかと思います。
※特にpythonに関してはメソッドバインディングされますので、かなり透過的に扱えます。

例を元にXMLRPCを説明して来ましたがどうでしたでしょうか?
色んな言語でJaikuクライアントを実装してみるのも面白いかもしれませんね。

Posted at by




Google Code Searchに「Google ソースコード検索にコードを追加」というページが出来ていました。
単にCVSやSVNのリポジトリだけでなく、.tar、.tar.gz、.tar.bz2、.zipといったアーカイブファイルの中身までクロールしてくれるようです。
コードを晒したいけどCVS、SVNサーバをお持ちでない方には朗報かもしれませんね。
Posted at by




もごもごAPIコンテストにて「WEB FLASH賞」を頂きました
先日、家のポストに「Dragon.jp」と書かれた郵便物が届いていました。
Dragon.jpからの贈り物1

中を開けると、もごもご運営事務局殿からの手紙と「WEB FLASH」という情報誌。
Dragon.jpからの贈り物2

それと「もごもごステッカー」が5枚入っていました。

「WEB FLASH」はこれから目を通します。ありがとうございました。
※Web屋でもない私が、こんなに高い^H^H いい本持ってていいんだろうか...

APIコンテスト等といったものに参加させて頂くのは今回が初めてでしたが、とても楽しめました。APIを使って実際にアプリケーションを幾つか作る事で初めてtwitterと、もごもごのAPIの差に気付くことが出来たり、上位入賞者の方々のマッシュアップ作品で勉強もさせて頂きました。

このような企画を用意して頂いた、もごもご運営事務局の方にお礼を申し上げます。次回もこのように楽しめる企画を期待しております。

ありがとうございました。

なお「もごもごステッカー」は眼鏡ケースに貼らせて頂いてます。
Dragon.jpからの贈り物3

残りの4枚は、どこに貼ろうか検討中です。
Posted at by




関西の食パンは5枚切りなの?
そうですよ。うちは5枚切りですよ!当たってますよ!
って事で、パンが何枚切りかで関西人かどうかを判断するAPIを作りました。

http://mattn.kaoriya.net/cgi-bin/breadslice.cgi

パラメータ:
slice: 何枚切りか
callback: JSONコールバック
サンプルリクエスト:5枚切り

簡単に作ったアプリケーション
あなたのパンは何枚切り?
あなたは...


これで気になるアノ人が関西人かどうか分かります。
Posted at by




以前書いた「LuaでTwitterるわ!」の「もごもご」版です。

続きを読む...

Posted at by




もごもご vimからもごもごを閲覧/更新できるアプリケーションを作りました。
ベースとなっているのは、Twitter : A simple client for Twitterです。
以前、この「Twitter : A simple client for Twitter」のマルチバイト周りとWindowsで動作するように修正した物をtwitter上でひっそりとアナウンスしましたが、それをベースにもごもごバージョンを作ってみました。
もごもごって、public_timelineでも認証がいるんですね...汗
使い方は、twitter版と同じ

publicステータス取得
:Mogo2PublicTimeline friendsステータス取得
:Mogo2FriendsTimeline ステータス更新
:Mogo2StatusUpdate 今日も、もごもご となります。
※スレッドへの発言も同様に
:Mogo2StatusUpdate >>343473 もごもご... と実行します。

ダウンロード:
※witter.pyがpythonスクリプト内でグローバルなメソッド名称定義してくれてますので、mogo2.pyでは接頭語「Mogo2」を付けてます。

また、オリジナルのtwitter版をマルチバイト/Windows対応した物も一緒に配布します。こちらは精神衛生上の都合もあり上記接頭語を「Twitter」に変更しています。
ダウンロード:
上記スクリプトのインストール方法は...メンドイので省略
Posted at by




なんか寝つけないので、もひとつ愚痴...
最近、お仕事でJava使ってます。

といってもJavaの画面上にはActiveX(Ocx)が張りついてます。しかもそのActiveXって.NETで出来てるんです。どんなシステムなのかと...苦笑
まぁそれが言いたいのでなく、COMやATLをJavaアプリで動かすので、Javaのメインスレッドを食われない用に、STAやATLで作ったOcxは別スレッド起こして実行してます。
なんかこういう場合にも、メインスレッドを汚ない(出来ればCOMなんか関与しない)ようなコードが、簡単に書けないものか...

非常にメンドイです。

そうそう今日職場で休憩時間にMSから出てるPowerShellっていう管理者用(?)シェルを試してみました。
感想としては、まさしく...「コマンドプロンプトに.NETという毛を生やした」というのが相応しく(いや、変な意味でなく)、私にとってはPowerとまでは行きませんが、違う物としてみれば使える代物なのかもしれません。

ただ、exeもパス内のものが実行出来るのですが、「startコマンド」が使えない。私にはダメージが大きすぎる。
私は仕事中はだいたい70〜80%をコマンドプロンプトの中で生活しますから、たまにエクスプローラを開くときなんかは、「メニューから...」なんて事はしないんですよね。「cd」してエクスプローラが開きたくなったら
C:\foo\bar\> start .
ってやるんです。
この「startコマンド」がないと、まるで周りからはPCド素人に見えてしまうんですよ。私...

マイクロソフトさん、私を助ける意味でもPowerShellに「startコマンド」を付けて下され...


マイクロソフトで思い出しました...
今日IronPythonを試しました。以前に少し触った事はあったのですが、MSエバンジェリストのブログで、「IronPythonを使ったMSAgentのサンプル」を見つけたので、試してみたんです。
ほう...確かに動く。


...で?
これってpythonのwin32com使ってもおんなじやん...
まぁアセンブリ参照みてモジュールベースのコーディングが出来るのかもしれないんですが、pythonって呼出フック出来るから、結局似たようなコードになるんじゃ...
ちなみにExcelを操作するpythonのコードは...
import win32com.client
xlApp=win32com.client.Dispatch("Excel.Application")
xlApp.Visible=1
xlApp.WorkBooks.Add()
sheet=xlApp.Sheets(1)
sheet.Cells(1, 1).Value="Hello Python"
こんな感じ?じゃぁ、「.NET」である必要って何?

あと余談ですが、MSAgentってSTAでしか動かないんすね。おまけにイベントシンク(IAgentNotifySink/IAgentNotifySinkEx)ってInvoke呼出してくれないし...泣
汎用ディスパッチシンクを呼ばせるには、IDispatchを継承してQueryInterfaceで嘘を返してあげればいいの?
#GetTypeInfoCountもGetTypeInfoも呼ばれない、IAgentNotifySinkを継承してないインスタンスをRegisterメソッドに渡すとエラー出るし...


さぁいい加減に寝ないと、死ぬぞ...
Posted at by




linux用もごもごクライアント、GtkMogo2のバージョン0.0.1をリリースします。

使い方は...感覚で分かるかと思います。





コードリポジトリはgtktwitterと共に、Google Project Hostingに移行しましたので、後日追ってお知らせいたします。

gtkmogo2-20070626
Posted at by




nayoyaグループ - naoyaの日記 - Gearmanのやつ#2
clouderさんは結局、MSG_WAITALLを使う方法を取られたようですね。個人な趣味としてはあまりMSG_WAITALLは使わないほうなので、きっと私の場合はループを回すかな。
理由は大した事ではなく、サーバ側で「Content-Length」を出力し、そのContent-Length数分データを送信するようなCGIを書いた場合、バグで「Content-Length」分満たない内に落ちてしまった場合に、クライアントの受信がMSG_WAITALLだとバッファ全部破棄されてしまいエラーハンドリングし辛いからです。
(※たとえばどこまで受信したかが分からないとか...)
今日は本題から外れますが、上記リンクの中にも出てきたソケットディスクリプタから「fdopen」する処理をWindowsではどう書くかをご紹介。

続きを読む...

Posted at by




おそろしやおそろしや...

むかーしむかし、Microsoft Visual J++という道具があったそうな...
その道具では、関数呼出の参照渡しを実現するために、配列を渡してJNI内部で値を設定するといった、奇怪な手法が使われておりましたとさ。

さて話は現代に移り、Microsoft Visual J#という道具が巷では出回っていると聞き、さっそく試した私は、腰を抜かしてしまいそうになりました。
public class Test
{
    public static void test1(int a) { a = 2; }
    public static void test2(/** @ref */ int a) { a = 2; }
    public static void main(String[] args) {
        int b = 3;
        test1(b); System.out.println(b);
        test2(b); System.out.println(b);
    }
}
まさか、こんな結果になりますとは...

Javaという言語仕様まで変えてしまうとは...

おそろしや、おそろしや...


#まぁ、Javaって言ってない(J#)から、いいんですか...
#そうですか...
Posted at by




ローカルDBやメモリDBとして使えるSQLiteは、開発者にとってかなり有用なツールです。 私はよく「あーーーあのSQLどう書こう」なんて悩む時にSQLiteのshell版を使います。今日はそのWin32 SQLite3のshell版sqlite3.exeの作り方をご紹介。

まず、ダウンロードページから
sqlite-3_5_0.zip
というファイルをダウンロードします。
次に、ソースツリー内の「src」ディレクトリに移動し、以下のMakefileを置きます。
.SUFFIXES: .c .obj

all : sqlite3.exe

sqlite3.exe :  alter.obj analyze.obj attach.obj auth.obj btree.obj build.obj callback.obj complete.obj date.obj delete.obj expr.obj func.obj hash.obj insert.obj legacy.obj loadext.obj main.obj opcodes.obj os.obj os_os2.obj os_unix.obj os_win.obj pager.obj parse.obj pragma.obj prepare.obj printf.obj random.obj select.obj shell.obj table.obj tokenize.obj trigger.obj update.obj utf.obj util.obj vacuum.obj vdbe.obj vdbeblob.obj vdbeapi.obj vdbeaux.obj vdbefifo.obj vdbemem.obj vtab.obj where.obj mutex.obj mem1.obj malloc.obj
    cl /Fesqlite3.exe alter.obj analyze.obj attach.obj auth.obj btree.obj build.obj callback.obj complete.obj date.obj delete.obj expr.obj func.obj hash.obj insert.obj legacy.obj loadext.obj main.obj opcodes.obj os.obj os_os2.obj os_unix.obj os_win.obj pager.obj parse.obj pragma.obj prepare.obj printf.obj random.obj select.obj shell.obj table.obj tokenize.obj trigger.obj update.obj utf.obj util.obj vacuum.obj vdbe.obj vdbeblob.obj vdbeapi.obj vdbeaux.obj vdbefifo.obj vdbemem.obj vtab.obj where.obj mutex.obj mem1.obj malloc.obj

.c.obj :
    cl /c /DSQLITE_THREADSAFE=0 $<
後は、Visual Studioのコンパイラにパスが通ってるならば「nmake」でコンパイルします。 なおSQLiteは、内部処理がUTF-8でやり取りされており、Win32版の場合はNTかどうかでCreateFileA/CreateFileW等、ワイド文字APIの呼び方を変えています。
しかし、コマンドライン引数には対応出来ておらず C:¥> sqlite3.exe データベース.db なんて事すると、エラーが発生します。ちなみにNTでない場合には変換無しにCreateFileAを使いますから問題なく動きます。
これって、コマンドラインをUTF-8にすれば動くんだろうけど C:¥> chcp 65001 やってUTF-8にしても文字は化けるし、IME動かないし意味無いですよね。
適当に --- os_win.c.orig   Mon Sep 03 22:39:38 2007
+++ os_win.c    Fri Sep 28 22:17:50 2007
@@ -137,6 +137,8 @@
 # define isNT()  (1)
 #else
   static int isNT(void){
+    if( GetACP() != CP_UTF8 )
+      return FALSE;
     if( sqlite3_os_type==0 ){
       OSVERSIONINFO sInfo;
       sInfo.dwOSVersionInfoSize = sizeof(sInfo);
こんなパッチ当てて、実行すると日本語のファイル名も扱える様になります。
このEXEをUSBなんかで常備しておくと、いざという時に助けられるかもしれませんね。

Posted at by




ショートカット一発検索で生活が変わる を見ていて、普段自分がWindowsで使ってるキーボードショートカットを書いて見る事にする。

ウィンドウ最大化
Alt-Space Alt-X
押し方はAltキーを押しっぱなしで「スペース」、「X」と連続で押す。
元に戻したい場合は「X」の代わりに「R」を押す。
システムのプロパティ
Win-Pause
押し方はWindowsキーを押しながらPauseキー
まれに無限ループするプログラムを書いてしまった時等に、プロセス強制終了したい場合等に使う。
コンピュータのロック
Win-L
押し方はWindowsキーを押しながら「L」キー
突然会議に召集を掛けられた場合には、すかさず「Win-L」して席を外す。
番外編
普段からコマンドプロンプトを使う事が多い私は、デスクトップにコマンドプロンプトのショートカットを貼り、そのショートカットのプロパティでショートカットーキーを「Ctrl + Shift + Alt + F12」に設定している。
これでどの画面からでも「Ctrl + Shift + Alt + F12」でコマンドプロンプトを起動出来る。
基本的にはオリジナルの設定を変える事はしないほうです。便利だと思ったら付け足すくらい。
ただvimrcは、もう911行もある...。
vimrcでどんな事やってるかについては、また違うエントリで...
Posted at by




音ログ for LinuxをCodeReposに入れました。
/lang/python/otolog4linux/trunk - CodeRepos::Share - Trac
「音ログ for Linux」は音ログをLinuxでもやりたい!という思いから開発を始めたソフトウェアで、現在では音ログAPIを実装したPLAYLOGという音楽SNSで利用出来ます。具体的には
といった音楽プレーヤの現在再生曲情報がPLAYLOGにアップロードされます。
名前にはLinuxとありますが、iTunesを使ってWindowsでも動きます。結構前からコード放置状態にありますが、そのままにしておくのも勿体ないので、CodeReposに上げる事にしました。へたっぴなpythonのコードですが少しでも誰かの役に立つのならば嬉しい話です。

Posted at by




久々動かしたら、動かなくなってました。PLAYLOGで、日付に関する制約が入り、バグを直すのを忘れてました。一応これで
3点修正が完了しました。(上記2点は既に修正済)
一応、何もバージョンアップしないというのもカッチョワルイ話なので、Music Player Daemon(mpc)で再生した曲情報アップロードにも対応しました。
個人的には一番軽いので、気に入っています。

これで「音ログ for Linux」がサポートしているプレーヤは
となりました。ただ、まだリリースされたばかりなのでPLAYLOG再生履歴でのクライアント名表示には「?」と表示されます。一応、事務局のほうにも新しいクライアントが増えた事は伝えておくつもりです。
#幾分、知名度がないクライアントですので採用されない可能性もあります。
#ちなみに、この曲をPLAYLOG再生履歴に残したの、私が初めてのようです。苦笑

otolog4linux-20070628

お知らせ:
ところで...
最近のニュースでも取り上げられている通り、MSの強力により、レーベルゲート自信がPLAYLOGのアップローダをリリースする事になる為、「プレイログアップローダ for WindowsMediaPlayer」は、よほど重大なバグでも見付からない限り、バージョンアップを行わない方針に決めさせて頂きました。

これまで幾度かリリースさせて頂きましたが、使用して頂いた皆様、バグ報告を頂いた方、本当にありがとうございました。
Posted at by




色々と弄っているうちに、Safariのメニュー部のフォントが戻らなくなりました。

safari4win_broken_font
調査の結果 C:\Documents and Settings\xxxx\Local Settings\Application Data\Apple Computer\safari xxxxはユーザ名称

のフォルダを削除する事で、フォントキャッシュが削除される事が分かりました。
どうやらSafari君、高速化の為かフォントをキャッシュしているようですね...

追記1:
削除は自己責任でお願いします。

追記2:
WebKitPreferences.plistに設定するフォント名は、上のフォルダにあるFonts.plist内に指定されている別名で指定すれば、「MS ゴシック」のようなフォント(MS-Gothic)も設定出来ると思います。
Posted at by




昨日のWWDC2007キーノートのサプライズで、Safari for Windowsがリリースされました。家はLinuxだったので手を出せず、先程ようやくダウンロードして起動出来ました。
フォント設定に少し難がありますが、とりあえず(?)動いております。

使った感じですが...

落ちる落ちる落ちる...

化ける化ける化ける...

テキストエリアが縮む縮む縮む...

スクリプトが動かない動かない動かない...

まぁベータですから...汗
最初はブラウザからこのエントリを書こうかと思いましたが、テキストエリアがつぶれて書けませんでした。

safari4win_broken_textarea

でもまぁ、UIとしてはスッキリしてて嫌いではないです。実は私、むかーしむかしはマカーだったりして...

safari4win

日本語を表示する際には、フォント名称に日本語を含まない物が良いのですが、Monaフォントが一番適していると思われます。

さてこれで、AppleはWindowsのブラウザシェアに参入した訳ですが、このベータをどれだけの期間で、使い物になるブラウザに持っていくかでAppleの技術力が問われてしまう事になりそうです。
iTunesはある意味、独自独占ソフトウェアな訳ですが、あのUI感からそれず、かつFirefox等に追いつくには、かなりの改良が必要になるはずです。

なお、印刷は意外と綺麗に出ました。

以下、iGoogleの印刷結果
safari4win_print
Posted at by




バグってました... orz


Windows版はもうすこしお待ち下さい。



Posted at by




GtkTwitterのWin32版をリリースします。
動作にはGTK+ Runtimeが必要になります。
結局、配布方法が分からないので実行モジュールと、依存ライブラリの配布元リンク提供という形で公開します。
なお、別途リリース案内させて頂いたソースにはWin32用Makefile(Makefile.w32)が含まれております。

gtktwitter-win32-20070626

ダウンロード
GTK+ Runtieme(追加インストール)
GTK+ Runtime Environment for Windowsのリンクから、GTK+ Runtimeをインストールします。
libcurl for Win32(同梱)
cURL and libcurlよりWin32版curlライブラリ(libcurl.dll)を同梱させて頂いています。
最新版は上記リンクから取得し直して下さい。
zlib for Win32(同梱)
GnuWin32よりWin32版zlibライブラリ(zlib1.dll)を同梱させて頂いています。
最新版は上記リンクから取得し直して下さい。
Posted at by




URLをクリッカブルにしてみました。
また、rpmも作成しましたので、よろしければどうぞ。




Posted at by





http://twitter.com/itkz/statuses/220045412
GtkTwitter の configure がこける理由がわからないので、aclocal.m4 の AM_PATH_GTK_2_0 を解析する羽目になった。仕事ってなにそれおいしいの
http://twitter.com/itkz/statuses/219941692
GtkTwitter をどうやっても起動できない。GTK+ Runtime をインストールしても動かないし、DLL を全て実行ファイルと同じ階層に置いても動かない。関数のエントリポイントが見付からないんだとよ! 舐めてっとぶっ飛ばすぞ。どういうことだ。
http://twitter.com/itkz/statuses/222783012
(続き)もちろん、GtkTwitter の configure は pkg-config をスルーしてるので config.log にエラーなど残っていない。だからコンパイル時の問題だと思って CFLAGS をいじくったりしていたわけだ。
http://twitter.com/itkz/statuses/221271572
結局のところ、数時間かけても GtkTwitter のビルドには成功していない。まずバイナリで落としてきた libcurl をリンクすると「エントリポイントがねえ」と言ってくる。調べると「cURL が MinGW で動かねえと騒いでる糞どもはソースからビルドしろ」と一喝されている

これ以上ほったからしにしておくと、惨殺されるという噂なので焦って記事アップしました...(嘘です)

まず、リポジトリですが
http://gtktwitter.googlecode.com/
にあります。ダウンロード用アーカイブは古いのでsvnから使って下さい。
mingw32の場合は、autotoolを使うまでもないので確認していません。(ごめんなさい)
必要な物は
GnuWin32
Glade/GTK+ for Windows
cURL: win32-ssl-mingw from Mirrors
あたりでダウンロード出来るかと思います。
Makefileは適当に
all : gtktwitter.exe

gtktwitter.exe : gtktwitter.o gtktwitter.res
    gcc -o gtktwitter.exe \
        -Lc:/gtk/lib \
        gtktwitter.o \
        gtktwitter.res \
        `pkg-config --libs gtk+-2.0 libxml-2.0 gthread-2.0` \
        -lcurl

gtktwitter.o : gtktwitter.c
    gcc -c \
        `pkg-config --cflags gtk+-2.0 libxml-2.0` \
        gtktwitter.c

gtktwitter.res : gtktwitter.rc
    windres -o gtktwitter.res --output-format=coff gtktwitter.rc

clean:
    -rm *.o *.res *.exe
あたりで誤魔化して下さい。
あとは、curl.hやlibcurl.aをmingwへ入れ
set PKG_CONFIG_PATH=C:\GTK\lib\pkgconfig
してからmake(mingw32-make)すると出来上がると思います。

どうか、これでお許し下さい。苦笑
Posted at by




実は、gtktwitterはユーザエージェント対応を結構前からやっておりまして、いつになったら「from GtkTwitter」と表示されるんだ...まってるよAlex君と、ただただ待っていたのですが、どうやらメールしないと取り込んで貰えない事が分かりまして、メールしました。金曜に「次のデプロイでリンクされるよ。ありがとう。」と浜村淳ばりの返事を貰いました。
そしてようやく...



これでようやく他のアプリに仲間入りした感じです。





Posted at by




バグフィックスです。
また、このバージョンからパッケージ名称(ディレクトリ名称)を「GtkTwitter」から「gtktwitter」に変更します。



Posted at by




WindowsにあるようなToggleDesktopをROX用に作ってみました。

http://rox.sourceforge.net/desktop/node/243
ソースはわずか17ステップ...。苦笑
Posted at by




追記1
タイトルがうまく取れていないっぽい。
あとリンクも見付けれてない...orz
時間見つけて直します。
一応、現在見ているページならポスト出来るみたい。

追記2
直した。

Tomblooが良く出来てて素晴らしい。
Tomblooは現在のコンテンツに合わせてポストする形式を集約し、コンテキストメニューから集約したアイテムをポスト先へ送信する、すばらしい拡張です。
現状、標準のポスト先としてFlickr、Tumblr等がポスト先として選択出来る様になっています。
Minibufferフリークとしては、これをMinibufferから使えないのは物悲しい!
ってことで適当にハックしてMinibufferからpostする方法をご紹介します。
とは言っても今日ご紹介する方法は、ブックマークレットでもなければグリースモンキーでもありません。Firefoxの拡張を弄る事になりますので自己責任でお願い致します。
まずTomblooの拡張が格納されているフォルダ、
${FIREFOX_PROFILE}/extensions/tombloo@brasil.to/chrome/content/library/Mozaic.html
を開き <script type="text/javascript" src="../library/20_Tumblr.js"></script>
<script type="text/javascript" src="../library/30_Tombloo.Service.js"></script>
<style>
となっている部分に <script type="text/javascript" src="../library/20_Tumblr.js"></script>
<script type="text/javascript" src="../library/30_Tombloo.Service.js"></script>
<script type="text/javascript" src="../library/32_Minibuffer.js"></script>
<style>
と32_Minibuffer.jsを追加します。次に
${FIREFOX_PROFILE}/extensions/tombloo@brasil.to/chrome/content/library/32_Minibuffer.js
を作成し、以下のソースをペーストして保存します。
var GreasemonkeyServiceClass = Components.classes["@greasemonkey.mozdev.org/greasemonkey-service;1"];
log(GreasemonkeyServiceClass);
if (GreasemonkeyServiceClass) {
    function update(target, src){
        for(var key in src)
            target[key] = src[key];
        
        return target;
    }
    function addBefore(target, name, before) {
        var original = target[name];
        target[name] = function() {
            before.apply(target, arguments);
            return original.apply(target, arguments);
        }
    }
    var GreasemonkeyService = GreasemonkeyServiceClass.getService().wrappedJSObject;
    addBefore(GreasemonkeyService, 'evalInSandbox', function(code, codebase, sandbox){
        if (sandbox.Minibuffer && sandbox.LDRize) {
            sandbox.Minibuffer.addCommand({
                name: 'tombloo',
                command: function(stdin) {
                    var view = sandbox.LDRize.getSiteinfo()['view'];
                    var xpath_link = sandbox.LDRize.getSiteinfo()['link'];
                    var xpath_title = view || 'descendant::text()[normalize-space(self::text()) != ""]';
                    var nodes = sandbox.Minibuffer.execute('pinned-or-current-node');
                    forEach(nodes, function(node){
                        try {
                            var context = update(update({
                                document  : sandbox.unsafeWindow.document,
                                window    : sandbox.unsafeWindow,
                                title     : sandbox.unsafeWindow.document.title,
                                selection : '',
                                event     : {},
                                mouse     : {x:0,y:0},
                                menu      : null,
                            }, null), sandbox.unsafeWindow.location);

                            var text = '', url = '';
                            with(sandbox.unsafeWindow) {
                                var nodesSnapshot;
                                nodesSnapshot = document.evaluate(xpath_link, node, null,
                                    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
                                if (nodesSnapshot.snapshotLength > 0)
                                    context.href = nodesSnapshot.snapshotItem(0).href;

                                nodesSnapshot = document.evaluate(xpath_title, node, null,
                                    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
                                var allstringlength = 0;
                                var ypos = 0;
                                for(var n = 0; n < nodesSnapshot.snapshotLength; n++) {
                                    var item = nodesSnapshot.snapshotItem(n);
                                    var str = item.nodeValue.replace(/\s/g,'');
                                    allstringlength += str.length;
                                    if(allstringlength > 30){
                                        text += str.slice(0, 30) + '...';
                                        break;
                                    }else{
                                        text += item.nodeValue;
                                    }
                                }
                            }
                            if (text.length) context.title = text;

                            var exts = Tombloo.Service.check(context);
                            var service = Tombloo.Service;
                            forEach(exts, function(ext){
                                service.share(context, service.extracters[ext.name]);
                            });
                        } catch(e) {alert(e)}
                    });
                }
            });
        }
    });
}
あとはブラウザを再起動すれば、Minibufferにtomblooというコマンドが追加され、pinを付けて :tombloo と実行すれば、Tumblrにリンクがポスト出来る様になっています。
現状はTomblooが扱えるコンテンツの中でも、リンクのポストにしか対応していませんが、selection等を弄ればQuote、画像を選択させれば画像ポスト(Tumblr以外からのReblog)がMinibufferから可能になるかと思います。
今後やる気が続行すれば、作者殿にTomblooをプラガブルにする為の提案をさせて頂きたい...と思っています。
※やる気が続行すれば...の話です。

ただ、やっぱり特権のある部分からMinibufferを操作する行為は幾らevalInSandboxとは言え怖い...苦笑


リンク:Tombloo 0.1.0
Posted at by




しげふみさんのサイトでmicrosummaryという言葉を初めて目にしました。
livedoor Reader購読者数のライブタイトル
これはFirefoxだけの拡張で置いておくのは勿体無い。
しばらくしたら私のサイトでもmicrosummaryに対応したいと思いますが、とりあえず遊びも兼ねて...

Yahooヘッドラインニュースの最新1件をFirefoxのライブタイトルで表示出来る物を作ってみました。
インストールには、Microsummary Buddyが必要です。
Microsummary Buddyをインストールした後、以下のリンクをクリックして下さい。
※Firefox限定です。
YahooヘッドラインニュースのMicrosummary Generatorをインストール
これを入れた後Yahooヘッドラインニュースに移動し、ブックマーク追加を行います。
microsummary-yahoo-headline-news1
1段目のプルダウンメニューを広げると以下の様な項目が現れます。
microsummary-yahoo-headline-news2
ブックマークの登録先は、「個人用ツールバーフォルダ」に行います
すると、ブックマークツールバーに以下の様なブックマークが登場します。
microsummary-yahoo-headline-news3
※microsummaryが見つかるとアドレスバーに青いアイコンが現れます。
ライブタイトルの更新はデフォルトで30分おきという事なので、30分おきにYahooのヘッドラインニュース最新1件がブックマークツールバーに表示される事になります。

さて、このMicrosummary Buddyの拡張XMLですが中身は以下の様になっています。
<?xml version="1.0" encoding="UTF-8"?>
<generator xmlns="http://www.mozilla.org/microsummaries/0.1" name="Yahoo Headline News">
 <template>
  <transform xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <output method="text"/>
   <template match="/">
    <text>News:</text>
    <value-of select="//h1[@class='yjXL'][1]/a[1]/text()"/>
   </template>
  </transform>
 </template>
 <pages>
   <include>^http://headlines\.yahoo\.co\.jp/hl$</include>
 </pages>
</generator>
"generator/pages/include"でmicrosummaryを生成するURLのパターンを定義し、"generator/template/transform"でライブタイトルとなるmicrosummaryの変換式を書きます。
XSLTで「//li...なんちゃら」とやっている部分がYahooヘッドラインニュースのトップ記事1件を取得しているXPathになります。

面白いですね。twitterの最新発言や、自分のサイトはてなブックマーク数をライブタイトルにしても面白いかも知れませんね。
Posted at by




以前、個人的なブームでPlaggerのPublish::XXXプラグインを作ってた頃の知識を再利用。

Minibufferには、ソーシャルブックマークへ登録するコマンド「bookmark」があります。
minibufferbookmarkcommand.user.js
ちょっと前これを色んなSBMに対応出来る様にプラガブルに修正したのですが、ノウハウを生かして以下2本のbookmarkプラグインを作成しました。

GooBookmarkプラグイン
minibufferbookmarkcommand.goobookmark.user.js
NiftyClipプラグイン
minibufferbookmarkcommand.niftyclip.user.js
それぞれ :bookmark -g
:bookmark -n
で実行出来る様になっています。
Publish::XXXの時と同様に利用している人は皆無かと思いますが、よかったらどうぞ。
Posted at by




もう眠くなってきたら :shutdown ソースは // ==UserScript==
// @name           Minibuffer Shutdown Command
// @namespace      Minibuffer.Shutdown.Command
// @include        http://*
// ==/UserScript==


(function(){
  var mes = [
    'System is going down for system shutdown now.......',
    'Starting local stop scripts.                       ',
    'Exiting Syslogd!                                   ',
    'Syncing all filesystems:                       [OK]',
    'Unmounting all filesystems:                    [OK]',
    'The system is going down NOW !!                    ',
    'Sending SIGTERM to all processes.                  ',
    'The system is halted. Press Reset or turn off power',
    'flushing ide devices:                          [OK]',
    'System halted.                                     ',
  ];
  function shutdown( stdin ) {
    GM_addStyle(<><![CDATA[
      #gm_minibuffer_flash_status * {
      font-family: 'terminal', 'monospace';
      font-weight: bold;
      }
      ]]></>);
    var n = 0;
    var timer = setInterval(function(){
      window.Minibuffer.status('shutdown'+n, mes[n], 50000);
      if (n++ >= mes.length) {
        clearInterval(timer);
        document.open();
        document.close();
        document.body.style.backgroundColor='black';
      }
    }, 1000);
    return stdin;
  }

  if (typeof window.Minibuffer != 'undefined') {
    window.Minibuffer.addCommand({
      name: "shutdown",
      command: shutdown,
    });
  }
})();
こんな感じ。
インストール:minibuffer.shutdown.user.js
てか実用的な物つくれ!俺
Posted at by




ちょ!!www
まだそれ出来てないすから!!!
http://h.hatena.ne.jp/noreply/9236556151112331479
id:noreply
ldrizeでpしたハイクにスターを打つminibufferコマンド欲しい
http://h.hatena.ne.jp/otsune/9236538558961639940
id:otsune
それ id:mattn で
週末時点ではアカウント取れてたしotsuneさんからのidコールには気付いていたけど週末は予定ぎっしりで、結局出来ませんでした。
とりあえず動くようになったので公開...
:pinned-node | Hatena::addStar でピンを付けたノードに「はてなスター」を付けられます。
動作には、LDRizeとMinibufferと、mattnへの愛情が必要です。

動かなかったら...そんとき対応します。
別にはてなハイクで無くてもLDRizeが動いて、はてなスターが付けられる所だったら動くかもしれません。
※とりあえず、はてなハイクでは確認出来ています。

インストール:minibuffer.hatena.addStar.user.js
追記
pinを付けてキー「H S」でも動きます。

mattn the Haiku Haiker Haikest!
Posted at by




こちらを参考に...
MinibufferベースのTwitter Post&Favorites Command Greasemonkey - 0x集積蔵
Jaiku版作ってみました。
以下ダウンロードURLがあります。

ただ、Jaikuの場合twitterと違ってBasic Authのダイアログを出す訳には行きません。Jaikuの場合にはAPIキーが必要となります。
グリモンをインストールした後
グリモンの設定ボタンを押してスクリプト編集に移り、「JaikuUser」と「JaikuApiKey」を修正します。
minibuffer-jaiku
個々のユーザに対するAPIキーはJaikuのAPIドキュメントに書いてあります。(黄色くおねしょしたみたいになっている部分です)
あとはtwitter版と同じ使い方になります。キーバインドとして「J T」および「J R」が追加されます。

なお、先ほどJaiku用のLDRize SITEINFOも書いておきましたので、pinからreplyする事も出来ます。

後は適当に...
minibufferjaikucommand.user.js

mattn the jaiku jaiker jaikest!.
Posted at by




MinibufferBookmarkCommand - 3.14

適当ですが...
はてなブックマークと違いdel.icio.usの場合は、クローラでタイトルを補完してくれませんので、XHRで自前で取得しています。その際、responseTextが文字化けを起こしている可能性がありますので要注意。
--- minibufferbookmarkcomman.user.js.orig   Sat Nov 24 14:53:01 2007
+++ minibufferbookmarkcomman.user.js    Sat Nov 24 14:51:57 2007
@@ -140,6 +140,90 @@
        postToHatenaB(nodes, urls);
    }
    
+   // delicious bookmark
+   var delicious = function(nodes, urls, tags, comment){
+       if(!urls.length) return;
+       var total = urls.length;
+       var time = new Date().getTime();
+       var getParam = function(node, url, fn, afterPost){
+           if(!url) return;
+           var callback = function(res){
+               var html = res.responseText.createHTML();
+               var form = window.Minibuffer.$X('//form[@id="delForm"]', html)[0];
+               var inputs = window.Minibuffer.$X('//form[@id="delForm"]//input', html);
+               var res = {};
+               inputs.forEach(function(node){
+                   res[node.getAttribute('name')] = node.getAttribute('value');
+               });
+               if(keys(res).length == 1){
+                   // already bookmarked
+                   var text = url;
+                   if(window.LDRize){
+                       var siteinfo = window.LDRize.getSiteinfo();
+                       if(siteinfo && siteinfo.view){
+                           var view = window.Minibuffer.$X(siteinfo.view, node)[0];
+                           if(view) text = view.textContent;
+                       }
+                   }
+                   window.Minibuffer.message('<small>'+text+'</small><br/> has already bookmarked.', 2000);
+                   afterPost();
+               }else{
+                   res.action = 'http://del.icio.us/' + form.action.replace(/^https?:\/\/[^\/]+\//, '');
+                   // not yet bookmarked
+                   fn(res, afterPost);
+               }
+           }
+           GM_xmlhttpRequest({
+             method: 'GET',
+             url: "http://del.icio.us/post?v=4&url="+encodeURIComponent(url),
+             onload: callback,
+             onerror: function(res){log('onerror',res.responseText, '\n',res.responseHeaders)},
+           });
+       }
+       var post = function(opt, afterPost){
+           if (!opt.description) {
+               var x = new XMLHttpRequest();
+               x.open('GET', opt.url, false);
+               x.send(null);
+               if (x.readyState == 4 && x.status == 200) {
+                   try {
+                       var html = x.responseText.createHTML();
+                       opt.description = window.Minibuffer.$X('//title', html)[0].text;
+                   } catch(e) {}
+               }
+           }
+           var request = [
+               "url=", encodeURIComponent(opt.url),
+               "&oldurl=", encodeURIComponent(opt.url),
+               "&private=", opt.private,
+               "&description=", encodeURIComponent(opt.description),
+               "&notes=", encodeURIComponent(comment),
+               "&tags=", encodeURIComponent(opt.tags),
+               "&v=", opt.v,
+               "&key=", opt.key,
+               ].join('');
+           GM_xmlhttpRequest({
+             method: 'POST',
+             url: opt.action,
+             headers: {
+                 'Content-Type': 'application/x-www-form-urlencoded'
+               },
+             onload: afterPost,
+             onerror: function(res){log('onerror',res.responseText, '\n',res.responseHeaders)},
+             data: request
+           });
+       }
+       var postToDelicious = function(nodes, urls){
+           if(!urls.length){
+               window.Minibuffer.status('bookmark'+time,'Bookmark 100 %', 1000)
+               return;
+           }
+           window.Minibuffer.status('bookmark'+time,'Bookmark ' + Math.floor((total-urls.length) / total * 100) + '%');
+           getParam(nodes.shift(), urls.shift(), post, function(){postToDelicious(nodes, urls)});
+       }
+       postToDelicious(nodes, urls);
+   };
+
 // var delicious = function(urls, tags){
 //     log('delicious: urls:tags',urls, tags);
 // }
@@ -147,7 +231,7 @@
 //     log('livedoor: urls:tags',urls, tags);
 // }
    addBookmark('hatena',    'h', hatenab);
-// addBookmark('delicious', 'd', delicious);
+   addBookmark('delicious', 'd', delicious);
 // addBookmark('livedoor',  'l', livedoorclip);
    
    window.Minibuffer.addCommand({
Posted at by




デジャブかもしれません
twitterを使ってみんなでアイデアを共有するサービス、ひらめいったーはてなスターサイト上で、twitterユーザのアイコンが表示されるようにするグリモン書いた。

続きを読む...

Posted at by




Firefoxをshellの様に扱えるグリモン「Minibuffer ? Userscripts.org」で使える、はてなブックマークコマンド「hatena-bookmark」書いた。 Minibufferは、LDRizeと併用すると「pinned-link」や「pinned-node」というルートコマンド(フィルタコマンドではない)が追加される。
例えば、LDRizeでpinを付けたリンクをタブで開くには :pinned-link | open とすれば良い。ここで注意しなければならないのがグリモンの設定で
  • Minibuffer
  • LDRize
  • ...LDRizeプラグインもしくはMinubufferプラグイン...
という順番にしておかないと、「pinned-link」や「pinned-node」が正しく動かなくなるので注意。

この順番になるように、以下のスクリプト入れると、「hatena-bookmark」コマンドが追加される。
LDRizeを入れておくと、ブログ等の記事1つずつにpinが付けられるようになっていて、そのpinを付けたものに対してパイプ形式にコマンドを繋げられる。
例えば、pinをつけたリンクのGoogleキャッシュを開くには :pinned-link | google-cache | open とすればよい。
※pinをつけるには「p」を押す。

「hatena-bookmark」の使い方は、何もpinしていない状態で「Alt-x」(ALTキーを押しながらx)を押して :hatena-bookmark とすると、現在見ているページのはてなブックマーク登録画面が立ち上がる。またpinを付けた状態で :pinned-link | hatena-bookmark とすると、pinが指すリンクのブックマーク登録画面が立ち上がる。
なお :pinned-node | hatena-bookmark では、pinしているHTMLノードの先頭にあるリンクを使ってブックマーク登録画面を表示する。
ちなみにソースはこんな感じ。 // ==UserScript==
// @name           Minibuffer Hatena Command
// @namespace      Minibuffer.Hatena-Command
// @description    add hatena-bookmark command to Minibuffer.
// @include        http://*
// @include        https://*
// ==/UserScript==

var SCRIPT_VERSION  = 'Thu, 15 Nov 2007'

var Minibuffer_Hatena = new function() {
    var self = this;

    this._run_bookmark = function(u, t) {
        var link;
        if (t) link = 'http://b.hatena.ne.jp/add?mode=confirm&is_bm=1&title='+escape(t)+'&url='+escape(u);
        else   link = 'http://b.hatena.ne.jp/add?mode=confirm&is_bm=1&url='+escape(u);

        setTimeout(function(){
            window.open(link, '_blank', 'width=550,height=600,resizable=1,scrollbars=1');
        }, 10);
    }

    this.bookmark = function ( stdin ) {
        if (stdin.length == 0) {
            self._run_bookmark(location.href, document.title);
        } else {
            stdin.forEach(function(obj){
                if (typeof obj == 'string' && obj.match(/^https?:.*/)) self._run_bookmark(obj);
                else if (typeof obj == 'object') {
                    try {
                        var links = obj.getElementsByTagName('a');
                        for(var n = 0; n < links.length; n++) {
                            if (links[n].href.match(/^https?:.*/)) {
                                self._run_bookmark(links[n].href);
                                break;
                            }
                        }
                    } catch(e) { }
                }
            });
        }
    };
    this.condition = function() { return true; };
};

if (window.Minibuffer) {
    Minibuffer.addCommand({
        "hatena-bookmark": Minibuffer_Hatena.bookmark,
    });
}

インストール:minibuffer.hatena.user.js
ちなみに、ファイル名が「minibuffer.hatena.bookmark.user.js」になっていないのは、いずれ他のコマンドも作って行きたいから...。

あ、あと記事とは無関係ですが、今日CodeReposのLDRize-SITEINFO書いときました。

追記1
bookmarkコマンドで、stdinを返すように修正した。
これで :pinned-link | hatena-bookmark | clear-pin とか出来る様になった。

追記2
申し訳ない。objがtypeofでstringとして戻るパターンとobjectとして戻るパターンがあるようです。修正しました。
Posted at by




Firefoxをshellの様に扱えるグリモン「Minibuffer ? Userscripts.org」で使える、Fuck!コマンド「fuck」書いた。 使い方は、はてなブックマークコマンドとほぼ同じ。
目障りな記事を見つけたら :fuck とすれば良い。 もしくはpinを付けて :pinned-link | fuck とすればpinを付けたリンクが全部「Fuck!」になる。
なお :pinned-node | fuck とすれば、pinが指すノード自体が削除される。
例えばtwitterなんかで
minibuffer-fuck1
followerの発言をpinしてfuckしようもんなら
minibuffer-fuck2
こんなんになっちゃう!
※followerの皆様ごめんなさい。

ソースはこんな感じ // ==UserScript==
// @name           Minibuffer Fuck Command
// @namespace      Minibuffer.Fuck.Command
// @description    add fuck command to Minibuffer.
// @include        http://*
// @include        https://*
// ==/UserScript==

window.Minibuffer.addCommand({
  name: 'fuck',
  command: function(stdin){
    if (stdin.length == 0) {
      document.open();
      document.write('<h1>Fuck!</h1>');
      document.close();
    } else {
      stdin.forEach(function(obj){
        if (("" + obj).match(/^https?:.*/)) {
          var links = document.getElementsByTagName('a');
          for(var n = 0; n < links.length; n++) {
            if (links[n].href == obj) {
              links[n].href = 'javascript:void(0);';
              links[n].addEventListener('click', function() {alert(this.innerHTML)}, false);
              links[n].innerHTML = 'Fuck!';
            }
          }
        } else if (typeof obj == 'object') {
          try {
            obj.parentNode.removeChild(obj);
          } catch(e) { }
        }
      });
    }
    return stdin;
  }
});

インストール:minibuffer.fuck.user.js

てか使い道なくね?
Posted at by




オフィシャル側で対応頂いたので、このグリモンの必要性は無くなりました。はてなスター日記
twitter上に、はてなスターを置く為のグリモン「つい☆すた」のサイト、「Twitter: What are you doing?」上でtwitterユーザのアイコンが表示されるようにするグリモン書いた。

続きを読む...

Posted at by




んー。
// ==UserScript==
// @name           Speedtest Ikasama
// @namespace      Speedtest.Ikasama
// @include        http://speedtest.10-fast-fingers.com/
// ==/UserScript==

var w=document.getElementById('eingabe');
var v=document.getElementById('vorgabe');
var s = 0;
var z = unsafeWindow.pruefstring;
var f1 = unsafeWindow.welchedown;
var f2 = unsafeWindow.welcheup;
unsafeWindow.wort = (z+z+z+z+z).split(' ');
(function go() {
    clearTimeout(s);
    var t = v.innerHTML.replace(/<[^>]+>/g, '');
    if (t){
        var i=0;
        while(true){
            t = v.innerHTML.replace(/<[^>]+>/g, '');
            var e = document.createEvent('KeyboardEvent');
            e.initKeyEvent('keypress', true, true, window, false, false, false, false, 0, t.charCodeAt(i));
            w.dispatchEvent(e);
            f1(e);f2(e);
            if (++i >= t.length) break;
        }
    }
    s = setTimeout(go, 0);
})();
こんなの使っても
typing-fast
8位って事は、1位の人もHackingやん!www
こんな速度、人間じゃありえん!
speedtest.ikasama.user.js
# 10-fast-fingers.com - Speedtest


ちなみにこのスクリプト、なんでこんな変な事しているかといいますと...
まずど頭で、「z+z+z+ ...」ってやってる部分は、あまりにタイプスピードが速すぎて、内部の配列がオーバーフローしてしまっているのでキャパを取り直しています。次にループ内で再度「t」を取り直しているのは、このSpeedtestがイカサマ防止の為にタイプ中にワードを増やす処理が入っており、それをハンドリングする為に入っています。
Posted at by




こりゃすげ。
URLを渡すとページの内容(htmlソース)をJSONPとかで取得できるAPIをYahoo! Pipesで作った(管理人日記) - むぅもぉ.jp
Yahoo! PipesにFetch Pageモジュールが追加されたので、さっそく作ってみた。
これで色んなことが出来るようになるし、グリースモンキーに頼っていたクロスドメインな処理がJSONだけで出来るようになる。
これまでにも同様の事が出来るサービスはあったけど、Yahoo! Pipesがサポートしたってのは強い。
試しにYahoo! JapanのHTMLをレンダリングするサンプルを作ってみた。
以下コード
<script type="text/javascript"><!--
function on_load_document(data) {
    document.getElementById('yahoo_html').innerHTML = data.value.items[0].description;
}
function load_document() {
    var url = 'http://www.yahoo.co.jp/';
    var s = document.createElement('script');
    s.charset = 'utf-8';
    s.src = 'http://pipes.yahoo.com/poolmmjp/page_loader?url=' + encodeURIComponent(url) + '&_render=json&_callback=on_load_document';
    document.body.appendChild(s);
}
--></script>

<input type="button" onclick="load_document()" value="Yahoo! Japanを表示">
<div id="yahoo_html"></div>
実行結果は↓

続きを読む...

Posted at by




まぁ、ただそれだけなんですが...
はてなスターに連射ボタンをつけるBookmarklet - 0x集積蔵
tyoro.exe はてなスターに連射ボタンをつけるBookmarklet 改造版
Operaは...しらん。
はてスタ連射
コード
(function(){
var SPEED=2000;
var d=document;
function r(b,m){
    b.onclick=function(){
        var t=setInterval(function(){
            var e;
            if(d.all){
              e=d.createEventObject();
              e.target=m;
              m.fireEvent("onclick",e);
            }else{
              e=d.createEvent("MouseEvents");
              e.initMouseEvent("click",true,true,window,1,10,50,10,50,0,0,0,0,1,m);
              m.dispatchEvent(e);
            }
        },SPEED);
        b.innerHTML='[STOP!]';
        b.onclick=function(){
            clearInterval(t);
            b.innerHTML='[&#36899;&#23556;!]';
            r(b,m);
        }
    }
}
var im=d.images;
for(var i=0,l=im.length;i<l;i++){
    var m=im[i];
    if(m.className=='hatena-star-add-button'){
        var b=m.parentNode.appendChild(d.createElement('b'));
        b.innerHTML='[&#36899;&#23556;!]';
        r(b,m);
    }
}
})();
Posted at by




そうなのか...知らなかった。

The Future of JavaScript
var a = [1,2,3]; var a2 = a.pop; var b = a2() は?
  this のコンテキストが変わってるのでエラーになる
なんでだろ。 メソッド呼び出し時に正しくコンテキストスイッチしてないって事?

例えば var obj = new(function Clazz() {
  var self = this;
  self.value = 0;
  self.plus = function() { self.value++; };
})();
var func_plus = obj.plus;
func_plus();
alert(obj.value);
なら結果は「1」になるよね?それってもしかして var obj = new(function Clazz() {
  this.value = 0;
  this.plus = function() { this.value++; };
})();
var func_plus = obj.plus;
func_plus();
alert(obj.value);
こうなってるって事じゃないの?うむ...それっていいの?
コンテキスト保持しておけば function Clazz() {
  var self = this;
  self.value = 0;
  self.plus = function() { self.value++; };
}
var obj1 = new Clazz();
var obj2 = new Clazz();
var func_plus = obj1.plus;
func_plus();
obj2.plus = func_plus;
obj2.plus();
alert(obj1.value + "," + obj2.value);
こんな事しても、例外出ずに動くよね?obj1.valueがインクリメントされる結果になるけど...

これってperlなら #!/usr/bin/perl

use strict;
use warnings;

package Clazz;
sub new {
  my $class = shift;
  my $self = { value => 0 };
  return bless $self, $class;
}
sub plus {
  my $self = shift;
  ++$self->{value};
  1;
}

my $obj1 = Clazz->new;
my $obj2 = Clazz->new;

my $func_plus = *Clazz::plus;
warn "func_plus=".$func_plus."\n";
$func_plus->($obj1);
warn sprintf "obj1->{value}=%d, obj2->{value}=%d\n",
    $obj1->{value}, $obj2->{value};

$obj2->{plus} = $func_plus;
$obj2->plus();
warn sprintf "obj1->{value}=%d, obj2->{value}=%d\n",
    $obj1->{value}, $obj2->{value};
こんなサンプルかな。実行結果は func_plus=*Clazz::plus
obj1->{value}=1, obj2->{value}=0
obj1->{value}=1, obj2->{value}=1
サブルーチンにはselfを渡すのはインスタンス自身になるから問題無く動くね。javascriptのようにfunctionの参照自身がインスタンス参照を保持しちゃってる訳じゃなので問題は発生しないか...

pythonなら class Clazz:
  def __init__(self):
    self.value = 0

  def plus(self):
    self.value = self.value + 1

obj1 = Clazz()
obj2 = Clazz()

func_plus = obj1.plus
print "func_plus=%s" % func_plus
func_plus()
print "obj1.value=%d, obj2.value=%d" % (obj1.value, obj2.value)

obj2.plus = func_plus
obj2.plus()
print "obj1.value=%d, obj2.value=%d" % (obj1.value, obj2.value)
こんなサンプルかな?実行結果は func_plus=<bound method Clazz.plus of <__main__.Clazz instance at 0xb7ef828c>>
obj1.value=1, obj2.value=0
obj1.value=2, obj2.value=0
pythonの場合は関数の参照自身がインスタンスの参照も保持しているから、いくらobj2.plusを上書きしてもobj2.valueが更新される訳じゃない。

rubyなら class Clazz
    def initialize() @value = 0 end
    def plus() @value += 1 end
    attr_accessor :value
end

obj1 = Clazz::new
obj2 = Clazz::new
func_plus = Clazz.instance_method :plus
puts "func_plus=%s" % func_plus
func_plus.bind(obj1).call
printf("obj1.value=%d, obj2.value=%d\n", obj1.value, obj2.value)
func_plus.bind(obj2).call
printf("obj1.value=%d, obj2.value=%d\n", obj1.value, obj2.value)
こんな感じ?実行結果は func_plus=#<UnboundMethod: Clazz#plus>
obj1.value=1, obj2.value=0
obj1.value=1, obj2.value=1
rubyの場合はperlぽいけど、UnboundMethodの呼び出しはまずインスタンスをbindしてから呼ぶ事になるので問題は発生しない。ちなみにobj1.plusを取得してobj2.plusに代入できないかやってみたけど、私の力足らずなのか出来なかった。

luaなら function Clazz()
  return {
    value = 0,
    plus = function(self)
      self.value = self.value + 1
    end
  }
end
local obj1 = Clazz()
local obj2 = Clazz()
local func_plus = obj1.plus
print("func_plus", func_plus)
func_plus(obj1)
func_plus(obj2)
print("obj1.value=", obj1.value)
print("obj2.value=", obj2.value)
こんな感じ?実行結果は func_plus   function: 0x8f36270
obj1.value= 1
obj2.value= 1
luaも結局selfを渡す事になるので、問題は派生しない。
ちなみにC++なら #include <stdio.h>

class Clazz {
private:
  int _value;
public:
  Clazz() : _value(0) {}
  void plus() { _value++; }
  int value() { return _value; }
};

int
main(int argc, char* argv[])
{
    Clazz* obj1 = new Clazz();
    Clazz* obj2 = new Clazz();

    typedef void (Clazz::*def_func_plus)();
    def_func_plus func_plus = &Clazz::plus;
    printf("func_plus=%p\n", func_plus);
    (obj1->*func_plus)();
    printf("obj1->value=%d, obj2->value=%d\n",
            obj1->value(), obj2->value());

    // obj2->plus = func_plus; # compile error

    return 0;
}
こんな感じ?実行結果は func_plus=0x8048574
obj1->value=1, obj2->value=0
まぁ、無茶すればobj2->plusも置き換えられない事はないけど、結果は見えてるからいいでしょ...

つまり、著名な言語で問題が発生しているのはjavascriptだけって事か...
それって...

まずくない?
Posted at by




その1 3桁ごとに区切る - PleasureDelayerDiary はてなブックマーク数


Number.prototype.split3_1 = function() {
    var r = ""; 
    var s = this.toString().split("").reverse();
    for(var i = 0; i < s.length; i++) {
        if(i % 3 == 0 && i != 0 && s[i] != "-") {
            r = s[i] + "," + r
        } else {
            r = s[i] + r;
        }
    }  
    return r;
}

その2 iandeth. - javascriptで数値をカンマ区切り文字列に変換する関数メモ はてなブックマーク数


Number.prototype.split3_2 = function () {
    var to = String(this);
    var tmp = "";
    while (to != (tmp = to.replace(/^([+-]?\d+)(\d\d\d)/,"$1,$2"))){
        to = tmp;
    }
    return to;
}

その3 JavaScriptで数値を3桁ごとに区切る - 0x集積蔵 はてなブックマーク数


Number.prototype.split3_3 = function() {
    var m = (this &lt; 0) ? -1 : 1;
    var str = String(this*m).split('.');
    var arr = String(str[0]).split(''), len = Math.ceil(arr.length/3), res = [];
    for (var i =0;i&lt;len;++i) res.push(arr.splice(-3,3).join(''));
    return (m == -1 ? '-' : '') + res.reverse().join(',') + (str[1] ? '.' + str[1] : '');
};

その4 [JavaScript]数値を3桁ごとに区切る はてなブックマーク数


Number.prototype.split3_ore = function() {
  ('' + this).match(/(-?)([0-9]+)(\.[0-9]*)?/);
  var sp = [RegExp.$1, RegExp.$2, RegExp.$3];
  var x = Math.floor(sp[1].length / 3) * 3;
  var len = sp[1].length;
  return sp[0] + (sp[1].substr(0, len - x)) + (len - x == 0 ? '' : ',') +
         (sp[1].substr(len - x, x).match(/[0-9]{3}/g).join(',')) + sp[2];
}
私ならこう書く。
Number.prototype.split3 = function() {
  var r = "", s = this.toString().split("").reverse().join("").replace(/\d{3}/g,
    function(v){r+=v+',';return ''});
  return (r + s).split("").reverse().join("");
}
alert((1000000).split3()) // 1,000,000
文字列を逆にして、replaceに指定した関数で3桁毎にカンマを入れた結果と、空で置換したsubstituteの結果(あまった結果)を足す。その後文字列を逆にして戻す。
マイナスもたぶんOK。


追記1
しまった。チェック甘すぎ。
Number.prototype.split3 = function() {
  var r = "", s = this.toString().split("").reverse().join("").replace(/\d{3}/g,
    function(v){r+=v+',';return ''});
  if (!s.match(/\d/)) r = r.substr(0, r.length-1);
  return (r + s).split("").reverse().join("");
}
計測してみる!
mattnおそ!www
関数呼び出しコストか?
追記2
うむ。小数か...
Number.prototype.split3_mattn2 = function() {
  var r = '', s = this.toString();
  s.match(/(-?)([0-9]+)(\.[0-9]*)?/);
  var sp = [RegExp.$1, s = parseInt(RegExp.$2), RegExp.$3];
  while(s >= 1000) {
    r = ',' + (s%1000) + r;
    s = parseInt(s/1000);
  }
  return sp[0] + s + r + sp[2];
}
-12345678.2356を3桁ごとに区切るテスト

タイム測定



うむ。それでも遅い。
Posted at by




はてなTIPS - アドレスから「id naoya」でダイアリーに
こんなの使えないかなぁ...
javascript:alert(%s);void 0
これを「js」てキーでレジストリ登録しておけば今まで javascript:alert(document.getElementById('banner'));void 0 ってやってたオブジェクトの存在確認が js document.getElementById('banner') って感じに少し楽になる。
単に評価式を返すので js document.getElementById('banner').innerHTML = 'ばなー'; ってな感じにも使えますね。

あと、もうすこし凝ってダイナミックローダ作ってamachangのjavascript-xpath.js使って xpath //div[@id='foo']//span なんて事も出来るのかも。もちろんIEなのでtoJSONなんかを実装した方がalertで確認し易いけど...。

Posted at by




[Autopagerize]pagerizeをダブルクリックで制御するように変更

[AutoPagerize]Pagerizeしてほしくない時も有るんだよな。誤爆防止に右上の■で切り替えれば良いけど。グッとガッツポーズしたら無効とかほしい
確かに右上のちっちゃいのにマウス合わせて「on/off」をクリックするのってマンドクサイ。
Toggle AutoPager
こんなブックマークレットをブックマークツールバーに置くか、keyconfigで「ctrl+g」とかにこのブックマークレット登録しておいて、「グッ」と発音しながら「ctrl+g」を押すと便利だったり便利じゃなかったり...。
Posted at by




いぬビームさんが作ったはてなスターをプロフィールアイコンに変えるブックマークレットで遊んでたんですが、複数の日記を含んだページや、はてなブックマークに付けられた複数人への「はてなスター」ではいちいち数字をクリックして展開しなくてはなりません。
で...書いた。

久々javascript書いた。

今日も徹夜だ。

同情するなら☆おくれ。

ブックマークレット:はてなスターの数字を展開

ソースコードは↓
javascript:var d=document,e=d.createEvent('MouseEvents');if(typeof d.getElementsByClassName=='undefined')d.getElementsByClassName=function(c){var m=[];var n=d.body.getElementsByTagName('*');for(var i=0;i<n.length;i++)if(n[i].className == c)m.push(n[i]);return m;};void(0);d.getElementsByClassName('hatena-star-inner-count').forEach(function(i){e.initEvent('click', true, true);i.dispatchEvent(e)});void(0);
※たぶんfirefoxでしか動かない。
※ちなみに私のサイトでは数字が出るほど☆がないので、いぬビームさんとこでやるのがいいかと...
追記
修正しました。document.getElementsByClassNameがundefinedでした。
Posted at by




Plaggerで、はてなハイクにポストするPublish::HatenaHaiku書いた。
コードはこんな感じ
package Plagger::Plugin::Publish::HatenaHaiku;
use strict;
use base qw( Plagger::Plugin );

use Encode;
use Time::HiRes qw(sleep);
use URI;
use Plagger::Mechanize;

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'publish.entry' => \&add_entry,
        'publish.init'  => \&initialize,
    );
}

sub initialize {
    my $self = shift;
    unless ($self->{mech}) {
        my $mech = Plagger::Mechanize->new;
        $mech->agent_alias('Windows IE 6');
        $mech->quiet(1);
        $self->{mech} = $mech;
    }
    $self->login_hatena_haiku;
}

sub add_entry {
    my ($self, $context, $args) = @_;

    unless ($self->conf->{default_keyword}) {
        Plagger->context->log(error => 'set default_keyword.');
    }
    my $summary = encode('utf-8', $args->{entry}->title)
        . "\n" . encode('utf-8', $args->{entry}->link);
    my $keyword = $self->conf->{default_keyword};

    my $keyword_behaviour = $self->conf->{keyword_behaviour};
    if ('default' ne $keyword_behaviour) {
        if ('tag' eq $keyword_behaviour) {
            my @tags = @{$args->{entry}->tags};
            $keyword = $tags[0] if ($tags[0]);
        }
        if ('title' eq $keyword_behaviour) {
            if ($summary =~ /^\[([^\]]+)\]/) {
                $keyword = $1;
            }
        }
    }

    my $res = eval { $self->{mech}->get('http://h.hatena.ne.jp/') };
    if ($res && $res->is_success) {
        eval {
            $self->{mech}->submit_form(
                form_number => 1,
                fields => {
                    word       => encode('utf-8', $keyword),
                    body       => $summary,
                },
            )
        };
        if ($@) {
            $context->log(info => "can't submit: " . $@);
        } else {
            $context->log(info => "Post entry success.");
        }
    } else {
       $context->log(error => "fail to post HTTP Status: " . $res->code);
    }
 
    my $sleeping_time = $self->conf->{interval} || 3;
    $context->log(info => "sleep $sleeping_time.");
    sleep( $sleeping_time );
}

sub login_hatena_haiku {
    my $self = shift;
    unless ($self->conf->{username} && $self->conf->{password}) {
        Plagger->context->log(error => 'set your username and password before login.');
    }
    my $res = $self->{mech}->get('https://www.hatena.ne.jp/login?location=http://h.hatena.ne.jp/');
    $self->{mech}->submit_form(
        form_number => 1,
        fields => {
            name  => $self->conf->{username},
            password => $self->conf->{password},
        },
    );
    if ($self->{mech}->content !~ 'http-equiv="Refresh"') {
        Plagger->context->log(error => "failed to login to hatena haiku.");
    }
}

1;

__END__

=head1 NAME

Plagger::Plugin::Publish::HatenaHaiku - Post to hatena haiku automatically

=head1 SYNOPSIS

  - module: Publish::HatenaHaiku
    config:
      username: your-username
      password: your-password
      default_keyword: id:mattn
      #keyword_behaviour: title
      #keyword_behaviour: tag
      #interval: 2

=head1 DESCRIPTION

This plugin automatically posts feed updates to hatena haiku
L<http://h.hatena.ne.jp/>. It supports automatic tagging(keyword) as well.
It might be handy for synchronizing your blog feeds into hatena haiku.

=head1 CONFIG

=over 4

=item username, password

username and password for Hatena Haiku. Required.

=item default_keyword

default keyword string. Required.

=item keyword_behaviour

Optional. if set 'title', it should accept 'plagger' in '[plagger]title'.
if set 'tag', it should accept a first of tags in feed.

=item interval

Optional.

=item timeout

Optional.

=back

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plagger>, L<Plagger::Mechanize>

=cut

default_keywordは必須項目として、keyword_behaviourでキーワードの動作が変えられます。
keyword_behaviourに何も設定しなければ(もしくは'default')、default_keywordが使われ、'title'を設定すれば"[書評]ほにゃらら"の"書評"がキーワードに、'tag'を設定すればフィードタグの先頭を使う様になっています。
ただ、やっぱりフィードの転載なのではてなハイクのトップページに掲載する/掲載しないのオプションが欲しいなぁ...=>はてな事務局さん

なお、コードはCodeReposに置いてあります。
lang/perl/plagger/lib/Plagger/Plugin/Publish/HatenaHaiku.pm

ところで、dankogai氏それっぽい事(書評とか)やってるように見えるんだけど、もしかして手動だろうか?
Posted at by




なんにつかうねん!www
/lang/perl/Acme-Jyogakusei/trunk/ - CodeRepos::Share - Trac
こうか!? #!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Acme::Jyogakusei;

my $jyogakusei = '女子高生';
my $re = Acme::Jyogakusei::regexp;
print "Jyogakusei\n" if $jyogakusei =~ /$re/;
あと #!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Acme::Jyogakusei;

my $jyogakusei = 'なんちゃって女子高生';
my $re = Acme::Jyogakusei::regexp;
$jyogakusei =~ s/(なんちゃって$re)/おばちゃん/g;
print $jyogakusei;
こんなんもありか...
Posted at by




iTunesのライブラリ情報XMLファイルをアップロードする事で自分に合ったアーティスト情報を教えてくれるサービス、「veena!」の検索ボックスを使って、指定のアーティストに関連する
  • YouTube動画
  • Yahooオークション情報
をWeb::Scraperでスクレイピングしてみようと思います。
ソースはそれ程難しくもなく
#!/usr/bin/perl

use strict;
use warnings;

use URI;
use URI::Escape qw(uri_escape_utf8 uri_unescape);
use Web::Scraper;
use YAML::Syck;

my $artist = shift || 'Ozzy Ozbourne';
my $uri = URI->new('http://www.veena.jp/srch_artist.php?artist_name='
    . uri_escape_utf8($artist));

my $youtube_list = scraper {
    process '//table[@class="info_tbl"]/tr/td',
        'video[]' => scraper {
            process '//a[1]', url => sub {
                my $url = shift->attr('href');
                $url =~ s/^.*\?url=(.*)$/$1/;
                uri_unescape($url);
            };
            process '//a[2]', title => 'TEXT';
            process '//img', image => '@src';
        };
    result 'video';
};

my $auction_list = scraper {
    process '//table[@class="info_tbl"]/tr/td',
        'auction[]' => scraper {
            process '//a[1]', url => '@href';
            process '//a[2]', title => 'TEXT';
            process '//img', image => '@src';
        };
    result 'auction';
};

my $artist_list = scraper {
    process '//a[contains(@href, "artist.php")]',
        'artists[]' => scraper {
            process 'a', id => sub {
                my $url = shift->attr('href');
                $url =~ s/^.*id=(.*)$/$1/;
                $url;
            };
            process 'a', 'youtube' => sub {
                my $url = shift->attr('href');
                $url =~ s/artist\.php/http:\/\/veena.jp\/list_youtube\.php/;
                my $list = $youtube_list->scrape(URI->new($url));
                \@$list;
            };
            process 'a', 'auction' => sub {
                my $url = shift->attr('href');
                $url =~ s/artist\.php/list_auction\.php/;
                my $list = $auction_list->scrape(URI->new_abs($url, $uri));
                \@$list;
            };
            process 'a', name => 'TEXT';
        }
};
my $result = $artist_list->scrape($uri);
warn Dump $result;
って感じ。YouTube動画情報一覧とYahooオークション情報はアーティスト情報にぶら下がる形で出力したかったので検索結果一覧用のscraperとその結果を取得するscraperを親子関係にしてあります。
結構一覧としてはキレイに出力されているかと思います。
---
artists: 
  - 
    auction: 
      - 
        image: !!perl/scalar:URI::http http://ac.c.yimg.jp/7/1026/1783/000/img305.auctions.yahoo.co.jp/users/6/4/6/7/rosiertrueblue-thumb-119657918759294.jpg
        title: Ozzy Osbourne
        url: !!perl/scalar:URI::http http://page.auctions.yahoo.co.jp/jp/auction/108393777
      - 
        image: !!perl/scalar:URI::http http://ac.c.yimg.jp/7/1022/1783/000/img245.auctions.yahoo.co.jp/users/6/4/6/7/rosiertrueblue-thumb-119657997018368.jpg
        title: Ozzy Osbourne
        url: !!perl/scalar:URI::http http://page11.auctions.yahoo.co.jp/jp/auction/n61267094
      - 
        image: !!perl/scalar:URI::http http://a1017.lm.a.yimg.com/7/1017/1783/000/img257.auctions.yahoo.co.jp/users/8/2/8/3/kokita74-thumb-119486785113507.jpg
        title: Ozzy Osbourne
        url: !!perl/scalar:URI::http http://page8.auctions.yahoo.co.jp/jp/auction/h52088580
   ...
    id: 216546
    name: Randy Rhoads (Ozzy Ozbourne)
    youtube:
      -
        image: !!perl/scalar:URI::http http://img.youtube.com/vi/MEUbYkLe_wo/default.jpg
        title: Ozzy Ozbourne's top 10 songs
        url: http://www.youtube.com/watch?v=MEUbYkLe_wo
      - 
        image: !!perl/scalar:URI::http http://img.youtube.com/vi/GLtjWi4qkIY/default.jpg
        title: Goodbye to Romance - Ozzy/Randy Rhoads (solo)
        url: http://www.youtube.com/watch?v=GLtjWi4qkIY
      - 
        image: !!perl/scalar:URI::http http://img.youtube.com/vi/AQqbNHhBWcI/default.jpg
        title: iron man
        url: http://www.youtube.com/watch?v=AQqbNHhBWcI
   ...
Ozzy OzbourneのキーワードでRandy Rhoadsも引っかかってウハウハです。
で、このYAMLをどうするか...
use LWP::UserAgent;
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
$ua->agent('Mozilla');
for my $artist (@{$result->{artists}}) {
  for my $video (@{$artist->{youtube}}) {
    my $url = $video->{url};
    my $req = HTTP::Request->new(GET => $url);
    $req->header('Accept-Encoding', 'identity');
    my $res = $ua->request($req);
    if ($res->is_error) {
      if ((my $verify_url = $res->request->uri) =~ /\/verify_age\?/) {
        my $verify_req = HTTP::Request->new(POST => $verify_url, {action_confirm => 'Confirm'});
        $res = $ua->request($verify_req);
        $res = $ua->request($req) if $res->is_success;
      }
    }
    if ($res->content =~ /video_id=([^&]+)&l=\d+&t=([^&]+)/gms) {
      my $flv = "http://youtube.com/get_video?video_id=$1&t=$2";
      print "Downloading $flv\n";
      my $download_req = HTTP::Request->new(GET => $flv);
      $download_req->referer($url);
      my $res = $ua->request($download_req);
      if ($res->is_success) {
        open FH, ">$2.flv";
        binmode FH;
        print FH $res->content;
        close FH;
        print "Downloaded $2.flv\n";
      } else {
        print "Failed to download $2.flv\n";
      }
    } else {
      print "Not found flv file\n";
    }
  }
}
やっぱこうなりますわね...

mattn the crazy train scraper!
Posted at by




タレントスケジュールなんてサイトを見つけたので、さっそくスクレイピング。
ドキュメントに同じid属性が複数あるという、なんともダイナミックなHTMLにもめげず作り上げたのが以下 #!/usr/bin/perl

use encoding 'utf-8';
use strict;
use warnings;
use Encode qw(from_to);
use URI;
use URI::Escape qw(uri_escape_utf8);
use Web::Scraper;
use YAML;

if ($^O eq 'MSWin32') {
    binmode(STDERR, ':encoding(shift_jis)');
    Encode::from_to($ARGV[0], 'cp932', 'utf-8');
}
my $talent = shift || '小島よしお';

my $talent_schedule = scraper {
    process '//div[@class="find_bl"]/following-sibling::*[1]//td', day => 'TEXT';
    process '//div[@class="find_bl"]/following-sibling::*[1]//td/div',
        'schedule[]' => scraper {
            process 'div', media => sub { my $m = $_->attr('class'); $m =~ s/^icon_//g; $m };
            process '/div/a', url => '@href';
            process '/div/a', title => 'TEXT';
            process '/div/node()[1]', timespan => sub {
                my $s = $_->string_value;
                $s =~ s/ //;
                $s =~ s/(^|[^\d])(\d):(\d\d)/0$2:$3/g;
                my @span = split(/[^\d:]/, $s);
                \@span;
            };
        };
    result qw/day schedule/;
};
my $uri = URI->new('http://talent-schedule.jp/'.uri_escape_utf8($talent));
my $oppappi_schedule = $talent_schedule->scrape($uri);
warn Dump $oppappi_schedule;
ちょっと日付まわりで苦労してますが...

小島よしおって、結構番組出てますねぇ。

でもそんなの関係ry)
Posted at by




とりあえず書き上げました。
またまた適当クオリティですが...

lang/perl/Net-Kotonoha - CodeRepos

使い方は...
use warnings;
use encoding 'utf-8';
use Net::Kotonoha;
use Data::Dumper;
use Encode;

#binmode(STDERR, ':encoding(shiftjis)');

my $kotonoha = Net::Kotonoha->new(
        mail     => 'xxxx@example.com',
        password => 'xxxxxxx',
    );

# 新着コト一覧
warn Dumper $kotonoha->newer_list;

# 最近のコト一覧
#warn Dumper $kotonoha->recent_list;

# コト番号120235を取得
my $koto = $kotonoha->get_koto(120235);

# タイトル表示
warn $koto->title;

# ○ユーザ一覧
warn Dumper $koto->yesman;

# ×ユーザ一覧
warn Dumper $koto->noman;

# 回答(0:未回答, 1: ○, 2:×, コメントは省略可能)
$koto->answer(1, 'けもたい');

# 自分の回答
warn Dumper $koto->answer;

※一覧系の戻りはハッシュの配列
こんな感じ...
まぁ、UIで遊ぶサービスですから、あまり使い道はないかもしれませんが...

追記
cpanに入れてありますので「cpan Net::Kotonoha」で入ります。
Posted at by




Web::Scraper使うときに、scraperコマンドを使って頑張る人もいれば、FirebugのDOMツリーで「XPathをコピー」とやっている人もいるでしょう。
前者の場合、端末でスクロールアウトするHTMLを見ながらXPathをこさえて間違ったらズラズラズラ…と画面が流れて行ってしまいます。後者の場合は、CLASSやID属性を使わないXPathが出来上がってしまいます。
映画に出てくるHackerの如く一発でXPathを決められればそれは素晴らしい事だと思いますが、いかんせん幾度か失敗しますよね。
で、何回もXPathを確かめられるツールが欲しいなと思い、perl-GTK2で作ってみました。

画面はこんな感じ
WebScraperHelper1
引数に「http://b.hatena.ne.jp/」を付けて起動したらこんな感じ
WebScraperHelper2
URLを変更して「Get」をクリックすれば再読み込みします。
そして、はてなブックマークトップページの「注目の動画」部分にある画像一覧を取得する為に //a[text()="注目の動画"]/../../..//img
というXPathを書いて「Update」をクリックすれば
WebScraperHelper3
こんな感じのHTMLが出来上がります。
あとはこれをWeb::Scraperのprocess部分に貼っつけるだけ。

ちなみにXPathでの属性値参照も出来ますので、はてなブックマークトップページで //meta[@http-equiv="Content-Type"]/@content
というXPathを書けば content="text/html; charset=UTF-8"
という結果が返ります。
起動にはCPANからGtk2モジュールをインストールする必要があります。HTMLのパース方法やノードの取得方法等は大体Web::Scraperと合わせていますので、Web::Scraperが動く環境にGtk2をインストールすれば動くかと思います。
また画面はLinux上で起動した物ですが、UN*Xらしい事は一切やってませんのでWindowsでも動作するかと思います。
ダウンロード:

もう少し機能を足そうかと思いましたが、今日はもうギブアップ。寝ます。
Posted at by




CodeReposに面白そうな物が入ってたので物色中。
どういうロジックでFriendを探しているのかは、まだ見てませんが...

こんなコードを実行すると... #!/usr/bin/perl

use strict;
use Net::Twitter::Friend::Finder::FromGoogle;

my $twitter = Net::Twitter::Friend::Finder::FromGoogle->new( {
        username => 'xxxxxxxxx',
        password => 'xxxxxxxxx' ,
        on_echo => 1,
        limit => 20 ,
        lang => 'ja' } );

$twitter->search;
$twitter->show;
こんな結果が返ります。 .----------------------------------------------.
| Net::Twitter::Friend::Finder::FromGoogle     |
'----------------------------------------------'
.---------+------------------------------------.
| Keyword | twitter                            |
'---------+------------------------------------'
.-----+----------------------+-----------------.
| #   | Twitter id           | Found count     |
'-----+----------------------+-----------------'
.-----+----------------------+-----------------.
| 1   | ikasam_a             | 1               |
| 2   | tsuda                | 1               |
| 3   | blog                 | 1               |
| 4   | help                 | 1               |
| 5   | tdtds                | 1               |
'-----+----------------------+-----------------'
なんだかwktk。

ところでFriend一覧で出てきたアカウント「help」って...

twitter-help
ちょwww
Posted at by




いいなぁiPod touch欲しいなぁ。

Plaggerでニコニコ動画を一括ダウンロード&変換 Podcast を生成して iPod touch で見る - 2007年11月最新版
うちでは臨時予算案は可決されそうにありません。宝くじが当たるま携帯で我慢したいと思います。

今日は、ゆーすけべさんの所に置いてあるYAMLをちょこっと弄って、携帯向け変換をやろうかと思います。ちょっとだけオリジナリティを出そうとニコニコではなく、YouTubeから...
まず、こんなYAMLを用意します。

youtube_hatena_tagged.yaml
global:
  assets_path: /home/user/plagger/assets/
  timezone: Asia/Tokyo
  log:
    evel: info

plugins:

  - module: Subscription::Config
    config:
      feed:
          - url: http://b.hatena.ne.jp/t/youtube?mode=rss&amp;sort=hot&amp;threshold=3
          
  - module: Filter::FindEnclosures
  - module: Filter::FetchEnclosure
    config:
      dir: /home/user/plagger/out
  - module: Filter::FLVInfo
  
  - module: Filter::FFmpeg
    config:
      command: /usr/bin/ffmpeg
      ext: 3gp
      dir: /home/user/plagger/out
      encoding: utf-8
      options:
        video_codec:         mpeg4
        audio_codec:         libamr_nb
        audio_sampling_rate: 8000
        audio_bit_rate:      12.2k
        frame_size:          176x144
      extra_options: -ac 1 -f 3gp

  - module: Publish::Feed
    config:
      format: RSS
      dir: /home/user/plagger/out
      filename: youtube_hatena_tagged.xml
はてなで「youtube」タグが付いているフィードを取ってきて、Enclosureをフェッチ&解析。最後にFFmpegで変換というフェーズになります。Feed作成は意味なさそうですが、この後役に立ちます。
私の携帯SB810T向けには、映像コーデックにmpeg4、音声コーデックにamr-nb(libamr_nb)を指定します。メーカーの仕様によると、AACでも行けそうな事が書いてありますが、どうやら動画でない音声ファイルの場合のみAACが再生出来るようです。
また、サンプルレート8000はamr-nbの標準値で、オーディオチャネル(ac)は1にしないといけない様です。 もちろん
  • lib/Plagger/Plugin/Filter/FFmpeg.pm
  • lib/Plagger/Plugin/Filter/FLVInfo.pm
はCodeReposから取得して下さい。
殆んどyusukebeさん(yousukebeさんではありません。ここ大事)のと同じです。大概はffmpegのオプションでなんとか出来るかと思います。ただ現状Plaggerのtrunkに入ってるFind-Enclosuresのyoutube.plでは、jp.youtube.comドメインのリソースが対象外になっていますので、以下のパッチを当てます。
Index: assets/plugins/Filter-FindEnclosures/youtube.pl
===================================================================
--- assets/plugins/Filter-FindEnclosures/youtube.pl (revision 1988)
+++ assets/plugins/Filter-FindEnclosures/youtube.pl (working copy)
@@ -3,7 +3,7 @@
 
 sub handle {
     my ($self, $url) = @_;
-    $url =~ qr!http://(?:www.)?youtube.com/(?:watch(?:\.php)?)?\?v=.+!;
+    $url =~ qr!http://(?:www.|jp.)?youtube.com/(?:watch(?:\.php)?)?\?v=.+!;
 }
 
 sub find {
※パッチが当たった物はCodeReposに入れてあります。チンして食べてください。ただGData APIでないのでカッコよくはありませんが。
あとは # plagger -c youtube_hatena_tagged.yaml を実行すると、上のYAMLで指定している「out」フォルダにゴッチョリと3gpファイルが出来上がっている筈です。
携帯の場合はPodcast出来ませんし、動画のサイズが大きくなるとメールで送ったりHTTPでダウンロードする事も出来ません。結局はメモリ転送になりますが、Feed.pmにちょっとだけ手を加えて...
Index: lib/Plagger/Plugin/Publish/Feed.pm
===================================================================
--- lib/Plagger/Plugin/Publish/Feed.pm  (revision 1988)
+++ lib/Plagger/Plugin/Publish/Feed.pm  (working copy)
@@ -122,6 +122,15 @@
     open my $output, ">:utf8", $filepath or $context->error("$filepath: $!");
     print $output $xml;
     close $output;
+
+    if ($self->conf->{command_after}) {
+        my $command = $self->conf->{command_after};
+        my $dir = $self->conf->{dir};
+        $filepath =~ s!\\!/!g;
+        $command =~ s!\$\(filename\)!$filepath!g;
+        $command =~ s!\$\(dir\)!$dir!g;
+        system($command);
+    }
 }
 
 sub make_author {
こんなパッチを当てると、フィード出力時に「command_after」が実行されますので   - module: Publish::Feed
    config:
      format: RSS
      dir: /home/user/plagger/out
      filename: youtube_hatena_tagged.xml
      command_after: find $(dir) -name "*.3gp" -exec cp \{\} /media/usbdisk/private/myfolder/My Items/Video
こんなYAMLにしておけば、出来上がった3GPがUSB越しに携帯のmicroSDカードにズコーーーーンと転送されるって仕組です。
microSD-card
ズゴーーーーン
まぁ、Podcastと違って出来上がるまで、良い子ちゃんで待ってなきゃいけないですが...

追記
plagger流儀で言えば、Notify::Commandを使うべきですね。
Posted at by




今日はもう寝ます。
ttyは明日ミマス。

typester++

sub handle {
    my ($self, $url) = @_;
    $url =~ qr!http://ttyshare.com/rec/\w+!;
}

sub find {
    my($self, $args) = @_;
    
    my $uri = $args->{url};
    my $response = LWP::UserAgent->new->post(
        $uri,
        ['download' => 1]);
    if($response->content =~ m/<div id="p-(.+?)" class="player">/) {
        my $enclosure = Plagger::Enclosure->new;
        $uri = sprintf('http://ttyshare.com/static/tty/%s/%s/%s/%s',
            substr($1, 0, 1), substr($1, 0, 2), substr($1, 0, 4), $1);
        $enclosure->url($uri);
        $enclosure->type('application/x-ttyrec');
        $enclosure->filename("$1.tty");
        return $enclosure;
    }

    return;
}
Posted at by




#!/usr/bin/perl
use strict;
use Perl6::Say;

undef &Perl6::Say::say;
sub my_say {
    my $this = shift;
    print @_;
    $this;
}
*Perl6::Say::say = \&my_say;

STDERR->say('フォォーー!!')->say('セイ')->say('セイ')->say('セイ');

こういうのは、おとなしく package IO::HG4;
use base qw(IO::Handle);

sub say {
    my $this = shift;
    print @_;
    $this;
}

IO::HG4->new->say('フォォーー!!')->say('セイ')->say('セイ')->say('セイ');
するのがいいと思った。
Posted at by




どなたかのはてなブックマークで、「コメント欄」なんてのを見つけたけど、タイトル見るとどうにも開き辛い...
皆さんそんな事を思った事はありませんか?
そんな時にはコレ!

WWW::CommentGetter
assetsにYAML置いて、URL指定すればコメントがゴッソリ!

/home/user/WWW-CommentGetter/assets/spalog-dotei.yaml
---
handle: http://spalog.net/dotei/.*
comments:
  body: div.dotei03-ct
こんなYAML置いて
/home/user/WWW-CommentGetter/spalog-dotei-comment.pl
use strict;
use warnings;
use Encode;
use WWW::CommentGetter;

my $assets_dir = '/home/user/WWW-CommentGetter/assets';
my $getter = WWW::CommentGetter->new($assets_dir);

use YAML;
binmode STDERR, ':encoding(shiftjis)' if $^O eq "MSWin32";
warn Dump $getter->get('http://spalog.net/dotei/ent_1922.php');
こんなコード書けばコメント欄がゴッソリ!
実行結果は各人で...

tokuhiromさん++
Posted at by




終わり無き戦い...
/lang/perl/plagger/lib/Plagger/Plugin/Publish/Magnolia.pm - CodeRepos::Share - Trac
Ma.gnolia.com...意外とUIが好き。

これで、私が同期しているソーシャルブックマークは
  • Publish::Delicious
  • Publish::LivedoorClip
  • Publish::Buzzurl
  • Publish::LivedoorCilp
  • Publish::Buzzurl
  • Publish::GooBookmark
  • Publish::NiftyClip
  • Publish::Pookmark
  • Publish::YahooBookmark
  • Publish::BlueDot という名の Pubilsh::Delicious
  • Publish::Magnolia
となった。
収拾がつかなくなってきた。
さて、明日は何作ろう

追記
otsuneさんのの方がよい。
CodeRepsに上げてあるMagnolia.pmは、plaggerのsvn/trunkが変わったタイミングで消します。
Posted at by




これまた適当に...
/lang/perl/plagger/lib/Plagger/Plugin/Publish/NiftyClip.pm - CodeRepos::Share - Trac
/lang/perl/plagger/lib/Plagger/Plugin/Publish/Pookmark.pm - CodeRepos::Share - Trac
NiftyClipは結構とUIが使いやすい気がした。
あと、POOKMARK Airlinesの方はjavascriptがonじゃないとログイン出来ないって事にハマりかけた。あとtwitterにポストする機能があるので、default_no_twitterという設定で無効に出来るようにした。

これで、私が同期しているソーシャルブックマークは
  • Publish::Delicious
  • Publish::LivedoorClip
  • Publish::Buzzurl
  • Publish::GooBookmark
  • Publish::NiftyClip
  • Publish::Pookmark
となった。
収拾がつかなくなってきた。
Posted at by




私は、Plaggerを使用して、はてなブックマークを別のソーシャルブックマークに同期していますが、その際ブックマークタグがどのように扱われるかについて以下説明します。
はてなブックマークのフィードは
RSS形式
http://b.hatena.ne.jp/[hatena account]/rss もしくは
AtomFeed形式
http://b.hatena.ne.jp/[hatena account]/atomfeed というURLで提供され、データには以下の様な物が含まれています。 ※以下の例はRSSの場合
<item rdf:about="http://coderepos.org/share/changeset/552">
    <title>Changeset 552 - CodeRepos::Share - Trac</title>
    <link>http://coderepos.org/share/changeset/552</link>
    <description>笑わせて頂きました</description>
    <content:encoded>
      &lt;blockquote cite="http://coderepos.org/share/changeset/552" title="Changeset 552 - CodeRepos::Share - Trac"&gt;
        
        &lt;cite&gt;&lt;a href="http://coderepos.org/share/changeset/552"&gt;Changeset 552 - CodeRepos::Share - Trac&lt;/a&gt; &lt;a href="http://b.hatena.ne.jp/entry/http://coderepos.org/share/changeset/552"&gt;&lt;img src="http://b.hatena.ne.jp/images/entry.gif" title="このエントリーを含むブックマーク" alt="このエントリーを含むブックマーク" border="0"&gt;&lt;/a&gt;&lt;/cite&gt;
      &lt;/blockquote&gt;
      &lt;p&gt;笑わせて頂きました&lt;/p&gt;
    </content:encoded>
    <dc:date>2007-10-19T21:19:44+09:00</dc:date>
    <dc:creator>mattn</dc:creator>
    <dc:subject>coderepos</dc:subject>
    <dc:subject>erogeek</dc:subject>
    <taxo:topics>
      <rdf:Bag>
      <rdf:li resource="http://b.hatena.ne.jp/t/coderepos" />
      <rdf:li resource="http://b.hatena.ne.jp/t/erogeek" />
      </rdf:Bag>
    </taxo:topics>
</item>
ブックマークした元リンクのtitle/linkに加え、ブックマークコメントが格納されたdescription、およびblockquote/citeタグを使用して引用元形式に表現されたcontent:encoded、さらにはブックマークタグを表現するdc:subjectが記述されています。
Plaggerの場合、descriptionよりもcontant:encodedを優先しており、コメントとしては冗長な引用部分が転送されてしまいます。これについては先日書いた「Plaggerで、はてなブックマークをdel.icio.usにミラーする時に、descriptionフィールドを衛生的に修正するフィルタプラグイン書いた」にある様にdescriptionをcontent:encodedに上書きしてやる事で対応出来ます。
先日この記事を書いた際、otsuneさんから「この目的であれば、 b.hatena.ne.jp/[hatena user]/atomを Filter::AtomLinkRelated すればOk」というブックマークコメント頂きました。
昨日、頂いたアドバイスの通りAtomFeedで試して見た所、複数設定した筈のブックマークタグが一つだけしか適応されないという現象が発生しました。
IRC(#plagger-ja)でotsuneさん、国内滞在説のあるmiyagawaさんに相談しながら原因を当たった所、昨日の夜にXML::Feedでのdc:subjectの扱い方に問題があるのではないかという事が分かりました。

ここで見て頂きたいのはdc:subjectというノード。dc:subjectは私の記憶ではAtom0.3では厳密に個数は規定されておらず、複数記述する事も出来てしまっています。結果、規定されていないことで色んな実装が表れてしまっています。
はてなの様に複数のdc:subjectを使って表現する物もあれば、del.icio.usの様に一つのdc:subject内に空白(スペース)等でセパレートしてタグを記述している物もあります。
以下、私が簡単に調べた各サービスのフィード出力状況と、そのフィード内のdc:subjectの扱われ方です。
サービス フィード形式 dc:subjectの扱い
はてなブックマーク RSS1.0
Atom0.3
タグ毎にdc:subject
del.icio.us RSS1.0 単一のdc:subjectを空白でセパレート
Livedoor Clip RSS2.0 タグ毎にdc:subject
Buzzurl RSS1.0 タグ毎にdc:subject
Goo Bookmark RSS1.0 出力されない
FC2 Bookmark RSS2.0
※1
出力されない
Pookmark Airlines RSS1.0 タグ毎にdc:subject
※2
Nifty Clip RSS1.0 タグ毎にdc:subject
※3
Blue Dot RSS2.0 単一のdc:subjectをカンマでセパレート
Digg RSS2.0 出力されない
※1 このフィードはちょっと頂けない
※2 入力UIは単一行だがダブルクオート記述出来る
※3 入力UIはjavascriptで追加形式(POSTは1個もしくは配列)
各サービス毎にdc:subjectの扱われ方はまちまちです。
これらの仕様をXML::Feedがどのように扱っているかが原因ではないかと思いました。
現状、XML-Feed-0.12のソースでは

lib/XML/Feed/Atom.pm(146): sub category {
    my $entry = shift;
    my $ns = XML::Atom::Namespace->new(dc => 'http://purl.org/dc/elements/1.1/');
    if (@_) {
        $entry->{entry}->add_category({ term => $_[0] });
    } else {
        my $category = $entry->{entry}->category;
        $category ? ($category->label || $category->term) : $entry->{entry}->get($ns, 'subject');
    }
}
となっていますが、上記"get"ではXML::Atomの"get"が呼ばれ、ARRAYの先頭しか返りません。
XML::Atomには"get"ではなく"getlist"も用意されており、こちらの方はARRAYを返してくれる仕様になっています。
(XML::Feed::RSSの方は元々categoryでARRAYを返す場合もある為、baseであるEntryは既にARRAYを返されても問題ない準備が出来ています)

dc:subjectが単一とは規定されていない事、XML::Atomで"getlist"が用意されている事を、XML::FeedのAUTHORであるBenjamin Trott氏にメールし、パッチも付けて送付しました。
どんな返事が返って来るか分かりませんが、これが正しい修正だとすればXML::Feedのアップグレードで直って来るかもしれません。

しばらくは、はてなブックマークからの同期はrssフィードを使いdescriptionからcontent:encodeを上書きするようなトリックを使うか、AtomFeedを使ってしかも上記の様な修正を入れて対応するかになります。
もしかしたら、Plagger側にcontent:encoded->summaryではなく、description->summaryとなるようなオプション入れても良いかも知れませんね。
それかtsupoさんのbookeyを使うってのもアリですね。
Posted at by




調べたら、BlueDotってdel.icio.us v1 API互換のAPIを公開してた。
ちゃんと調べるべきだなぁ...
ただ、Publish::Delciousではendpoint書き換えられないからパッチ書いた。
Index: lib/Plagger/Plugin/Publish/Delicious.pm
===================================================================
--- lib/Plagger/Plugin/Publish/Delicious.pm (revision 1981)
+++ lib/Plagger/Plugin/Publish/Delicious.pm (working copy)
@@ -18,10 +18,14 @@
 
 sub initialize {
     my ($self, $context, $args) = @_;
-    $self->{delicious} = Net::Delicious->new({
+    my $opt = {
         user => $self->conf->{username},
         pswd => $self->conf->{password},
-    });
+    };
+    for my $key (qw/ endpoint/) {
+        $opt->{$key} = $self->conf->{$key} if $self->conf->{$key};
+    }
+    $self->{delicious} = Net::Delicious->new($opt);
 }
 
 sub add_entry {
YAMLには - module: Publish::Delicious
  config:
    username: del.icio.us-username
    password: del.icio.us-password
    interval: 2
    post_body: 1
    endpoint: https://secure.bluedot.us/v1/
と書くと行ける!

これで、私が同期しているソーシャルブックマークは
  • Publish::Delicious
  • Publish::LivedoorClip
  • Publish::Buzzurl
  • Publish::LivedoorCilp
  • Publish::Buzzurl
  • Publish::GooBookmark
  • Publish::NiftyClip
  • Publish::Pookmark
  • Publish::YahooBookmark
  • Publish::BlueDot という名の Pubilsh::Delicious
となった。
収拾がつかなくなってきた。
こうなったらどこまでやれるか勝負だ
Posted at by




これまた適当に...
/lang/perl/plagger/lib/Plagger/Plugin/Publish/YahooBookmark.pm - CodeRepos::Share - Trac
Yahoo!ブックマーク...正直、今後ログインする機会なんてあるのか?

これで、私が同期しているソーシャルブックマークは
  • Publish::Delicious
  • Publish::LivedoorClip
  • Publish::Buzzurl
  • Publish::GooBookmark
  • Publish::NiftyClip
  • Publish::Pookmark
  • Publish::YahooBookmark
となった。
収拾がつかなくなってきた。
必要かどうかなんて、もうどうでもいい
Posted at by




えーと...。
ただ今、私の環境では
  • Opera 9.50 Alpha
  • Firefox 2.0.0.7
どちらを使ってでもFC2Bookmarkにログイン出来ません。
User Agent Switcher使っても駄目。IE6でログインするとマイブックマーク一覧でjavascriptエラーが多発。登録画面表示しただけで20秒ほどハング。
もうね...ワーキングなんちゃらとか色んな事、言わせんといて下さい。

で、本題。Publish::LivedoorClipをパクらせて頂き、Publish::FC2Bookmarkを書きました。CodeReposに上げときます。
それに合わせて、blosxomのbookmarksプラグインに、FC2Bookmarkの被ブックマーク数画像を追加しています。(こちらはモバイル対応していません)

現状、まともにFC2Bookmarkに登録する方法がPlaggerでしか無いなんて...

「それPla」どころか「それPlaしか」だよ。

Posted at by




Publish::Twitterコピって、Publish::Wassrをでっちあげた。
一応動いてる。TwitterからWassrへポストした結果
※テストでは1件だけポストした。

twitter2wassr.yaml
global:
  assets_path: /home/user/plagger/assets
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://twitter.com/statuses/user_timeline/[twitter user].rss

  - module: Filter::BreakEntriesToFeeds
    config:
      use_entry_title: 1

  - module: Publish::Wassr
    config:
      username: [user name]
      password: [pass word]

Plagger/Plugin/Publish/Wassr.pm
package Plagger::Plugin::Publish::Wassr;
use strict;
use base qw( Plagger::Plugin );

use Encode;
use Net::Wassr;
use Time::HiRes qw(sleep);

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'publish.entry' => \&publish_entry,
        'plugin.init'   => \&initialize,
    );
}

sub initialize {
    my($self, $context) = @_;
    my %opt = (
        user => $self->conf->{username},
        passwd => $self->conf->{password},
    );
    for my $key (qw/ apihost apiurl apirealm/) {
        $opt{$key} = $self->conf->{$key} if $self->conf->{$key};
    }
    $self->{wassr} = Net::Wassr->new(%opt);
}

sub publish_entry {
    my($self, $context, $args) = @_;

    my $body = $self->templatize('wassr.tt', $args);
    # TODO: FIX when Summary configurable.
    if ( length($body) > 159 ) {
        $body = substr($body, 0, 159);
    }
    $context->log(info => "Updating Wassr status to '$body'");
    $self->{wassr}->update( {status => encode_utf8($body)} ) or $context->error("Can't update wassr status");

    my $sleeping_time = $self->conf->{interval} || 15;
    $context->log(info => "sleep $sleeping_time.");
    sleep( $sleeping_time );
}

1;
__END__

=head1 NAME

Plagger::Plugin::Publish::Wassr - Update your status with feeds

=head1 SYNOPSIS

  - module: Publish::Wassr
    config:
      username: wassr-id
      password: wassr-password

=head1 DESCRIPTION

This plugin sends feed entries summary to your Wassr account status.

=head1 CONFIG

=over 4

=item username

Wassr username. Required.

=item password

Wassr password. Required.

=item interval

Optional.

=item apiurl

OPTIONAL. The URL of the API for wassr.jp. This defaults to "http://wassr.jp/user/xxx/statuses" if not set.

=item apihost

=item apirealm

Optional.
If you do point to a different URL, you will also need to set "apihost" and "apirealm" so that the internal LWP can authenticate.

    "apihost" defaults to "api.wassr.jp:80".
    "apirealm" defaults to "API Authentication".

=back

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plagger>, L<Net::Wassr>

=cut
assets/plugins/Publish-Wassr/wassr.tt
[% IF entry.body %][% entry.body_text %][% ELSE %][% entry.title_text %][% END %] [% entry.permalink %]
deps/Publish-Wassr.yaml
name: Publish::Wassr
author: Yasuhiro Matsumoto
depends:
  Net::Wassr: 0

Net::Wassrは[Perl]Net::Wassr - Hatena::Diary::Neko::kak 500 Internal Server Errorを使用。
Posted at by




先日書いた「個人的ソーシャルブックマークサービスの歩き方」という記事にもある通り、私は個人的な資料をdel.icio.us、ソーシャルなものをはてなブックマークに...と使い分けています。
ただし、携帯からはdel.icio.usが使えない為、はてなブックマークを使ってお気に入りユーザのブクマから必要な物だけを自分のブックマークとしてエントリしています。その後、資料として必要な物をdel.icio.usに手作業で転送しています。ただし量が多い場合にはPlaggerを使うこともあります。
ただし、ここで一つ問題が発生していました。
はてなブックマークのフィードにはブクマコメントがitem/descriptionフィールドに格納されています。ただしPublish::Delicousを含むほぼ全てのSBM系プラグインではsummaryではなくbody(body_text)をコメント部として扱う仕様になっています。ですので
http://b.hatena.ne.jp/mattn/rss <description>おぉ。thx>miyagawa</description>
とdescriptionフィールドに格納されている文字列そのままが欲しいにも関わらず <content:encoded>
  &lt;blockquote cite="http://www.ac.cyberhome.ne.jp/~mattn/cgi-bin/blosxom.cgi/software/lang/perl/20071015162834.htm" title="Big Sky :: Publish::Wassrをでっちあげた"&gt;
    
    &lt;cite&gt;&lt;a href="http://mattn.kaoriya.net/software/lang/perl/20071015162834.htm"&gt;Big Sky :: Publish::Wassrをでっちあげた&lt;/a&gt; &lt;a href="http://b.hatena.ne.jp/entry/http://www.ac.cyberhome.ne.jp/~mattn/cgi-bin/blosxom.cgi/software/lang/perl/20071015162834.htm"&gt;&lt;img src="http://b.hatena.ne.jp/images/entry.gif" title="このエントリーを含むブックマーク" alt="このエントリーを含むブックマーク" border="0"&gt;&lt;/a&gt;&lt;/cite&gt;

  &lt;/blockquote&gt;
  &lt;p&gt;おぉ。thx>miyagawa&lt;/p&gt;
</content:encoded>
という元記事の引用文が含まれたbodyで配信されてしまいます。はじめはPublish::XXXでpost_bodyしているSBM系のプラグインを全て直そうかと(use_summaryみたいなオプションで)思いましたが面倒。いっそAggregator::SimpleのXML::Feed::RSSを操作している部分にオプション付けて強制的にcontentでなくsummaryを使わせるように修正しようかとも思いました。ただ、よく考えたらsummaryをbodyに上書きしてやるプラグインを書いた方が便利だし汎用的だと思い以下のプラグインを作りました。
Plagger/Plugin/Filter/SummaryToBody.pm
package Plagger::Plugin::Filter::SummaryToBody;
use strict;
use base qw( Plagger::Plugin );

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'update.entry.fixup' => \&filter,
    );
}

sub filter {
    my($self, $context, $args) = @_;
    $args->{entry}->body($args->{entry}->summary);
}

1;

__END__

=head1 NAME

Plagger::Plugin::Filter::SummaryToBody - copy summary field to body field.

=head1 SYNOPSIS

  - module: Filter::SummaryToBody

=head1 DESCRIPTION

This plugin copy summary field to body field. This is helpful to sanitize
description field. ex) Hatena bookmark field include <blockquote> tag for
quote.

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plagger>, L<Plagger::Plugin::Filter::SummaryToBody>

=cut
使い方はmodule定義だけ。以下は私がはてブからdel.icio.usの転送につかっているYAML
hatebu2delicous.yaml
global:
  assets_path: /home/user/plagger/assets/
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://b.hatena.ne.jp/[hatena user]/rss

  - module: Filter::SummaryToBody

  - module: Filter::Rule
    rule:
      module: Deduped
      path: /tmp/hatebu2delicious.db

  - module: Publish::Delicious
    config:
      username: [delicious username]
      password: [delicious password]
      interval: 2
      post_body: 1
どっちかっていうとBreakXXX系のプラグインで、しかも個人用途でしかありませんが一応公開しておきます。
後でCodeReposにも置いておきます。

追記
もしかしたら空繰再繰さんの「Plagger::Plugin::Filter::ExtractBody」を使ってXPathで「p」とする事でも同じ結果になるかもしれませんね。
こちらは後日試します。
Posted at by




otsune nowa - Publish::GooBookmarkを書くためにHTMLソースとか見てるけど」を見ると、otsuneさんが既に書きかけているかも知れないけど...
私の適当なので良ければ...
package Plagger::Plugin::Publish::GooBookmark;
use strict;
use base qw( Plagger::Plugin );

use Encode;
use Time::HiRes qw(sleep);
use URI;
use Plagger::Mechanize;

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'publish.entry' => \&add_entry,
        'publish.init'  => \&initialize,
    );
}

sub initialize {
    my $self = shift;
    unless ($self->{mech}) {
        my $mech = Plagger::Mechanize->new;
        $mech->agent_alias('Windows IE 6');
        $mech->quiet(1);
        $self->{mech} = $mech;
    }
    $self->login_goo_bookmark;
}


sub add_entry {
    my ($self, $context, $args) = @_;

    my @tags = @{$args->{entry}->tags};
    my $tag_string = @tags ? join(',', @tags) : '';

    my $summary;
    if ($self->conf->{post_body}) {
        $summary = encode('utf-8', $args->{entry}->body_text); # xxx should be summary
    }

    my $uri = URI->new('http://bookmark.goo.ne.jp/add/detail/');
    $uri->query_form(
        url  => $args->{entry}->link,
    );

    my $res = eval { $self->{mech}->get($uri->as_string) };
    if ($res && $res->is_success) {
        eval {
            my $button = $self->{mech}->form_name('boomarkEdit')->find_input('addDetail') || 'editEdit';
            $self->{mech}->submit_form(
                form_name => 'boomarkEdit',
                fields => {
                    title       => encode('utf-8', $args->{entry}->title),
                    keywordlist => encode('utf-8', $tag_string),
                    comment     => $summary,
                    publicno    => 0,
                    point       => $self->conf->{rate} || 1,
                },
                button => $button
            )
        };
        if ($@) {
           $context->log(info => "can't submit: " . $@);
        } else {
            $context->log(info => "Post entry success.");
        }
    } else {
       $context->log(info => "fail to bookmark HTTP Status: " . $res->code);
    }
 
    my $sleeping_time = $self->conf->{interval} || 3;
    $context->log(info => "sleep $sleeping_time.");
    sleep( $sleeping_time );
}

sub login_goo_bookmark {
    my $self = shift;
    unless ($self->conf->{username} && $self->conf->{password}) {
        Plagger->context->log(error => 'set your username and password before login.');
    }
    my $res = $self->{mech}->get('https://login.mail.goo.ne.jp/certify-cgi/login.cgi?site=bookmark.goo.ne.jp');
    $self->{mech}->submit_form(
        form_name => 'f1',
        fields => {
            uname => $self->conf->{username},
            pass  => $self->conf->{password},
        },
    );
}

1;

__END__

=head1 NAME

Plagger::Plugin::Publish::GooBookmark - Post to goo bookmark automatically

=head1 SYNOPSIS

  - module: Publish::GooBookmark
    config:
      username: your-username
      password: your-password
      interval: 2
      post_body: 1
      #rate: 3

=head1 DESCRIPTION

This plugin automatically posts feed updates to goo bookmark
L<http://bookmark.goo.ne.jp/>. It supports automatic tagging as well. It
might be handy for synchronizing delicious feeds into goo bookmark.

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plagger>, L<Plagger::Plugin::Publish::LivedoorClip>, L<Plagger::Mechanize>

=cut
レート(GooBookmarkでいうpoint)を設定出来るようにした。
それと、Publish::LivedoorClipで重複登録の際に、エラーが出ていたので、パッチを書いた。こちらもレートを変えられるようにした。
Index: LivedoorClip.pm
===================================================================
--- LivedoorClip.pm (revision 1976)
+++ LivedoorClip.pm (working copy)
@@ -46,12 +46,17 @@
         tags  => encode('utf-8', $tag_string),
         title => encode('utf-8', $args->{entry}->title),
         notes => $summary,
+        rate  => $self->conf->{rate} || 1,
     );
 
     my $add_url = $uri->as_string;
     my $res = eval { $self->{mech}->get($add_url) };
     if ($res && $res->is_success) {
-        eval { $self->{mech}->submit_form(form_name => 'clip') };
+        eval {
+            my $form_name = 'clip';
+            $form_name = 'edit_form' if $self->{mech}->form_name($form_name);
+            $self->{mech}->submit_form(form_name => $form_name)
+        };
         if ($@) {
            $context->log(info => "can't submit: " . $args->{entry}->link);
         } else {
otsuneさんと、Publish::LivedoorClipのAUTHORさんがOKならば、それぞれCodeReposに上げる予定です。

しかしまぁ、SBM同期用YAMLがエライ事になってきた。
global:
  assets_path: /home/user/plagger/assets/
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://b.hatena.ne.jp/[hatena user]/atomfeed

  - module: Filter::AtomLinkRelated

  - module: Filter::Rule
    rule:
      module: Deduped
      path: /tmp/syncsbm.db

  - module: Publish::Delicious
    config:
      username: xxxx
      password: xxxx
      interval: 2
      post_body: 1
  - module: Publish::LivedoorClip
    config:
      livedoor_id: xxxx
      password: xxxx
      interval: 2
      post_body: 1
      rate: 3
  - module: Publish::Buzzurl
    config:
      usermail: xxxx
      password: xxxx
      interval: 2
      post_body: 1
  - module: Publish::GooBookmark
    config:
      username: xxxx
      password: xxxx
      interval: 2
      post_body: 1
      rate: 3
追記1
otsuneさんのいうWWW::Mechanizeで書いてしまった...
追記2
otsuneさんからツッコミの有難い頂いたので、修正後にCodeReposにアップします。otsuneさんありがとうございました。
追記3
さらにotsuneさんからツッコミの有難い頂いたので、今後は慎重に行きます。苦笑
Posted at by




Publish::Twitterコピって、Publish::Jaikuをでっちあげた。
一応動いてる。TwitterからJaikuへポストした結果
※テストでは1件だけポストした。
※apikeyはココから

twitter2jaiku.yaml
global:
  assets_path: /home/user/plagger/assets
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://twitter.com/statuses/user_timeline/[twitter user].rss

  - module: Filter::BreakEntriesToFeeds
    config:
      use_entry_title: 1

  - module: Publish::Jaiku
    config:
      username: [user name]
      userkey: [your api key]

Plagger/Plugin/Publish/Jaiku.pm
package Plagger::Plugin::Publish::Jaiku;
use strict;
use base qw( Plagger::Plugin );

use Encode;
use Net::Jaiku;
use Time::HiRes qw(sleep);

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'publish.entry' => \&publish_entry,
        'plugin.init'   => \&initialize,
    );
}

sub initialize {
    my($self, $context) = @_;
    my %opt = (
        username => $self->conf->{username},
        userkey => $self->conf->{userkey},
    );
    $self->{jaiku} = Net::Jaiku->new(%opt);
}

sub publish_entry {
    my($self, $context, $args) = @_;

    my $body = $self->templatize('jaiku.tt', $args);
    # TODO: FIX when Summary configurable.
    if ( length($body) > 159 ) {
        $body = substr($body, 0, 159);
    }
    $context->log(info => "Updating Jaiku status to '$body'");
    $self->{jaiku}->setPresence( message => encode_utf8($body) ) or $context->error("Can't update jaiku status");

    my $sleeping_time = $self->conf->{interval} || 15;
    $context->log(info => "sleep $sleeping_time.");
    sleep( $sleeping_time );
}

1;
__END__

=head1 NAME

Plagger::Plugin::Publish::Jaiku - Update your status with feeds

=head1 SYNOPSIS

  - module: Publish::Jaiku
    config:
      username: jaiku-id
      userkey: jaiku-apikey

=head1 DESCRIPTION

This plugin sends feed entries summary to your jaiku account status.

=head1 CONFIG

=over 4

=item username

Jaiku username. Required.

=item userkey

Jaiku apikey. Required.

=item interval

Optional.

=item timeout

Optional.

=back

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plagger>, L<Net::Jaiku>

=cut
assets/plugins/Publish-Jaiku/jaiku.tt
[% IF entry.body %][% entry.body_text %][% ELSE %][% entry.title_text %][% END %] [% entry.permalink %]

どこに納品するかが分かりません。

追記 CodeReposにcommitしました。
Posted at by




Journal of miyagawa (1653)
TEXTや@srcといったショートカット結果に対して任意のフィルタをかませる事が出来るようになったようです。
これまでのように process "span.entry-content", comment => 'TEXT';
と指定していた部分を process "span.entry-content", comment => [ 'TEXT', 'MyFilter' ];
と記述出来るようになったのです。
MyFilterは「Web::Scraper::Filter::MyFilter」というパッケージで定義され、filterプロシージャが呼び出されます。

さっそく、twitterの発言では70%近くが英語のmiyagawaさんの発言をスクレイピングし、エキサイト翻訳で日本語にフィルタするサンプルを作って見ました。
package Web::Scraper::Filter::EnglishToJapanese;
use base qw( Web::Scraper::Filter );
use warnings;
use LWP::UserAgent;
use HTTP::Request::Common qw(POST);

sub filter {
    my($self, $value) = @_;
    my $req = POST( 'http://www.excite.co.jp/world/english/',
        [before => $value, wb_lp => 'ENJA'] );
    my $data = $Web::Scraper::UserAgent->request($req)->content;
    $data =~ s!\x0D|\x0A!!g;
    $data =~ s/^.*?<textarea[^>]*name="after"[^>]*>(.*?)<\/textarea>.*?$/$1/;
    return $data;
}

1;

use URI;
use Web::Scraper;

my $twitter = scraper {
    process 'td.content',
        'comments[]' => scraper {
            process "span.entry-content", comment => [ 'TEXT', 'EnglishToJapanese' ];
        };
    result 'comments';
};
my $comments = $twitter->scrape( URI->new("http://twitter.com/miyagawa/") );
use YAML;
warn Dump $comments;
で、結果
---
- comment: ' ウェブログを作られた http://tinyurl.com/2xldch '
- comment: ' ウェブを出荷します:、:フィルタサポートがある削り器0.21_01。 バージョン番号が言うようにこれがdevリリースであるのに注意してください。'
- comment: ' 見ます。'
- comment: ' ダッシュボード懺悔室、Yellowcard、少年は少女が好きです: 多くの誘惑が今月のSF warfieldで http://www.ticketmaster.com.. を見せます。 ... '
- comment: ' 100のコメント. diggの上のトップページングのためのtakesako、おめでとう、ワオ490、diggs、 http://tinyurl.com/255ht7 '
- comment: ' スクリーンからStreoパート2までNFGを聞くのがあります。 輝かしいアルバム'
- comment: ' @hanekomu、うん、それはしゃぶられます。 そこでは、日本で同じです。 請求先の住所が米国にある状態で、運よく私はcreditcardsを持っています。'
- comment: ' Dashboard Confessionalの新しいアルバム http://www.amazon.com/gp/pr.. を購入した、アマゾン'
- comment: ' より古いリンクの取り逃がすことは一時的であるように思えました。私がfriendfeedされることへのスイッチかJaikuに好きでないので、 http://tinyurl.com/2gq4uj は少し救いました。'
- comment: ' したがって、さえずりの丁付けはいつまでも、行きましたか? そうだとすれば、私は、確実にさえずりを使用するのを止めるつもりです。'
- comment: ' http://subtech.g.hatena.ne... のウェブログを作りました。 Yappo++typester++Plagger++'
- comment: ' @Yappo++'
- comment: ' IT Crowd s02e05を見ます。'
- comment: ' ep1を見ます。'
- comment: ' 12ドルでmonoprice.comからの外でコンポーネントケーブルと結合器を私のPSPビデオに購入しました。 すさまじい値'
- comment: ' 作成されて、playstationのための削り器は、給送 http://tinyurl.com/yqbtjb plagger++ウェブを格納して、発行しています:、:削り器++'
- comment: ' http://feeds.feedburner.com.. に加入しました。'
- comment: ' 私の2週間のabsenseでは、私はWaMu、Master、およびCapitalOneから10の+クレジットカード申し出を受けました。 ため息をついてください。'
- comment: ' ビールを飲みます。'
エキサイト翻訳の部分をスクレイピングするんが筋ちゃうんかいな!というツッコミは無しでお願いします。
Posted at by




こういう使い方もあるね。
で、どうする...って訳でもないけど
※そういうの、「使い道ない」っていうんだよね。そうだよね。
#!/usr/bin/perl

use strict;
use warnings;

use Web::Scraper;
use URI;
use YAML;

my $airlines_accident_scraper = scraper {
  process '//div[@class="entry-content"]//table/tr',
    'airlines[]' => scraper {
      process '//td[1]', title => 'TEXT';
      process '//td[2]', last_accident => 'TEXT';
      process '//td[3]', flight_count => 'TEXT';
      process '//td[4]', death_accident => 'TEXT';
      process '//td[5]', death_rate => 'TEXT';
      process '//td[6]', accident_incidence => 'TEXT';
      process '//td[7]', total_rank => 'TEXT';
    };
  result 'airlines';
};

my $list = $airlines_accident_scraper->scrape(URI->new('http://www.manji.com/jp/2007/08/post_22.html'));
use YAML;
warn Dump $list;
リストは、マスコミが報じない危険な航空会社リストから拝借。

余談ですが...
Web::Scraper 0.16あたりから、@参照するとstringでなく、URIか返ってくるようになってるので、「認証付きのページで@srcを拾い上げて、認証無しでは参照出来ない画像を落とす」なんて事に使えるようになったみたいです。
Posted at by




Web::Scraper 0.15とcisco_scraper.pl
問題が一つ。添削してくださったパッチだと process '//li/node()[4]', 'title' => sub {$_->string_value;};
となっているのですが、4番目とは限らないんです。
たとえば、
http://www.cisco-records.co.jp/html/item/004/010/item393180.html
は何曲か試聴サンプルがないために、この処理だと取得できないです。
おろろ...
これはtext()でTextNodeを参照するしかないですね。
ただ、text()では改行等のゴミまで拾ってしまうので、以下のようにnormalize-space()で空文字ノードを省いています。
もしかすると、node()[2]も同じように修正した方がいいかもしれませんね。
#!/usr/bin/perl

use strict;
use warnings;

use Web::Scraper;
use URI;
use YAML;
use Data::Dumper;

my $uri = shift;

my %scraper;

$scraper{'link'} = scraper {
    process 'a', 'name' => 'TEXT';
    process 'a', 'uri'  => '@href';
    result qw/name uri/;
};

$scraper{'genre'} = scraper {
    process '//a[1]', 'top'   => $scraper{link};
    process '//a[2]', 'style' => $scraper{link};
    result qw/top style/;
};

$scraper{'track'} = scraper {
    process '//li/text()[normalize-space(.)!=""]', 'title' => sub {
        my $s = $_->as_XML;
        $s =~ s/\s+$//;
        return $s;
    };
    process 'li>a', 'uri' => '@href';
    result qw/title uri/;
};

$scraper{'item'} = scraper {
    process 'td.de_title',      'title'  => 'TEXT';
    process 'td.de_artist',     'artist' => 'TEXT';
    process 'td.nm_jacket>img', 'image'  => '@src';
    process 'td.de_price',              'price'   => 'TEXT';
    process 'td.de_label>a',            'label'   => $scraper{link};
    process 'td.de_genre',              'genre'   => $scraper{genre};
    process 'td[headers="de_format"]',  'format'  => 'TEXT';
    process 'td[headers="de_release"]', 'release' => 'TEXT';
    process 'td[headers="de_country"]', 'country' => 'TEXT';
    process 'td[headers="de_sheet"]',   'sheet'   => 'TEXT';
    process 'td[headers="de_arrival"]', 'arrival' => 'TEXT';
    process 'td[headers="de_nomber"]',  'number'  => 'TEXT';
    process '//p[@class="de_star"]/node()[2]', 'star' => 'TEXT';
    process 'ul[id="de_sound"]>li', 'tracks[]' => $scraper{track};
    result
        qw/title artist image price label genre format release release country sheet arrival number star tracks/;
};

my $item = $scraper{'item'}->scrape( URI->new($uri) );
warn Dump $item;
あと、ブックマークコメント
コールバック渡しだと相対URLの展開がされないのは僕だけ?
との事ですが...少し調べてみた所Web::Scraper側でパッチが必要かもしれません。
以下svn/trunk(rev2351)からの差分です。
Index: lib/Web/Scraper.pm
===================================================================
--- lib/Web/Scraper.pm  (revision 2351)
+++ lib/Web/Scraper.pm  (working copy)
@@ -152,12 +152,12 @@
         local $_ = $node;
         return $val->($node);
     } elsif (blessed($val) && $val->isa('Web::Scraper')) {
-        return $val->scrape($node);
+        return $val->scrape($node, $uri);
     } elsif ($val =~ s!^@!!) {
         my $value =  $node->attr($val);
         if ($uri && is_link_element($node, $val)) {
             require URI;
-            $value = URI->new_abs($value, $uri);
+            $value = URI->new_abs($value, $uri)->as_string;
         }
         return $value;
     } elsif (lc($val) eq 'content' || lc($val) eq 'text') {
Posted at by




これでFilterも作りやすくなるのかな...
例えば、はてなブックマークのフィードからShibuya.pmタグが付いてる物のOPMLを作るとか?(自身無さげ)
でもこれ、MIMEパターンをconfig.yamlに上手くめり込ませる方法ってないのかな...
指定する場合、「このURLに対しては変則的なxxxなMIMEで取りたい」って使いたいんだよね。

Index: lib/Plagger/Plugin/Subscription/Feed.pm
===================================================================
--- lib/Plagger/Plugin/Subscription/Feed.pm (revision 1959)
+++ lib/Plagger/Plugin/Subscription/Feed.pm (working copy)
@@ -17,7 +17,6 @@
 sub load {
     my ( $self, $context ) = @_;
 
-    # TODO: Auto-Discovery, XML::Liberal
     my $uri = URI->new( $self->conf->{url} )
       or $context->error("config 'url' is missing");
 
@@ -30,6 +29,20 @@
     my $content = Plagger::Util::load_uri($uri);
     my $feed = eval { Plagger::FeedParser->parse(\$content) };
 
+    if unless($feed) {
+        use HTML::TokeParser;
+        my $parser = HTML::TokeParser->new(\$content);
+        while (my $token = $parser->get_tag("link")) {
+            my $attr = $token->[1];
+            if ($attr->{rel} eq 'alternate'
+                    && ($attr->{type} eq 'application/rss+xml'
+                     or $attr->{type} eq 'application/atom+xml') {
+                $uri = $attr->{href};
+                $feed = eval { Plagger::FeedParser->parse(\$content) };
+                last;
+            }
+        }
+    }
     unless ($feed) {
         $context->log( error => "Error loading feed $uri: $@" );
         return;
Posted at by




どっちかっていうと、今日の出来事みたいな記事になります。
題名の件をやろうとまず、以下のようなYAMLを書いた。
plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://b.hatena.ne.jp/t/shibuya.pm?mode=rss

  - module: Publish::OPML
    config:
      title: Shibuya.pm
      filename: shibuya-pm.opml
で実行したけれどOPMLが空っぽ。
ソースを追ってBreakEntriesToFeedsが使えそうだったので以下の行を足した。   - module: Filter::BreakEntriesToFeeds
    config:
      use_entry_title: 1
でも駄目。で、miyagawaさんにメールした。なぜか英語で...
余談 : なぜ英語か
以前、Web::Scraperについて質問メールを送った。でも反応が無かったので物は試しと英語で書いた。そしたら返事が返って来た。
→ 以後英語... orz
miyagawaさんから、「use BreakEntriesToFeeds」と返事が来たけど、「BreakEntriesToFeeds」は1 entryを1 feedに変換する為のプラグインで、subscriptionを変更するものでは無かった。
で、書きあげたのが以下のプラグイン

BreakEntriesToSubscriptions.pm
package Plagger::Plugin::Filter::BreakEntriesToSubscriptions;
use strict;
use base qw( Plagger::Plugin );

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'update.feed.fixup' => \&break,
    );
}

sub break {
    my($self, $context, $args) = @_;

    for my $entry ($args->{feed}->entries) {
        my $feed = $args->{feed}->clone;
        $feed->clear_entries;

        $feed->add_entry($entry);
        $feed->link($entry->link);
        $feed->url($entry->link);
        eval {
            use HTML::TokeParser;
            my $agent = Plagger::UserAgent->new;
            my $res = $agent->fetch($entry->link, $self);
            my $parser = HTML::TokeParser->new(\$res->content);
            while (my $token = $parser->get_tag("link")) {
                my $attr = $token->[1];
                if ($attr->{rel} eq 'alternate'
                        && ($attr->{type} eq 'application/rss+xml'
                         or $attr->{type} eq 'application/atom+xml')) {
                    $feed->url(URI->new_abs($attr->{href}, $entry->link)-> as_string);
                    last;
                }
            }
        } if $self->conf->{use_auto_discovery};
        $feed->title($entry->title)
            if $self->conf->{use_entry_title};
        $context->subscription->add($feed);
    }

    $context->subscription->delete_feed($args->{feed});
}

1;

__END__

=head1 NAME

Plagger::Plugin::Filter::BreakEntriesToSubscriptions - some entry = 1 subscription

=head1 SYNOPSIS

  - module: Filter::BreakEntriesToSubscriptions

=head1 DESCRIPTION

This plugin breaks all the subscription entries into a single feed. This is
a fairly hackish plugin but it's helpful for make OPML from feeds.

=head1 CONFIG

=over 4

=item use_entry_title

Use subscription's title as a newly generated feed title. Defaults to 0.

=back

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 THANKS

Tatsuhiko Miyagawa

=head1 SEE ALSO

L<Plagger>

=cut
ドキュメントにも書いた通り、ちょっと(かなり?)hackishなプラグインです。
このプラグインを使って

shibuya-pm2opml.yaml
plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://b.hatena.ne.jp/t/shibuya.pm?mode=rss

  - module: Filter::BreakEntriesToSubscriptions
    config:
      use_entry_title: 1
      use_auto_discovery: 1

  - module: Publish::OPML
    config:
      title: Shibuya.pm
      filename: shibuya-pm.opml
こんなYAMLを用意すれば
「Shibuya.pm」のタグが付いている「はてなブックマーク」からオートディスカバリでフィードを取得したOPML
こんなOPMLファイルが出来上がります。

miyagawaさんに感謝

おしまい

※もしかしたらオートディスカバリ出来なかったURL(例えばPDFとか)はOPMLに含めないようにするオプションがいるかも...
Posted at by




Web::Scraperの0.15がリリースされたので、色々試してみてます。
ほんのりと変わった所を調べてみます。

UserAgentが置き換えられるようになった

uaのスコープを変えて頂けたので、先日の「WWW::MechanizeとWeb::Scraperでtwitterのfriendsを全部取ってみる」でやった以下の様な無理やりなUA置き換えでなく undef &Web::Scraper::__ua;
*Web::Scraper::__ua = sub {
    $mech;
};
以下のようにキレイな置き換えが出来るようになります。 $Web::Scraper::UserAgent = $mech;
スコープもourになってますから、別のパッケージ作るときにも便利かもしれませんね。


ショートカットとしてRAWが使えるようになった

これはインラインのjavascriptをそのままスクレイピングするのに使えますね。
#!/usr/bin/perl

use strict;
use warnings;
use Web::Scraper;
use URI;
use YAML;
use Data::Dumper;

my $my_script = scraper {
    process '//script[2]', 'code' => 'raw';
    result 'code';
};
my $item = $my_script->scrape( URI->new("http://mattn.kaoriya.net") );
warn Dump $item;
この例では、はてなスターのトークン設定部が取得出来ます。 --- |-
<!--
Hatena.Star.Token = '43aa5d7954c8fc062faae4eaaa864913599c277b';
-->
※htmlというショートカットでも使えます。


相対URLから絶対URLを自前で生成しなくてもよくなった

お腹が空いてきたのでWeb::Scraperでモスバーガーのメニューをスクレイピング」でやったように、今までは process '.', url => sub {URI->new_abs($_->attr('href'), $uri)->as_string;};
というコードが必要でしたが、「@」による属性参照でリンクエレメントであるようならば、自動的に絶対URLを生成してくれます。
上のようなコードも process '.', url => '@href';
と楽になりますね。
以上が私が0.13と0.15のdiffをざーーーーと見た感じの変更点です。


おまけ

今日はこれに付け加え、一つtipsを...
Web::ScraperでCISCO RECORDSをスクレーピングより
たとえば <li><span>1</span>Track 1</li>
というHTMLから"Track1"だけを抽出しようにも process 'li', 'title' => 'TEXT';
だと 1Track1 なんて結果になるのでそれを回避するために process 'li', 'title' => sub {
    my $elem = shift;
    $elem->find_by_tag_name('span')->delete;
    return $elem->as_text;
};
なんてことをしてるのですが、もっといい方法があるはず。
treeを壊さずやるとすれば、TextNodeを参照するのがいいかと思います。
例えば、XPathのnode()を使い、番号指定で取得します。だた現状のWeb::ScraperではTextNodeはショートカットで参照出来ませんので、以下のようにstring_valueを返すように手を加えると上手く行きます。
※相対URLの修正も含んでいます。
--- cisco_scraper.pl    Tue Sep 18 13:30:20 2007
+++ cisco_scraper2.pl   Tue Sep 18 14:12:49 2007
@@ -14,9 +14,7 @@
 
 $scraper{'link'} = scraper {
     process 'a', 'name' => 'TEXT';
-    process 'a', 'uri'  => sub {
-        return URI->new_abs( $_->attr('href'), $uri )->as_string;
-    };
+    process 'a', 'uri'  => '@href';
     result qw/name uri/;
 };
 
@@ -27,23 +25,15 @@
 };
 
 $scraper{'track'} = scraper {
-    process 'li', 'title' => sub {
-        my $elem = shift;
-        $elem->find_by_tag_name('span')->delete;
-        return $elem->as_text;
-    };
-    process 'li>a', 'uri' => sub {
-        return URI->new_abs( $_->attr('href'), $uri )->as_string;
-    };
+   process '//li/node()[4]', 'title' => sub {$_->string_value;};
+    process 'li>a', 'uri' => '@href';
     result qw/title uri/;
 };
 
 $scraper{'item'} = scraper {
     process 'td.de_title',      'title'  => 'TEXT';
     process 'td.de_artist',     'artist' => 'TEXT';
-    process 'td.nm_jacket>img', 'image'  => sub {
-        return URI->new_abs( $_->attr('src'), $uri )->as_string;
-    };
+    process 'td.nm_jacket>img', 'image'  => '@src';
     process 'td.de_price',              'price'   => 'TEXT';
     process 'td.de_label>a',            'label'   => $scraper{link};
     process 'td.de_genre',              'genre'   => $scraper{genre};
@@ -53,12 +43,7 @@
     process 'td[headers="de_sheet"]',   'sheet'   => 'TEXT';
     process 'td[headers="de_arrival"]', 'arrival' => 'TEXT';
     process 'td[headers="de_nomber"]',  'number'  => 'TEXT';
-    process 'p.de_star',                'star'    => sub {
-        my $elem = shift;
-        $elem->find_by_tag_name('span')->delete;
-        return $elem->as_text;
-
-    };
+    process '//p[@class="de_star"]/node()[2]', 'star' => sub {$_->string_value;};
     process 'ul[id="de_sound"]>li', 'tracks[]' => $scraper{track};
     result
         qw/title artist image price label genre format release release country sheet arrival number star tracks/;
このTextNode参照の需要があるならショートカットでも良いと思うんですが、いまんところ無さそうですね。

追記
otsune氏より「diff -uじゃないと...」という指摘で修正。
Posted at by




Exuberant ctags

ctags 5.7 improves Perl support

  • Added support for 'package' keyword
  • Added support for multi-line subroutine, package, and constant definitions
  • Added support for optional subroutine declarations
  • Added support for formats
  • Ignore comments mixed into definitions and declarations
  • Fixed detecting labels with whitespace after label name
  • Fixed misidentification of fully qualified function calls as labels
これ凄いす。もう朝からお腹いっぱいです。
さっそくWin32版試してみました。
改行されたメソッドにもジャンプ出来るし、余計なコメントにもヒットしないし、使いやすいです。
vimで開発する方のpluginフォルダには必ずと言って良いほど入っているtaglist.vimを使うとパッケージ名称も一覧されます。
パッケージやサブルーチンがキレイに一覧されます。
さらに今回「package」に対応したので、ちょっと時間は掛かりますが C:\Perl\site\lib>ctags -R -h ".pm"
こんな事して...
set tags=./tags,tags,../tags,c:/perl/site/lib/tags
こんな事しておくと...


ctags_beforejump
こんな状態でビジュアル選択しておいて、"C-]"を押す事で
ctags_afterjump
こんな感じにタグジャンプします。ウマーーー
複数含むモジュールだと、taglist.vimのTagListには複数のpackageが表示されます。

すばらしす...
Posted at by




muumoo.jpPlaggerで取得したGoogleブックマークのフィードを整えるFilter:GoogleBookmarksFeedを書いたけど日本語消えちゃう (管理人日記)という記事より。
喜んだのもつかの間、日本語の文字を含むタグやコメントを書くと、その文字が消えてしまうようです。Plaggerではありがちな問題なような気がしますが、このPluginでも起きてしまいました。
確かに、ブラウザ上からだと日本語は見えるんですが、どうやらGoogleさんはUser-Agentを見て勝手にencodingをISO-8859-1に変えておられるようです。
# curl -L 'https://www.google.com/bookmarks/?output=rss' -u username:password
<?xml version="1.0" encoding="ISO-8859-1"?><rss vers
...
config.yamlの先頭に global:
  timezone: Asia/Tokyo
  user_agent:
    agent: Mozilla/5.0
を入れたら取得出来ました。
ブクマコメントで書こうかと思いましたが、記事が半月程前のものなので管理人さんも見てないかと思い、記事にしました。

それよりも...LivedoorClip.pmで Plagger [info] plugin Plagger::Plugin::Subscription::Config loaded.
Plagger [info] plugin Plagger::Plugin::UserAgent::AuthenRequest loaded.
Plagger [info] plugin Plagger::Plugin::Filter::GoogleBookmarksFeed loaded.
Plagger [info] plugin Plagger::Plugin::Publish::LivedoorClip loaded.
Plagger [info] plugin Plagger::Plugin::Bundle::Defaults loaded.
Plagger [info] plugin Plagger::Plugin::Aggregator::Simple loaded.
Plagger [info] plugin Plagger::Plugin::Summary::Auto loaded.
Plagger [info] plugin Plagger::Plugin::Summary::Simple loaded.
Plagger [info] plugin Plagger::Plugin::Namespace::HatenaFotolife loaded.
Plagger [info] plugin Plagger::Plugin::Namespace::MediaRSS loaded.
Plagger [info] plugin Plagger::Plugin::Namespace::ApplePhotocast loaded.
Plagger::Plugin::Aggregator::Simple [info] Fetch https://www.google.com/bookmarks/?output=rss
Plagger::Plugin::UserAgent::AuthenRequest [info] Adding credential to Google Search History at www.google.com:443
Plagger::Cache [debug] Cache HIT: Aggregator-Simple|https://www.google.com/bookmarks/?output=rss
Plagger::Plugin::Aggregator::Simple [debug] 200: https://www.google.com/bookmarks/?output=rss
Plagger::Plugin::Aggregator::Simple [info] Aggregate https://www.google.com/bookmarks/?output=rss success: 15 entries.
Died at C:/Perl/site/lib/WWW/Mechanize.pm line 1705.
なエラーが出る。なんぞ?
とりあえずcpan upgrade行ってきます。

追記1
GoogleBookmarksFeedで、tagsは1個でも配列で返ってきてそうだったので以下のように修正してます。もしかしたら間違ってるかも *** GoogleBookmarksFeed.pm.orig Tue Sep 04 11:39:49 2007
--- GoogleBookmarksFeed.pm  Tue Sep 04 11:40:15 2007
***************
*** 22,28 ****
              $args->{entry}->body($orig_body);
              $context->log(info => "Parsing Google Bookmarks title " . $args->{entry}->permalink);
          }
!         if (my @orig_tags = @{$args->{orig_entry}->{entry}->{$ns}->{bkmk_label}}) {
              $args->{entry}->tags(@orig_tags);
          }
      }
--- 22,28 ----
              $args->{entry}->body($orig_body);
              $context->log(info => "Parsing Google Bookmarks title " . $args->{entry}->permalink);
          }
!         if (my @orig_tags = $args->{orig_entry}->{entry}->{$ns}->{bkmk_label}) {
              $args->{entry}->tags(@orig_tags);
          }
      }
追記2
大嘘ついてました。tagsは1つの場合は文字、2つ以上の場合は配列で戻るみたいです。 *** GoogleBookmarksFeed.pm.orig Tue Sep 04 11:39:49 2007
--- GoogleBookmarksFeed.pm  Tue Sep 04 14:54:17 2007
***************
*** 22,29 ****
              $args->{entry}->body($orig_body);
              $context->log(info => "Parsing Google Bookmarks title " . $args->{entry}->permalink);
          }
!         if (my @orig_tags = @{$args->{orig_entry}->{entry}->{$ns}->{bkmk_label}}) {
!             $args->{entry}->tags(@orig_tags);
          }
      }
  }
--- 22,33 ----
              $args->{entry}->body($orig_body);
              $context->log(info => "Parsing Google Bookmarks title " . $args->{entry}->permalink);
          }
!         if (my $orig_tags = $args->{orig_entry}->{entry}->{$ns}->{bkmk_label}) {
!           if (ref($orig_tags) eq "ARRAY") {
!               $args->{entry}->tags($orig_tags);
!           } else {
!               $args->{entry}->tags([$orig_tags]);
!           }
          }
      }
  }
Posted at by




こんな夜中に何やってんだか...
お腹が空いてきたので、モスバーガーのホームページからメニューをスクレイピングしてみる。
別に買いに行く訳じゃないけど...

思った以上に苦戦。苦戦の理由は「HTMLにIDやCLASSが殆んど振られておらず、XPathで抽出出来るパタンがない」こと。しょうがないのでまた無茶ぶりを発揮して、ノード階層をパタンとして使い、最小マッチのノードから欲しいノードへ上昇するというドロ臭いXPathを書いた。
パタンは、td要素を2つ持つtr要素で、かつそのtd要素内にはhref属性に"/menu/"という文字列を含んだa要素、しかもそのa要素は"pdf"という文字列を含んでいない。
結果、CSSセレクタは全く使わなかった(使えなかった?)。これじゃ、Web::Scraperのスライド資料の悪い例のままだ...
ま、取れたので良しとしよう。


mosburger-scraper.pl #!/usr/local/bin/perl

use warnings;
use strict;
use Web::Scraper;
use YAML;
use URI;

my $uri = URI->new("http://www.mos.co.jp/menu/index.html");
my $mosburger = scraper {
    process '//tr[count(td)=2]/td/a[contains(@href,"/menu/") and not(contains(@href,".pdf"))]/img/../../..',
        'menus[]' => scraper {
            process '/tr/td[1]/a',     url => sub {URI->new_abs($_->attr('href'), $uri)->as_string;};
            process '/tr/td[1]/a/img', title => '@alt';
            process '/tr/td[1]/a/img', image => sub {URI->new_abs($_->attr('src'), $uri)->as_string;};
            process '/tr/td[2]/a',
                'perk' => scraper {
                    process '.', url => sub {URI->new_abs($_->attr('href'), $uri)->as_string;};
                    process 'img', title => '@alt';
                };
        };
    result 'menus';
};

my $burgers = $mosburger->scrape($uri);
warn Dump $burgers;

---
- image: http://www.mos.co.jp/menu/img/ph_hamburger18.jpg
  perk:
    title: サウザン野菜バーガー ¥300
    url: http://www.mos.co.jp/menu/hamburger/thousand/
  title: サウザン野菜バーガー
  url: http://www.mos.co.jp/menu/hamburger/thousand/
- image: http://www.mos.co.jp/menu/img/ph_hamburger19.jpg
  perk:
    title: [期間限定 10月中旬まで] シーザーサラダバーガー ¥300
    url: http://www.mos.co.jp/menu/hamburger/seasar/
  title: シーザーサラダバーガー
  url: http://www.mos.co.jp/menu/hamburger/seasar/
...
補足情報(場合によってはセットメニュー)も一緒に取得出来ます。

あー。はらへった。
Posted at by




twitterのAPIでfriendsが100件しか取れなくなって久しいですが...
WWW::MechanizeとXPathでtwitterの全friendsを取得するサンプル作ってみました。 あまりやり過ぎると、オフィシャル側に怒られそうな気もしますが...
後の使い方は、適当で... #!/usr/local/bin/perl

use warnings;
use strict;
use LWP::Simple;
use XML::Simple;
use WWW::Mechanize;
use HTML::TreeBuilder::XPath;
use HTML::Selector::XPath qw(selector_to_xpath);
use Data::Dumper;

my $username = 'your_username';
my $password = 'your_password';

my $m = WWW::Mechanize->new(timeout => 10);
$m->get('http://twitter.com/login');
$m->submit_form(
    form_number => 1,
    fields    => {
        username_or_email  => $username,
        password           => $password,
    },
    button    => 'commit',
);

my $xpath = selector_to_xpath('tr.vcard');
my @friends;

my $num_page = 1;
while (1) {
    my $res = $m->get("http://twitter.com/friends/?page=$num_page");
    my $encoding = $res->header('Content-Encoding');
    my $content = $res->content;
    $content = Compress::Zlib::memGunzip($content) if $encoding =~ /gzip/i;
    $content = Compress::Zlib::uncompress($content) if $encoding =~ /deflate/i;

    my $tree = HTML::TreeBuilder::XPath->new;
    $tree->parse($content);
    $tree->eof;
    my @nodes = $tree->findnodes($xpath);
    for my $tr (@nodes) {
        push(@friends, {
                nick => $tr->findnodes('td/strong/a')->[0]->as_text,
                image => $tr->findvalue('td[@class="thumb"]//img/@src')->as_string,
                name => $tr->findvalue('td[@class="thumb"]//img/@alt')->as_string,
                description => $tr->findvalue('td/strong/a/@title')->as_string,
                url => $tr->findvalue('td[@class="thumb"]/a/@href')->as_string,
            });
    }
    $tree->delete;
    @nodes or last;
    $num_page++;
}

print Dumper @friends;

最近遊んでる物、ほとんどmiyagawa氏のものばっかだな...
Posted at by




via twitter/plaggerのポスト

FrontPage - habu Wiki @ SF.jp

habuは、プラグインを組み合わせることでRSSを加工して再配信するためのソフトウェアです。簡単に言っちゃえば,Plaggerもどきです。

plaggerも好きだし、pythonも好きなら試さない訳には行きません。インストールはマイコミジャーナルが詳しいかと思います。私は、いきなりsvn/trunk取得して必要な物はeasy_installしました。

まぁ題名の件で言えば、iTunesで聞いている曲をTwitterにPostするPythonのスクリプト ? TRIVIAL TECHNOLOGIES 2.0を使えば動きそうですが、とりあえず...

habuではpluginはクラスで定義され、実行時にexecuteというメソッドが呼び出されます。気をつけなければならないのがexecuteはperl版と異なりentry単位にhookされるのではなく、全てのcontentsを持って呼び出されます。
※開発中なのかbaseとなるpluginクラスといった物はありません。
またhabuにはまだtemplateが実装されていません。ですのでポスト形式は現状プラグイン本体に記述する事になります。(いずれ改良されるかもしれません)
あと、これはいたし方ないですがsite-packageにあるxxx.pyを使うpublisher/xxx.pyでimport xxx出来ません。
今回のtwitterポストプラグインも「twitterPost.py」という名前にしてあります。
webutilsクラスやlogクラス等で、おおよそperl版と同じような事が出来ます。perlよりもpythonの方が可読性がありますし、pytnonにも負けない程のライブラリ郡もありますから、色んな事が出来るかと思います。Djangoなんかと組み合わせても面白そうです。
あと、面白いなと思ったのがコマンドライン用インタフェース「runhabu.py」に"--download-module"というオプションがあり、svnサーバからダウンロード出来る仕組みがあるようです。こちらは追々試します。
pythonが好きでplaggerも触りたい人は、こちらから初めてみてみるのも良いかもしれませんね。
で、最後に適当に作ったプラグインがコレ
habu/publisher/twitterPost.py
# -*- coding: utf-8 -*-
from twitter import Api
import habu.log as log

class TwitterPublisher(object):
  def __init__(self, config, environ):
    self.username = config.get("username", "twitter username")
    self.password = config.get("password", "twitter password")

  def execute(self, content):
    try:
      api = Api(self.username, self.password)
      for entry in content["entries"]:
        message = entry["title"] + ":"
        if len(entry["summary"]):
          message += entry["summary"]
        else:
          message += entry["description"]
        message += " " + entry["link"]
        if len(message) > 159:
          message = message[0: 159] + "..."
        api.PostUpdate(message)
    except Exception, e:
      log.error()
    else:
      log.info("twitterPost : commit")

def create(config, environ):
  return TwitterPublisher(config, environ)
あと、用意するYAMLはこんな感じ
global:
  timezone: Asia/Tokyo
  log: stdout

pipeline:
  rss_fetcher:
    - module: subscription.config
      config:
        feed:
          - http://b.hatena.ne.jp/[hatena user]/rss
    - module: filter.join
    - module: filter.sort
      config:
        reverse: True
    - module: publisher.twitterPost
      config:
        username: [twitter username]
        password: [twitter password]
deps相当、Crypt相当の物はまだ実装されていませんのでご利用は計画的に。
Posted at by




昨日書いた「pythonで動作するPlagger「habu」でtwitterにポストするプラグイン書いた」を作者の方がご覧になられ、野良プラグイン置き場のcommit権限を頂きました。
さっそく、昨日の「twitterPost.py」もcommitさせて頂きました。
で、今日はperl版のPublish::Gmailを移植。
おおよそPublish::Gmailを取り込めてますが、viaをサポート出来ていません。
ソースは適当に(いつもながら)こんな感じ...

# -*- coding: utf-8 -*-
__author__ = 'mattn.jp@gmail.com'
__version__ = '0.1'

import smtplib
import urllib
import urlparse
import re
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
from email.MIMEImage import MIMEImage
from email.Header import Header
from email.Utils import formatdate
from HTMLParser import HTMLParser
import habu.log as log

class _EnclosureParser(HTMLParser):
  def __init__(self):
    HTMLParser.__init__(self)
    self.base = ""
    self.files = []

  def set_base(self, url):
    if url.endswith("/"):
      self.base = url + "index.html"
    else:
      self.base = url

  def handle_starttag(self, tag, attrs):
    if tag.lower() == "img":
      for attr in attrs:
        if attr[0] == "src":
          self.files.append(urlparse.urljoin(self.base, attr[1]))
          break

class MailPublisher(object):
  def __init__(self, config, environ):
    self.mailto = config.get("mailto", "mail to")
    self.mailfrom = config.get("mailfrom", "mail from")
    self.mailroute = config.get("mailroute", "mail route")
    self.encoding = config.get("encoding", "utf-8")

  def execute(self, content):
    try:
      if self.mailroute.has_key("host"):
        s = smtplib.SMTP(self.mailroute["host"])
      else:
        s = smtplib.SMTP()
      for entry in content["entries"]:
        html = """<html>
<body>
""" + entry["description"] + """
</body>
</html>"""
        msg = MIMEMultipart()
        msg['Subject'] = Header(entry["title"], self.encoding)
        msg['From'] = self.mailfrom
        msg['To'] = self.mailto
        msg['Date'] = formatdate()
        msg['X-Mailer'] = "pyhabu::mailPost %s" % __version__
        related = MIMEMultipart('related')
        alt = MIMEMultipart('alternative')
        related.attach(alt)
        ep = _EnclosureParser()
        try:
          ep.set_base(entry["link"])
          ep.feed(html)
        except Exception, e:
          pass
        finally:
          ep.close()
        cnt = 1
        for filename in ep.files:
          fp = urllib.urlopen(filename)
          headers = fp.info()
          if headers.has_key("content-type"):
            mimetype = headers["content-type"].replace("image/", "")
            name = "img%d.%s" % (cnt, mimetype)
            img = MIMEImage(fp.read(), mimetype, name=name)
            img['Content-ID'] = '<%s>' % name
            related.attach(img)
            html = re.compile(r"(<img.*>)").sub(
              lambda x:x.group(1).replace(filename, name), html)
            cnt += 1
        content = MIMEText(html.encode(self.encoding, "replace"), 'html', self.encoding)
        alt.attach(content)
        msg.attach(related)

        s.sendmail(self.mailfrom, self.mailto, msg.as_string())
        log.info("mailPost : commit")
    except Exception, e:
      log.error()
    finally:
      s.close()

def create(config, environ):
  return MailPublisher(config, environ)

※おっ!enclosureもサポートしてんじゃん...
で、用意するYAMLはこんな感じ...
global:
  timezone: Asia/Tokyo
  log: stdout

pipeline:
  rss_fetcher:
    - module: subscription.config
      config:
        feed:
          - http://www.example.com/index.rss
    - module: filter.join
    - module: filter.sort
      config:
        reverse: True
    - module: publisher.mailPost
      config:
        mailto: yourname@gmail.com
        mailfrom: yourname@gmail.com
        mailroute:
          host: mail.example.com
        #encoding: iso-2022-jp

結論:pythonもperlも楽しい。
Posted at by




Powered by Vim 会社でちらりと覗いたデスクトップにvimを見つけました。少しずつですが何故か最近vimユーザが増えている気がしています。
vimは開発の場で見かけるとカッコよく映りますね。vimユーザでない方から見ると「何だか分かんない内にソースコードが出来上がって行く」と神懸り的な物に見えるようです。
今日はこれまでもvimを使って来たけどなかなか上達しない方、またはこれからvimを覚えようとされているvimビギナーの方に送る、vimの使い方を上達させる5つのオキテ。

キーボードは見るな

まずは基本。要はブラインドタッチしろという意味ですが、なぜvimのオキテなのかというと...
vimは頭で考えるテキストエディタです。ブラインドタッチが出来ていないと、vimに命令を与える際に思考を止めてしまい学習出来なくなります。以下のオキテをブラインドタッチよりも先に学習してしまう事は、ある意味無駄な学習ともなり得ます。

マウスを触るな

vimに限らずU*IX系のテキストエディタでは、マウスを使わず全ての編集が出来る様になっています。
マウスを探す事で思考を止めてしまい、せっかく思いついたかもしれない素晴らしいアイデアを自ら消し去ってしまうかも知れません。
海外の方がvimを触っている動画を幾らか見たことがありますが、彼らは常に喋るようにタイプしコタツの上にあるテレビのチャンネルを探す様な感覚でテキストを検索しています。
vimを上達させる為には見たかったテレビ番組の内容を喋りながらもチャンネルを変えられる...くらいの当たり前さをテキスト編集スキルとして身に着けなければなりません。

脳内で英文を作れ

vimは基本的にカウント、モーション、オブジェクトという3つの要素を用いてvimに命令することで編集を行います。
例えば行を3行消すのに、SHIFTキーを押しながら3回矢印キーを押してDELキーを押す...という通常のテキストエディタとは違い、「消す 3 下」と言った少し英語に似た脳内文章を自分で表現しvimに命令しなくてはなりません。この脳内文章を如何に効率良く作れるかがvim上達への近道となるのです。
現在のカーソル位置続く単語3つを消して別の物に置き換えたい場合、vim使いならば脳内で「変える 3 単語」という文章が出来上がり「c3w」とタイプされるのです。

短いショートカットキーを知れ

良く出来たテキストエディタでは、良く登場する入力方法を予め決められたキーボードショートカットとして割り当てられています。
例えばプログラムのソースコードを編集中に、カーソルがある次の行から新たな行として入力を開始したい場合、通常のテキストエディタならば<End>を押して行末まで移動し<Enter>を押して入力を開始しなければなりませんが、vimだとノーマルモードから「o」だけ。
この辺りの気配りさが如何にもコード書き専用エディタと言われる由縁なのかもしれません。

人のテクニックを盗め

これはテキストエディタに限らず、何にでも言える話ですがオープンソース界隈では自らの設定ファイルまでも公開したがる人がワンサカいます。
まぁ私もその一人ですが...
vimは一人で「:help」から勉強するには多すぎる程のノウハウが詰まったテキストエディタです。自分で思い付かなかった素晴らしいテクニックは、ありがたくら盗んでしまいましょう。

vimはどれ程使い込んでも満足する事が出来ないテキストエディタです。
どんなに上達しても時に人の設定ファイルや編集方法に驚かされたりします。
頭で考えるテキストエディタだからこそ、人それぞれの編集方法が生まれるのでしょうね。
vimを使い始めてみようと思われている方、このどっぷりと深い世界にのめり込んで見ませんか?
Posted at by




色んな人のvimrcを見ていて意外とmapされていないのがこの技。
元はと言うとKoRoNさんから教えて貰った技で、先日公開した私のvimrcにも入っています。
" expand path
cmap <c-x> <c-r>=expand('%:p:h')<cr>/
" expand file (not ext)
cmap <c-z> <c-r>=expand('%:p:r')<cr>
これを使って例えば # vi /path/to/file/file.txt
type some text...
:pwd
/path/to/file
な状態で、ファイルのある場所にカレントディレクトリを移したいならば :cd <C-X>  <= Ctrl押しながらX とコマンドモードで<C-X>をタイプすれば :cd /path/to/file/ と補完されます。あとはENTERで移動。
結構便利だったりします。またコマンドモードで<C-Z>とすれば編集中のファイルへの絶対パスに補完されますので、例えば編集中のHTMLファイルをFirefoxで確認したいならば :!firefox-remote <C-Z>ENTER で簡単に確認出来ます。書きはじめたjavascriptを試すにはいいですね。
※Windowsの場合はvimrcで「set shellslash」してあり、かつfirefoxまでのパスが通っている状態ならば「:!firefox <C-Z>」で行けるかもしれません。(確認してません)

応用として、例えばまだインストールしていないGreaseMonkeyスクリプトをvimで閲覧/編集中に「:!firefox <C-Z>」すれば簡単にインストール出来てしまいます。UN*XユーザでいちいちファイラからFirefoxにjsファイルをドロップ...なんてメンドクサイですよね。特にcoderepos等のCVS/SVNリポジトリでグリースモンキーを見つけ中身をvimで確認してすぐさまインストールしたい!なんて時には大活躍です。
おそらく他にも使い道は沢山あるかと思います。
皆さんもvimrcに加えてみては如何でしょうか。
Posted at by




意外と知られていないんですね。ビジュアル選択って
vimで選択範囲を置換
うわーん。これやりかたかったんだよー!知らなかったよー!
Powered by Vim 同じネタを説明しても面白くないので、今日はビジュアル選択後に行うアクションについて...
「'<'>」の後には、「s(substitute)」だけでなく「g(global)」や「v(vglobal)」を書く事もでき、行単位でのビジュアル選択(正式にはlinewise-visual選択)を行った行に対して絞込みを行い、その上で置換を行う事も出来ます。
例えば 問題
※以下の阿藤について間違っている物に×を入れよ
(  ) 俺は阿藤会だ
(  ) 俺こそ阿藤下位だ
(  ) 僕も阿藤回だ
(  ) リッチに阿藤買いだ
(  ) 実は私の従兄弟が阿藤快だ
(  ) 叔父が阿藤飼いだ
(  ) 海で阿藤貝を拾った
(  ) お前、阿藤甲斐性あるな
こんなテキストならば、「(  )」が付いている行を選択して
:'<,'>v/阿藤快/s/(  )/(×)/g
でおしまい。
※「:」を押した時点で「'<,'>」は補完されます。
意味は、ビジュアル選択している部分から「v」で「阿藤快」の含まれない行を抜き出し、その結果に対して「s」で「(  )」を「(×)」に置換するという物です。

また、例えばテキストファイルに書かれた以下の様なスケジュール一覧があったとします。
予定表
1. 09:00 出社
2. 10:00 会議(午前の部)
3. 12:00 昼休憩
4. 13:00 会議(午後の部)
     ここで仕様を煮詰める
5. 16:00 内部ミーティング
6. 16:30 資料作成
7. 17:30 客先にて打ち合わせ
昼休憩の後に項目番号4として「13:30 来客予定」を入れたくなったらどうしますか?
4から7までを一つずつ足して行きますか?
vimなら4で始まる行から7で始まる行までを選択して
:'<,'>g/^\d/exec "normal 0\<c-a>" とすれば4以降が1個ずつずれるので、5の上から4で書き始めればよいのです。
vimではノーマルモード時、数値の上で<c-a>を押すと数値がインクリメントされる(<c-x>でデクリメント)という機能があるので、これを利用して先頭行の数字に対して<c-a>キーを送信しています。

さらに会議(午後の部)の開始が1時間が遅れるとなった場合、「13:00」を含む行から「17:30」を含む行まで選択して
:'<,'>g/^\d/s/\(\d\d\):/\=printf("%02d:", submatch(1)+1)/ でおしまい。
先頭が数字で始まる行に対して「s」で「数値+数値+":"」を検索し値に1足し、printfでゼロ付き文字にして置換しています。
※printf()はvim7でしか動きません。
少し工夫すれば30分足して60分になった物は1時間繰り上げる...なんて事も出来るでしょうね。

vimってパズルみたいで面白いですよね。方法はこれだけでなく、人によっては私よりも手数の少ない方法を使われる方もいます。
凝ると色んな事が出来ますので、皆さん凄いの見つけたら教えて下さいね。
Posted at by




今まで誰にも見せた事無かったですが...
出しちゃいます。

/dotfiles/vim/mattn-vimrc - CodeRepos::Share - Trac
/dotfiles/vim/mattn-gvimrc - CodeRepos::Share - Trac
正直デカイです。

もう随分昔からあるvimrcなので使ってない機能もあれば、忘れてしまったノウハウもいっぱい...
あまり参考にならないかもしれませんが、どうぞ。

mattn the vimmer!

追記
マルチプラットフォーム用です。
Posted at by




http://www.hsbt.org/diary/20071022.html#p02
だれか indent たのむ - HsbtDiary (2007-10-22)
頼まれた!
って訳ではないですが...

vimは数多くのファイルフォーマットに対応しており、通常扱うファイルであれば、ほぼ全てサポートしているんじゃないかという位、多くのファイルフォーマットに対応出来ています。
どれくらい凄いかと言うと...
gvimの「シンタックス(S)」メニューを展開すると画面がいっぱいになるくらいです。
vim_ff

さて、VBでのインデントですが、例えばVB6の以下の様なソース Private Sub Class_Initialize()
Debug.Print "あめんぼ赤いな"
        If Me.strValue = "" Then
    Debug.Print "エラー!"
End If
End Sub
Private Sub Class_Terminate()
Debug.Print "あいうえお"
End Sub
こんなに崩れたファイルでも、vimなら「gg=G」でおしまい。 Private Sub Class_Initialize()
    Debug.Print "あめんぼ赤いな"
    If Me.strValue = "" Then
        Debug.Print "エラー!"
    End If
End Sub
Private Sub Class_Terminate()
    Debug.Print "あいうえお"
End Sub
例えば、以下の様なJavaのソースファイルでも public class Test {
            public Test() {
                System.out.println("コンストラクタ");
            }
public void doPhpSpot() {
//ここでは何もしない
}
}
上のコマンドで public class Test {
    public Test() {
        System.out.println("コンストラクタ");
    }
    public void doPhpSpot() {
        //ここでは何もしない
    }
}
あらキレイ!
例えばファイル全体でなく、一部分だけインデントを修正したい場合にはビジュアル選択(V押下後にjk移動)した後で「=」とすれば、部分的にインデントし直されます。

どうですか!vim使いたくなりませんか!

mattn often say sales hype.
Posted at by




Googleが提供している開発用tagツールGoogle GTags(gtags)をWindowsで動かす手順です。
以下、パッチを上げてますが、接続タイムアウトのalarmを無効化しています。必要だと思われる人は、SIGALRMを別の方法で実装してください。
まず、サイトからsvnで最新ソースを取得します。
C:\TEMP> svn checkout http://google-gtags.googlecode.com/svn/trunk/ google-gtags 次に、以下のパッチを当てます。
Index: regexp.h
===================================================================
--- regexp.h    (revision 57)
+++ regexp.h    (working copy)
@@ -4,7 +4,9 @@
 #ifndef TOOLS_TAGS_REGEXP_H__
 #define TOOLS_TAGS_REGEXP_H__
 
+extern "C" {
 #include <regex.h>
+}
 #include "tagsutil.h"
 
 class RegExp {
Index: configure
===================================================================
--- configure   (revision 57)
+++ configure   (working copy)
@@ -10,7 +10,7 @@
 if [ ! -e "scons/scons.py" ]; then
     pushd scons > /dev/null
     echo "Unpacking scons..."
-    tar xzvf scons-local.tar.gz > /dev/null
+    gzip -dc scons-local.tar.gz | tar xv > /dev/null
     if [[ "$?" == 0 ]]; then
    echo "Done"
     else
Index: gtags.cc
===================================================================
--- gtags.cc    (revision 57)
+++ gtags.cc    (working copy)
@@ -49,6 +49,10 @@
 #include "tagsoptionparser.h"
 #include "tagsrequesthandler.h"
 
+#ifdef WIN32
+# include <winsock2.h>
+#endif
+
 DEFINE_STRING(tags_file, "", "The file containing the tags information.");
 
 
@@ -78,6 +82,11 @@
     return -1;
   }
 
+#ifdef _WIN32
+  WSAData wsadata;
+  WSAStartup(MAKEWORD(2,0), &wsadata);
+#endif
+
   logger = new StdErrLogger();
 
   tags_request_handler = new TagsRequestHandler(GET_FLAG(tags_file),
@@ -90,4 +99,9 @@
 
   delete tags_request_handler;
   delete logger;
+
+
+#ifdef _WIN32
+  WSACleanup();
+#endif
 }
Index: gtags.py
===================================================================
--- gtags.py    (revision 57)
+++ gtags.py    (working copy)
@@ -175,11 +175,11 @@
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      address = socket.getaddrinfo(host, port, socket.AF_INET,
                                socket.SOCK_STREAM)
-     signal.signal(signal.SIGALRM, alarm_handler)
-     signal.alarm(CONNECT_TIMEOUT)
+     #signal.signal(signal.SIGALRM, alarm_handler)
+     #signal.alarm(CONNECT_TIMEOUT)
      s.connect(address[0][4])
-     signal.alarm(0)
-     signal.alarm(DATA_TIMEOUT)
+     #signal.alarm(0)
+     #signal.alarm(DATA_TIMEOUT)
 
      # need \r\n to match telnet protocol
      s.sendall(command + '\r\n')
@@ -191,7 +191,7 @@
      while data:
        buf.write(data)
        data = s.recv(1024)
-     signal.alarm(0)
+     #signal.alarm(0)
      return buf.getvalue()
 
 # Instance of connection_manager that forwards client requests to gtags server
Index: tags_logger.h
===================================================================
--- tags_logger.h   (revision 57)
+++ tags_logger.h   (working copy)
@@ -67,6 +67,7 @@
   }
 };
 
+#undef ERROR
 const int INFO = 0, WARNING = 1, ERROR = 2, FATAL = 3, NUM_SEVERITIES = 4;
 
 // uncomment out the standard google logger
Index: socket_server.cc
===================================================================
--- socket_server.cc    (revision 57)
+++ socket_server.cc    (working copy)
@@ -18,10 +18,14 @@
 
 #include <assert.h>
 #include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <unistd.h>
+#ifdef WIN32
+# include <winsock2.h>
+#else
+# include <sys/socket.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
+# include <unistd.h>
+#endif
 #include <string>
 
 #include "tagsprofiler.h"
@@ -29,6 +33,12 @@
 #include "tagsrequesthandler.h"
 #include "socket_server.h"
 
+#ifdef WIN32
+typedef int socklen_t;
+#define write(x, y, z) send(x, y, z, 0)
+#define close(x) closesocket(x)
+#endif
+
 extern GtagsLogger* logger;
 
 DEFINE_INT32(tags_port, 2222, "port to tags server");
次にgoogle-gtagsのソースルートにregex for win32を解凍します。これで、gnu_regex_distというフォルダが出来ます。
zsh for win32等をお持ちの方ならば、そのまま sh configure
お持ちでない方でもconfigureの中身を見ると大体検討が付きます。
Makefile.w32は以下の通り。mingw32-makeでビルドします。
all: gtags.exe

gtags.exe : filename.cc gtags.cc sexpression.cc strutil.cc symboltable.cc tagsoptionparser.cc tagsprofiler.cc tagsrequesthandler.cc tagstable.cc socket_server.cc gnu_regex_dist/regex.c
    gcc -DHAVE_STRING_H -c -I. gnu_regex_dist/regex.c
    gcc -I. -Ignu_regex_dist -o gtags.exe filename.cc gtags.cc sexpression.cc strutil.cc symboltable.cc tagsoptionparser.cc tagsprofiler.cc tagsrequesthandler.cc tagstable.cc socket_server.cc regex.o -lstdc++ -lws2_32
あとはspiritlooseのはてなダイアリー - [Vim]Google Tags(GTags)を試してみた(with Vim)を参考に lang_call_to_server = {
  "c++" : { "definition" : [("localhost", 2222)],
            "callgraph" : [] },
  "java" : { "definition" : [],
             "callgraph" : [] },
  "python" : { "definition" : [],
               "callgraph" : [] } }
等と設定して、vimrcに exec "set runtimepath+=".escape(globpath(&runtimepath, 'gtags'), ' ')
nmap <C-]> :call Gtag(expand('<cword>'))<CR>
を追加します。
これで設定はOKです。次に以下の手順でソースツリーでtagsファイルを生成します。
python c:/temp/google-gtags/gentags.py --etags=c:/emacs/bin/etags.exe --rtags=c:/temp/google-gtags/rtags.py --etags_to_tags=c:/temp/google-gtags/etags_to_tags.py ここではetagsとしてemacsに含まれるバイナリを使用しましたが、ctags.exeをetags.exeにリネームしても同様に使えます。
これで「cpp.tags.gz」というファイルが生成されますので、あとはサーバを起動します。
c:/temp/gtags.exe --tags_file ./cpp.tags.gz --tags_port 2222 --gunzip 起動したらvim(gvim)を起動して、タグジャンプしたい部分でC-]します。
spiritlooseさんの言うように確かに速いですね。共同開発等では便利かもしれませんね。

オフトピですが、このライブラリに含まれるSconsというビルドツールについて今度調べてみようかと思います。
Posted at by




vimを使っていて開いているバッファ全てからあるキーワードを検索したい場合、「vimgrep」コマンドを使っています。開いているファイルの拡張子を指定して :vimgrep /sometext/ *.c *.h
と実行したり、実際開いているファイル名を羅列したりする事があります。検索結果の一覧もクイックフィックス「clist」で確認出来ます。
でもこれだと保存していないと結果に現れませんし、幾ら拡張子で絞っても余計なファイルがマッチしてしまう可能性があります。

今日のtipsはvim-dev MLに流れたスレッドからご紹介

「vimgrep」を実行するとクイックフィックスが作成されますが、このクイックフィックスに検索結果を追加する「vimgrepadd」というコマンドがあります。
これを全てのバッファに対して実行するように「bufdo」コマンドを絡めます。
:bufdo vimgrepadd /sometext/ %
なるほどね...

ちなみにスレッドの中で「クイックフィックスをクリアするには単にcexpr ""でいける」とBram Moolenaar氏が書いてます。

見えてるバッファだけで実行する場合は「bufdo」の代わりに「windo」が使えますね。
Posted at by




vimにはexplore.vimというスクリプト(現在はnetrw.vimに統合)が付属しており # vim /usr/include 等と実行すると、vimがファイラとして起動します。同様にコマンドラインから :e /usr/include と実行しても同じ結果になります。コマンド単体としてもExploreとして起動出来ます。
このExplore実は結構よく出来ていて、以前ご紹介した「男は黙ってvimでリモート編集」の応用として :e ftp://ftp.vim.org/pub/vim/
Enter username: anonymous
Enter Password: *********
でフォルダ閲覧出来ます。(*********はanonymous) " ============================================================================
" Netrw Directory Listing                                        (netrw v109)
"   ftp://ftp.vim.org/pub/vim/
"   Sorted by      name
"   Sort sequence: [\/]$,\.h$,\.c$,\.cpp$,\.[a-np-z]$,*,\.info$,\.swp$,\.o$\.obj
"   Quick Help: <F1>:help  -:go up dir  D:delete  R:rename  s:sort-by  x:exec
" ============================================================================
../
./
MIRRORS
README
amiga
atari
be
beanie.gif
doc
extra
faq.html
farsi
green_ball.gif
index.html
ftp://ftp.vim.org/pub/vim/ [RO]                                       1,1     2%

また、Exploreではファイル名にカーソルを合わせて「x」をタイプすると拡張子に合わせてアプリケーションが起動します。
例えばWindowsでファイル名が「勤務表.xls」であればExcelが起動します。

この「x」で外部アプリケーションが起動する機能、現状はWindows、GNOME、KDEをサポートしています。
ちょっとソースを見たところ、netrw.vimには元となったexplore.vimに昔々に私が入れ込んだ「explFileHandler」が別名「netrwFileHandlers」として取り込まれてました。
ただ、「netrwFileHandlers」は私が元々想定していた単一のユーザ関数ではなく「netrwFileHandlers#Invoke」という関数でファイル種別毎に分別され、ファイル種別毎のスクリプト関数が実装されていました。
これをグローバルで宣言すれば自分独自の設定も出来るという仕組みです。 let g:netrw_browsex_viewer='-'
" エディタであるvimから秀丸起動して、何やってんだか...
function! NFH_txt(file)
    " netrwFileHandlers.vimの不具合回避?
    let f = substitute(a:file, '^\([A-Z]\)COLON', '\1:', '')

    exe "silent !start c:/progra~1/hidemaru/hidemaru.exe \"".f."\""
    return 1
endfunction

こんな感じのユーザ関数を作れば例えば.plや.shでperlやbashを起動したりする事も出来ます。
コード内にある「netrw_browsex_viewer」ですが、"-"に設定すると上記のようなユーザ/スクリプト関数を呼び出す機能として動作しますが、実行可能なコマンドを設定するとそのまま起動してくれるようにもなっています。
これを使用すれば、現状Windows、GNOME、KDEしかサポートしていないnetrw.vimでも、Mac OS Xに対応する事が出来ます。
私はMac OS Xを持っていないので確認出来ませんが、MacWiki - OSXの固有コマンドを見ると、Mac OS Xではコマンドラインからファイルを開く「open」コマンドがあるらしいので let g:netrw_browsex_viewer = 'open'
とvimrcに設定しておけば、Exploreから「x」をタイプする事でファイル種別に応じたアプリケーションが起動出来るかと思います。
※どなたか動作報告頂ければ、オフィシャルにマージして貰えるかもしれません。

その他、netrw.vimが使用するftp/sshのコマンドライン等の設定は :NetrwSettings
とすれば、設定画面が表示されますので、色々カスタマイズして見ると面白いかもしれませんね。

mattn the vim explorer
Posted at by




最近Plaggerにハマってるからって、やる事メチャメチャやな...

Plaggerのフィードをvimの「--remote-send」を使って転送するPlaggerプラグインを作りました。
vimには、リモートサーバと言う機能があり以下のオプション/コマンドで別に起動しているvimへコマンド/式/ファイルを送信する事が出来ます。

--remote [+{cmd}] {file} ...
別のサーバへファイルを送信します。
+{cmd}により開く際にコマンドを併用出来ます。
--remote-silent [+{cmd}] {file} ...
--remote と同等ですが、エラー等が発生してもメッセージ出力されません。
--remote-wait [+{cmd}] {file} ...
--remote と同等ですが、送信したvimが終了するまで待機します。
vimで編集した結果をどこかに送るシェルスクリプト等では使えるかもしれません。
--remote-wait-silent [+{cmd}] {file} ...
--remote-wait と同等ですが、エラー等が発生してもメッセージ出力されません。
--remote-tab [+{cmd}] {file} ...
--remote と似ていますが、指定されたファイルを全て別タブで開きます。
--remote-tab-silent [+{cmd}] {file} ...
--remote-tab と同等ですが、エラー等が発生してもメッセージ出力されません。
--remote-tab-wait [+{cmd}] {file} ...
--remote-tab と同等ですが、送信したvimが終了するまで待機します。
--remote-tab-wait-silent [+{cmd}] {file} ...
--remote-tab-wait と同等ですが、エラー等が発生してもメッセージ出力されません。
--servername {name}
リモートサーバを名称で指定します。リモートサーバ一覧を取得するには --serverlist を使用します。
--remote-send {keys}
リモートサーバにキーを送ります。基本的にnormalコマンドで与える引数と同等です。
--remote-expr {expr}
リモートサーバに式を送り、その結果を出力します。
--serverlist
リモートサーバ一覧を出力します。

その他、スクリプトから使える
remote_expr({server}, {string} [, {idvar}])
{server}に対して{string}という式を評価して貰います。
:echo remote_expr("gvim", "2+2") と書くと4が表示されます。
remote_foreground({server})
{server}をフォアグラウンドにします。 remote_expr({server}, "foreground()") と同等の機能ですね。
等がありますので、vimを起動しなくても色々な事が出来ます。

今日は、はてなブックマークから「vim」タグが付いているエントリをvimに転送するPlaggerプラグインを作りました。
確認はWindowsでしかしてませんが、X Windowが動いている環境や、Mac OS X等でも動くかと思います。
※あと、フィードタイトルにエスケープ文字や「<ESC>」等といったvimのキー識別ぽい物があると動かない可能性があります。 package Plagger::Plugin::Publish::Vim;
use strict;
use base qw( Plagger::Plugin );

our $VERSION = '0.01';

use Encode;

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'plugin.init' => \&initialize,
        'publish.entry' => \&add_entry,
    );
}

sub initialize {
    my($self,$context) = @_;

    $self->{vim} = $self->conf->{vim};
    $self->{vim} = "vim" if (!$self->{vim});
    $self->{server} = $self->conf->{server};
    if (!$self->{server}) {
        open(IN, "vim --serverlist|");
        $self->{server} = <IN>;
        close(IN);
        chomp $self->{server};
    }
    open(IN, sprintf("%s --servername %s --remote-expr \"&encoding\"|",
        $self->{vim}, $self->{server}));
    $self->{encoding} = <IN>;
    print $self->{encoding}."\n";
    close(IN);
    my $command = sprintf("%s --servername %s --remote-send \"<C-\\\><C-N>:new<CR>i\"",
        $self->{vim}, $self->{server});
    system($command);
}

sub add_entry {
    my($self, $context, $args) = @_;

    $context->log(info => $self->{server});
    my $command = sprintf("%s --servername %s --remote-send \"%s\n\t%s\n<C-W>\"",
        $self->{vim}, $self->{server},
        encode($self->{encoding} || 'utf8', $args->{entry}->{title}),
        encode($self->{encoding} || 'utf8', $args->{entry}->{link}),
    );
    system($command);
}

1;
で、YAMLはこんな感じ
global:
  assets_path: /home/user/plagger/assets
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://b.hatena.ne.jp/t/vim?mode=rss

  - module: Filter::BreakEntriesToFeeds
    config:
      use_entry_title: 1

  - module: Publish::Vim
    config:
      #vim: vim7
      #server: GVIM1
実行結果は
vim_plagger

またくだらんもん作ってしもた...
Posted at by




昨日の記事「Publish::Jaikuをでっちあげた」でご紹介したソースコードは、最終的にはCodeReposにcommitする事にしました。
で、その際CodeReposのトップページで読んだコミットルール
Commit messege rule
svn ci -m "lang/LanguageName/BigProjectName: Commit messege."
svn ci -m "lang/LanguageName/misc/ScriptName: Commit messege."
svn ci -m "dotfiles/SoftwareName/Username-Filename: Commit messege."
を守ってcommit時にファイル名を一覧しました。
で、悩んだのが複数のファイルをcommitする場合。
色々な人のcommit logを見てたら、皆さん lang/LanguageName/BigProjectName,
lang/LanguageName/misc/ScriptName,
dotfiles/SoftwareName/Username-Filename:
  Added.
といった書き方をされていました。
でもこれってU*NIXなら"pwd"して、コピれば簡単ですが、Windowsの場合は"¥"になったり、部分的にチェックアウトしている場合には"pwd"で取得出来るものはなかったりと不便だったりします。
で、なんか楽出来ないかなと思いまして今回はこのcommit対象ファイル一覧を"svn commit"時のコメントとして出力してくれるvimscriptをご紹介します。
※というか、さっき作りました。

仕組みとしては、"svn info"を実行すると出力される
URL : http://host/root/path/to/dir
Repository Root : http://host/root
という部分の2行の差、「path/to/dir」を取得し、svn commit実行時にエディタに表示されている
--This line, and those below, will be ignored--

A    docs
A    docs/file1.txt
A    docs/file2.txt
A    docs/file3.txt
のファイル名部分の先頭に上記フォルダ「path/to/dir」を付与し path/to/dir/docs,
path/to/dir/docs/file1.txt,
path/to/dir/docs/file2.txt,
path/to/dir/docs/file3.txt:
というコメントを生成します。
あとはこれをFileTypeがsvnの場合に動作するようにautocmdを作ればsvn commit時に
path/to/dir/docs,
path/to/dir/docs/file1.txt,
path/to/dir/docs/file2.txt,
path/to/dir/docs/file3.txt:
 
--This line, and those below, will be ignored--

A    docs
A    docs/file1.txt
A    docs/file2.txt
A    docs/file3.txt
という画面が現れます。カーソルも":"の次の行に移動しますので、そこからコメントを書くと事が出来ます。
ツールは横着から生まれる物ですね!

vimscriptのコードは以下の通り
"=============================================================================
" File: svn_file_comment.vim
" Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
" Last Change: Fri, 12 Oct 2007
" Version: 0.1
"-----------------------------------------------------------------------------
" when editing comment for 'svn commit',
"  it append svn comment like following
"
"   root/path/to/dir/docs,
"   root/path/to/dir/docs/file1.txt,
"   root/path/to/dir/docs/file2.txt,
"   root/path/to/dir/docs/file3.txt:
"   <= cursor
"   --This line, and those below, will be ignored--
"   A    docs
"   A    docs/file1.txt    
"   A    docs/file2.txt    
"   A    docs/file3.txt    
"-----------------------------------------------------------------------------

function! AppendCommitFiles()
  let lstart = search("^--", "n")
  let lend = line("$")
  if line(".") > 1 || lstart != 2
    return
  endif
  let oldlang=$LANG
  let $LANG="C"
  let lines=system("svn info")
  let $LANG=oldlang
  let url=substitute(lines, '.*\nURL: \([^\x0A]*\).*', '\1', '')
  let root=substitute(lines, '.*\nRepository Root: \([^\x0A]*\).*', '\1', '')
  if match(url, root) != 0
    return
  endif
  let basedir=substitute(strpart(url, strlen(root)), '^\/*', '', '')
  let lcur = lstart
  let lines = ""
  let mx = '^\s*[A-Z]\s\+\([^$]\+\)$'
  while lcur <= lend
    let line = getline(lcur)
    if line =~ mx
      let lines .= basedir."/".substitute(line, mx, '\1', '')."\<NL>"
    endif
    let lcur = lcur + 1
  endwhile
  let lines = substitute(lines, '\n.', ',&', 'g')
  let lines = substitute(lines, '\n$', ':&', '')
  call cursor(0)
  let value = getreg("a")
  let type = getregtype("a")
  call setreg("a", lines, "c")
  execute 'normal! "ap'
  call setreg("a", value, type)
  silent! /^$
endfunction
autocmd FileType svn call AppendCommitFiles()
例によって、このソースもCodeReposのコノ辺に置く予定です。

追記
ちょこっと修正
Posted at by




コマンドモードで :help! 結果↓ E478: 慌てないでください!
tipsというよりイースターエッグかな...
Posted at by




vimscriptの発祥から考えると、vimで扱えるオブジェクトはあくまで数値、文字列レベルの物でしかないと思われがちですが、Dictionaryとfunction()を使えば、それとなく継承ぽい事が出来ます。
まぁ、javascriptに近い言語仕様というのもありますからね。
以下のコードでは、簡単なクラスとオブジェクトを定義しています。
function! Class_Prototype() dict
  return self
endfunction

function! Class_Override(...) dict
  if a:0 == 0|throw "Invalid Parameter"|endif
  let class = copy(self)
  let class.__NAME__ = a:1
  if type(a:2) == type(class.New)
    let class.New = a:2
  else
    let class.New = self.New
  endif
  let class.Super = self
  return class
endfunction

function! Class_New(...) dict
  let instance = copy(self)
  call remove(instance, "New")
  call remove(instance, "Override")
  let instance.Super = self
  return instance
endfunction

function! Class_ToString() dict
    return self.__NAME__
endfunction

let Object = {
 \ "__NAME__" : "Object",
 \ "Prototype"function("Class_Prototype"),
 \ "Override"function("Class_Override"),
 \ "Super"{},
 \ "New"function("Class_New"),
 \ "ToString"function("Class_ToString")}
この状態で if exists("object")|unlet object|endif
let object = Object.New()
echo object.ToString() . ":..."
とすると Object:... と表示されます。

ここで function! Human_Sing() dict
  return self.perfix . "は" . self.name . "。" . self.title
endfunction
function! Human_New(...) dict
  let instance = copy(self)
  let instance.perfix = a:1
  let instance.name = a:2
  let instance.title = a:3
  let instance.Sing = function("Human_Sing")
  return instance
endfunction
let Human = Object.Override("Human", function("Human_New"))
とすると、Objectクラスを継承するHumanクラスを定義する事が出来ます。
さらにこの状態で if exists("human")|unlet human|endif
let human = Human.New("私", "人間", "一般人")
echo human.ToString() . ":" . human.Sing()
とすると Human:私は人間。一般人 と表示されます。
つまり、Newメソッドをオーバーライドし、Singメソッドを追加した事になります。ToStringメソッドはObjectクラスのメソッドとなります。
vimscriptはJavascriptのように、メンバを動的に生成出来ますので
function! Gian_Boxing(who) dict
  return a:who . "のくせに生意気だぞ!!!"
endfunction
let Gian = Human.Override("Gian", {})
let Gian.Boxing = function("Gian_Boxing")
if exists("gian")|unlet gian|endif
let gian = Gian.New("俺", "ジャイアン", "ガキ大将")
echo gian.ToString() . ":" . gian.Sing()
echo gian.Boxing("のび太")
とすると Gian:俺はジャイアン。ガキ大将
のび太のくせに生意気だぞ!!!
とジャイアンが生成出来ます。
意外とやれるもんですね。

ただ、せっかくローカルスコープ、スクリプトスコープ、グローバルスコープ等、名前空間は既にしっかり存在してるんだから、もう少し他のオブジェクト指向言語(JavaやJavaString、C#やVB.NET)のように既定メソッドとかデフォルトコンストラクタみたいな概念が欲しいなぁ...
あと関数名は先頭大文字強制ってのは痛い。
ま、その辺はまた今度...

mattn the vimscripter
Posted at by




vim_html 普段vimを使っていらっしゃる方は、ソースコードや/etcにある設定ファイル等をブログに書きたいと思う事が多いかと思います。
私のブログでもほぼ7割方ソースコードが含まれるブログ記事となっています。

ソースコードをブログにアップする際、やはりテキストエディタの様にシンタックスに色が付いた常態だと分かりやすいですよね。
世の中には色んな方法があるようです。

ただ、私としてはやはり静的ファイルでHTMLを出力したいし、なるべく多くのフォーマットで、かつマルチバイトに対応していてほしい。
私は普段テキストエディタとしてvimを使っていますが、vimのソースコードハイライト機能はタダモノではなく、かつ対応しているファイルフォーマットの数も :echo len(split(globpath(&rtp, "syntax/*.vim"),"\n"))
とするだけでも500個以上のファイルフォーマットに対応している事が分かります。
さらにvimのオフィシャルサイトに行けば世の中に存在するプログラミング言語のほぼ全てのsyntaxファイルが揃うでしょう。
vimにも、HTML出力の機能が無いわけではありません。「tohtml.vim」を使えば現在ハイライトされているバッファの中身をHTMLファイルにしてくれる機能があります。
#include <stdio.h>

int main(int argc, char* argv[]) {
    printf("Hello, World\n");
    return 0;
}
こんなファイルであれば :TOHtml
とする事で <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>C:/temp/helloworld.c.html</title>
<meta name="Generator" content="Vim/7.1">
</head>
<body bgcolor="#000000" text="#c0c0c0"><font face="monospace">
<font color="#ff6060">#include </font><font color="#ff40ff">&lt;stdio.h&gt;</font><br>
<br>
<font color="#00ff00">int</font>&nbsp;main(<font color="#00ff00">int</font>&nbsp;argc, <font color="#00ff00">char</font>* argv[]) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;printf(<font color="#ff40ff">&quot;Hello, World</font><font color="#008080">\n</font><font color="#ff40ff">&quot;</font>);<br>
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#00ffff">return</font>&nbsp;<font color="#ff40ff">0</font>;<br>
}<br>
</font></body>
</html>
こんなHTMLを出力してくれます。
ただ...<font>はいただけませんね。

最近のHTMLでは御法度です。HTML Validatorも許しません。HTMLに直接色を埋め込むなんて最近ではタブー化しつつあります。(このサイトには沢山ありますが...)

今回は、「tohtml.vim」から実行される「2html.vim」を使用してブログに最適なHTMLを出力出来るコツを説明して行きます。
「2html.vim」は基本動作を変えられるように複数のオプションを持っています。以下そのオプションについて説明します。

use_xhtml

出力するHTMLをXHTMLに強制します。たとえば<br>は<br />と出力されます。
また<DOCTYPE>も合わせて変更されます。

html_number_lines

HTMLを出力する際に行番号を出力してくれます。ただし同一要素内に出力されますのでマウスで選択すると行番号まで選択されてしまいます。

html_font

HTMLのフォントを強制出来ます。通常はmonospaceが使われますが、これよりも優先したいフォントがある場合に使用します。

html_use_css

コードのシンタックスを<font>ではなく<span>で出力します。
class属性は上記「Comment」や「Statement」といった識別が使用されます。

html_use_encoding

metaタグでcharset指定できます。但し現状の「2html.vim」にはencodingが「cp932」の場合の処理がありませんので、以下のパッチを「2html.vim」に当てる必要があります。
*** 2html.vim   Fri Aug 03 13:23:20 2007
--- 2html.vim.org   Tue Jul 31 09:48:00 2007
***************
*** 162,168 ****
      let s:html_encoding = 'iso-8859-1'
    elseif s:vim_encoding =~ "^cp12"
      let s:html_encoding = substitute(s:vim_encoding, 'cp', 'windows-', '')
!   elseif s:vim_encoding == 'sjis' || s:vim_encoding == 'cp932'
      let s:html_encoding = 'Shift_JIS'
    elseif s:vim_encoding == 'big5'
      let s:html_encoding = "Big5"
--- 162,168 ----
      let s:html_encoding = 'iso-8859-1'
    elseif s:vim_encoding =~ "^cp12"
      let s:html_encoding = substitute(s:vim_encoding, 'cp', 'windows-', '')
!   elseif s:vim_encoding == 'sjis'
      let s:html_encoding = 'Shift_JIS'
    elseif s:vim_encoding == 'big5'
      let s:html_encoding = "Big5"
これでWindowsを使っていらっしゃる人でも「charset=Shift_JIS」と出力されます。

html_no_pre

<pre>を使わずHTML出力する際に使用します。最近はRSSで全文配信する風潮もあるみたいで、その際に<![CDATA[...]]>使いたくない人には有用かもしれません。

html_start_line

ソースからHTMLを生成する際の開始行番号を指定します。

html_end_ilne

ソースからHTMLを生成する際の終了行番号を指定します。

html_ignore_folding

ソースコードがfoldingされていても内部をHTML出力するように設定します。
設定せずにfoldingされているソースをHTML出力すると以下のようなHTMLが出力されます。
#include <stdio.h>

int main(int argc, char* argv[]) {
+---  2 行:printf("Hello, World\n");
}

html_whole_filler

foldingモード時に、追加された行を意味する行「... inserted lines」を表示したく無い場合に設定します。

以上が「2html.vim」で使用できるオプションです。
通常は何も設定されていませんが、以下ではブログ等に貼り付ける際の私なりのコツを示します。
  • スタイルシートを定義する
  • CSSファイルやHTMLファイルの<style>部に以下のコードを追加しておきます。
    .code {
     border-bottom    : 1px solid #777777;
     border-left      : 5px solid #777777;
     border-right     : 1px solid #777777;
     border-top       : 1px solid #777777;
     background       : #555555;
     color            : #ffffff;
     overflow-x       : auto;
     white-space      : nowrap;
     font-family      : monospace;
    }

    /* source code */
    .code>.Comment {
     color: #aaaaaa;
    }
    .code>.Constant,
    .code>.String,
    .code>.Character,
    .code>.Number,
    .code>.Boolean,
    .code>.Float {
      color: #aa7777;
    }
    .code>.Identifier,
    .code>.Function {
     color: #77aa77;
    }
    .code>.Statement,
    .code>.Conditional,
    .code>.Repeat,
    .code>.Label,
    .code>.Operator,
    .code>.Keyword,
    .code>.Exception {
     color: #77aaaa;
    }
    .code>.PreProc,
    .code>.Include,
    .code>.Define,
    .code>.Macro,
    .code>.PreCondit {
     color: #aaffff;
    }
    .code>.Type,
    .code>.StorageClass,
    .code>.Structure,
    .code>.Typedef {
     color: #aaaa55;
    }
    .code>.Special,
    .code>.SpecialChar,
    .code>.Tag,
    .code>.Delimiter,
    .code>.SpecialComment,
    .code>.Debug {
     color: #777777;
    }
    .code>.Underlined {
     color: #00ff00;
     text-decoration: underline;
    }
    .code>.Ignore {
     color: #777777;
    }
    .code>.Error {
     color: #ff0000;
    }
    .code>.Todo {
     color: #0000ff;
    }
    .code>.Folded {
     color: #aaffff;
     background-color: #aaaaaa;
    }
    Internet Explorer6ではセレクタが正しく動作しないかと思いますので修正が必要です。
  • オプションをXHTML使用,CSS使用,PRE未使用モードに設定する
  • ~/_vimrc(un*xでは~/.vimrc)に以下を追加します。
    let g:use_xhtml = 1
    let g:html_use_css = 1
    let g:html_no_pre = 1
  • ソースコードをHTML化する
  • 全体をHTML出力するならば何も選択せず、また部分的にHTML出力するならば行選択(linewise-visual)して
    :TOHtml
    と実行します。
    HTMLには<html>や<body>も含まれますので、実際には<body>から</body>を切り取るといいでしょう。

これで色んなソースコードを皆に見てもらうことが出来るようになります。この方法はあくまでmattnが良いと思った方法ですので、もしかしたらより便利な方法があるかもしれません。
一度CSSさえ設定してあれば、上記の手順1つで簡単にHTML出力出来ます。ぜひコードを晒け出してみて下さい。

mattn the OSS fan.
Posted at by




vim.orgを物色してたら
Vimpress : Manage wordpress blog posts from Vim
というvimからWordPressに記事投稿出来るスクリプトを発見。まぁ、だいたい中身も想像出来ましたが物は試しと随分前にアカウントを取ってて全然使ってないWordPress.comのサイトにポストしてみました。 で、予定通りマルチバイトで文字化けしたので、改造してみました。
基本的な修正は文字コード周りですが、カテゴリの取得方法をMTからmetaWeblogに変えて、BXR: Blosxom XML-RPC Interfaceにも対応してみました。
ただここへのポストはまだ試してないです。苦笑
オリジナルがGPL2ライセンスでしたので、今回はpatchで配布します。
ダウンロード:
どうぞ、楽しいWordPress(+vim)ライフをお楽しみ下さい。

追記
使い方はblog.vimの中で宣言しているblog_usernameとblog_passwordを設定し、blog_urlにXMLRPCのエンドポイントを設定します。
あとは :BlogList でリスト取得(ENTERで開く) :BlogNew で新規ブログ、その状態から :BlogSend で投稿です。(リストからENTERで開いた場合は更新です)
Posted at by




vimを使っていらっしゃる半数以上は開発者の方かと思います。
今日は、そんな開発者の方に便利なtipsをご紹介。

Makefile編集中のファイル名補完

一般的なファイルシステムでは、ファイル名に「=」という文字を使うことが出来ます。ただ、実際に使っているかどうかといえば、めったに使われる事はありません。なのに TOP_DIR=/usr/inc
まで打って<c-x><c-f>とタイプしてもファイル名が補完されないのは、悲しかったりします。 こんなときには以下の設定をvimrcに入れておくと便利です。
autocmd FileType Makefile setlocal isfname-== isfname+=32
通常、フォルダ名やファイル名に「=」やスペースを使う事はまずありませんし、*unix系の開発を経験された事がある方ならば、スペースが混じってしまう事は一大事になる事もご存知かと思います。
この設定だと、Makefileにだけ適応されますし補完もサクサク出来ますね。
ちなみにWindowsで「C:/Program Files/hogehoge」を補完されたいならば TOP_DIR="C:/Progra
と「"」を先頭にしておく事で補完出来ます。
もしかすると、*unixな方は「I」や「L」もisfnameから消してしまう事で CFLAGS=-I/usr/inc
とか LDFLAGS=-L/usr/li
でも補完出来て幸せかもしれません。

長いブロックや複数ブロックがある言語の開発

jsp(JavaServerPages)やASP(ActiveServerPage)、php等で開発される場合には、開始タグで各言語の処理を開始します。例えばphpであれば <?php
$f = fopen("test.dat", "r");
と「<?php」が開始タグとなります。
vimでは各言語に合わせてシンタックスハイライト表示する機能を持っていますが、この開始/終了タグを辿って色付けをしています。この開始/終了タグの検索は、最悪ファイル全体を検索してしまい重たくなってしまう可能性がある為、vimでは閾値範囲内しか検索しないようになっています。
ただ、全ての言語でこの閾値が相応しいものとは限らず、まれに長いブロックが存在するとシンタックスハイライトが効かなくなってしまう事もあります。ここではこの閾値の変更方法を説明します。
閾値は、「syntax sync 」コマンドの「minlines」という値で設定でき、以下のようにautocmdで設定します。
autocmd FileType jsp,asp,php,xml,perl syntax sync minlines=500 maxlines=1000
私は上記の設定を使っています。

pythonのインデントをタブではなくスペースにする

これはvimユーザでpythonの開発をされる方であれば、これは必須ですね。
以下の設定はタブ幅4で、expandtabにしています。
autocmd FileType python setlocal ts=4 sw=4 sta et sts ai
ただ、まれにスペース2つの人とかもいますから、人のソースを修正する際には向かないかもしれませんね。

Emacsユーザに送るキーバインド

これはおまけ。
nmap <m-x> :
nmap <silent> <c-x>1 :only<cr>
nmap <silent> <c-x>2 :sp<cr>
nmap <c-x><c-w><c-w>
cmap <m-x> <nop>
もっとこだわりたい方は、vimacsの方をお勧めします。

追記1:リンクが間違ってました
追記2:KoRoN氏の指摘で「BufEnter」から「FileType」に修正(コピペ元がマズってました)
追記3:さらにKoRoN氏の指摘で「FileType」のpatternが間違ってるとの指摘で修正(もしかしたらBufEnter使ってたのは拡張子使いたいからだったのかな?覚えてないや)

mattn the Onsen Ikitai!
Posted at by




vimのtipsに何かいいのがないか探してたら...
:help scroll-smooth なんてのを発見。

中身みたら...
==============================================================================
Smooth scrolling                                        *scroll-smooth*

If you like the scrolling to go a bit smoother, you can use these mappings: >
        :map <C-U> <C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y><C-Y>
        :map <C-D> <C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E><C-E>

(Type this literally, make sure the '<' flag is not in 'cpoptions').

==============================================================================
と...

まぁスムースですわな。
書いた奴出て来い
Posted at by




前のエントリで気づいたのですが、実はJSONとvimって愛称がよいのでは?と気づきました。
確かに複雑なJSON(例えばif文等を含んだりしているもの)は扱えませんが、JSONPが実行出来るならばメソッド引数にデータ本体が渡る為、簡単なものなら解釈出来る事が分かりました。

例えば、以下のメソッドを定義します。
scriptencoding utf-8
function! JsonHandler(data)
  return a:data
endfunction
function! GetJsonP(url)
  let ret = system("curl -s \"" . a:url . "?callback=JsonHandler\"")
  let org = &enc
  let &enc = "utf-8"
  let ret = substitute(ret, '\\u\([0-9a-zA-Z]\{4\}\)', '\=nr2char("0x".submatch(1))', 'g')
  exe "let val = " . iconv(ret, "utf-8", org)
  let &enc = org
  return val
endfunction
function! GetJson(url)
  let ret = system("curl -s \"" . a:url . "\"")
  let org = &enc
  let &enc = "utf-8"
  let ret = substitute(ret, '\\u\([0-9a-zA-Z]\{4\}\)', '\=nr2char("0x".submatch(1))', 'g')
  exe "let val = " . iconv(ret, "utf-8", org)
  let &enc = org
  return val
endfunction

GetJsonメソッドはURLを指定してJSONオブジェクトを取得する関数です。
はてブのコメント一覧であれば、以下のような処理で取得出来ます。
if exists("json")
  unlet json
endif
if exists("jvar")
  unlet jvar
endif
let json = GetJson("http://b.hatena.ne.jp/entry/json/?url=http://mattn.kaoriya.net/web/hatena/20070726110753.htm")
for jvar in json["bookmarks"]
  echo jvar["user"] . " - " . jvar["comment"]
endfor
実行結果は以下の通り(参考:はてブでアーーーーッ!!!)
yheld - アーーーーッ!!!フヒヒ、サーセンwwww
parkbench -
ku0522 - アーーッ!!
tyoro1210 - アーーーーッ!!!
mattn - 自分でアーーーーッ!!!
また、del.icio.usの場合は通常のJSONにデフォルトのコールバックが含まれてしまいますので、vimでは解釈出来ません。しかしJSONPならばメソッドを指定する事で左記javascriptが消えてくれます。専用にGetJsonPという関数を用意しました。
はてブと同様に if exists("json")
  unlet json
endif
if exists("jvar")
  unlet jvar
endif
let json = GetJsonP("http://del.icio.us/feeds/json/mattn.jp")
for jvar in json
  echo jvar["d"] . " - " . jvar["u"]
endfor
とする事で(参考:mattn.jp in del.icio.us) FlashVillage.com - FREE Flash Templates - http://www.flashvillage.com/
その対応に意味はあるのか (Yak blog) - http://www.greenspace.info/mt/2007/07/31/post_38.html
Top Rated - Free Stock Photos - http://public-domain-photos.com/
Niconorati (ニコノラティ) - ブログで今話題のニコニコ動画 - http://pulpsite.net/niconorati/
SourceForge、「優秀なオープンメ[スプロジェクト」の投票結果を発・- ZDNet Japan - http://japan.zdnet.com/oss/story/0,3800075264,20353786,00.htm
IT戦記 - style.cssText の使い処に関する考察 - http://d.hatena.ne.jp/amachang/20070730/1185788557
void GraphicWizardsLair( void ); // Ustream.tvの凄いところは、Flashで実用的なIRCクライアントを作っちゃったところ - http://www.otsune.com/diary/2007/07/30/1.html#200707301
カイ氏伝: Wikipediaの「RSSリーダー」がとんでもない件 - http://blogging.from.tv/archives/000564.html
美輪明宏のチンコの有無を返すAPI作った | dzfl::blog - http://dzfl.jp/blog/2007/07/29/miwa-mojo-api/
美輪明宏のチンコの有無を配信するRSS作った - PRESS RELEASE on VOX - http://asada.vox.com/library/post/%E7%BE%8E%E8%BC%AA%E6%98%8E%E5%AE%8F%E3%81%AE%E3%83%81%E3%83%B3%E3%82%B3%E3%81%AE%E6%9C%89%E7%84%A1%E3%82%92%E9%85%8D%E4%BF%A1%E3%81%99%E3%82%8Brss%E4%BD%9C%E3%81%A3%E3%81%9F.html
Cookie Manager | Javascript Code | All Things Webby - http://insin.woaf.net/code/javascript/cookiemanager.html
[N] 無料で使える18のアプリケーション&ウェブサービス - http://netafull.net/lifehack/021313.html
flivpee - Google Code - http://code.google.com/p/flivpee/
MOONGIFT: ≫ オープンメ[スのFlashムービープレーヤ「Flivpee」:オープンメ[スを毎日紹介 - http://www.moongift.jp/2007/07/flivpee/
TechCrunch Japanese アーカイブ ≫ Pownceが、ついにAPIを公開 - http://jp.techcrunch.com/archives/pownce-moving-to-open-api-eventually/
といった結果が得られます。
本来ならcallbackもvimscriptを呼ばせるべきかもしれませんね。
vimを使ってマッシュアップアプリを作ってみたい人には、便利なtipsかも知れません。
私はライブラリ化するつもりはありませんが、してみたい人がいるならばぜひvimscriptsに登録してみて下さい。
まぁ、「意外と知られていない」という割には自分も知らなかった訳ですが...

the half of mattn is composed of fondness.
Posted at by




意外と便利なvimscript、なんとなく今日は底に眠ってそうで実は便利なスクリプトをご紹介。
vimで翻訳出来たら便利かな?なんて思ったりしませんか?
出来ます。

香り屋testdirというフォルダに、excitetranslate.vimというファイルがあります。
これを取ってきて、pluginsディレクトリに放り込むだけ。
ただし作られてから結構古く、exciteのURLも変更されていますので、以下のパッチを当てる必要があります。

*** excitetranslate.vim.orig    Mon Jul 23 16:30:34 2007
--- excitetranslate.vim Mon Jul 23 16:28:52 2007
***************
*** 4,16 ****
  "
  " Maintainer: MURAOKA Taro <koron@tka.att.ne.jp>
  " Author: Yasuhiro Matsumoto <mattn_jp@hotmail.com>
! " Last Change:25-Dec-2021.
  
  if !exists('g:excitetranslate_options')
    let g:excitetranslate_options = 'register,buffer'
  endif
  
! let s:excite_web = 'http://www.excite.co.jp/world/text/'
  
  function! s:CheckEorJ(word)
    let all = strlen(a:word)
--- 4,16 ----
  "
  " Maintainer: MURAOKA Taro <koron@tka.att.ne.jp>
  " Author: Yasuhiro Matsumoto <mattn_jp@hotmail.com>
! " Last Change:25-Dec-2021.
  
  if !exists('g:excitetranslate_options')
    let g:excitetranslate_options = 'register,buffer'
  endif
  
! let s:excite_web = 'http://www.excite.co.jp/world/english/'
  
  function! s:CheckEorJ(word)
    let all = strlen(a:word)
***************
*** 38,43 ****
--- 38,44 ----
    silent! %v/^<textarea /d _
    silent! %v/name="after"/d _
    silent! %s/<[^>]*>//g
+   silent! %s/\r//g
    let line = getline(1)
    silent bw!
    " Remove temporary files
注意)本プラグインを動作させる為には、「Chalice」に含まれる「aliceライブラリ」が必要です。「aliceライブラリ」の最新版は、svnリポジトリから取得出来ます。

あとは、vimのバッファで「This is a pen!」と70年代生まれ小学校1年生レベルの英語を打ち込み :ExciteTranslate とすれば これはペンです! と70年代生まれ小学校2年生レベルの答えが返ってきます。
またexcitetranslate.vimは、内部が英語ぽいか日本語ぽいかで逆の動作もしますので「魔女の宅急便」を訳すと Kiki's Delivery Service と戸田奈津子並みの答えが返ってきます。
Posted at by




まきこみ計画のcozymaxさんに、「blosxom bookmarks plugin」を紹介頂きました。
【Blosxom】bookmarksプラグインを導入

いやぁ我ながら「適当クオリティ」とは恐いものです。コラ
どんどん修正して良い物にしてあげてください。
そしてフィードバックだけはお忘れなく。コラ

御指摘は、ありがたく頂き、速攻でbookmarksプラグインを修正致します。
普段もコマンドプロンプトで生きている程un*x生活が浸透してしまっているので、GUIなftpツールでダウンロードして、修正してアップして...なんて事しませんよ。
vimmerならば、リモート直編集っす。

:e ftp://user@server/path/to/file/file.txt
Enter Password:

ちなみにvim、ftp以外にもscp、http、webdav、rsync、sftp等が使えます。ファイルへのパスが仮想でない分、webdav(URLはdav://)なんか、かなり便利です。
工夫すればsmbclient使って「smb://」にも対応出来るんじゃないですかね。

しかしながら最近、vimのオフィシャルへは一つもパッチを送っていないというありさま。 かろうじてチュートリアルの翻訳を進めているくらいです。
:help mbyte
で出てくるメアドも既に使えません。

シャキっとします。シャキっと。はい。
Posted at by



2008/01/30


それは...

チミに足りないものカード
Vimの足りないものカード
by ふりーむ! 無料ゲーム/フリーゲーム
Posted at by