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
    let g:hoge = a:string
    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」とインクリメントされます。
素晴らしい。

あー、豚まん食べたくなってきた。

2008/01/30

2008/01/24

はてな
突然ですが、サーバを解約しようと思います。
2006年の1月からですから、ほぼ2年ここで色んな事を書いた気がします。

最近は、はてなブックマークdel.icio.us等でもブックマークして下さる方も増え、このサイトを消してしまうのは惜しい気もしますが、また新たな場所で始めたいと思っています。

つきましては現在、移転先を探しています。
これまで、blosxomというblogツールと自己拡張でやってきましたが、次のサイトにそれほどこだわりはありません。
選択肢に、はてなダイアリーや、Voxも考えています。
もちろんCGIが動けば尚の事良いのですが...
ちなみ私、小遣い制度のためお金はありません。自鯖が持てる程の環境もありません。
これまで書いた資料性のある記事は、再度アップしたいとも思います。

皆さん、良い所知りませんか?

コメント、ブックマークコメントお待ちしております。

Posted at 10:59 in その他 | WriteBacks (3)
Tagged as: etc
Bookmarks: add to hatena add to hatena | add to del.icio.us add to del.icio.us | add to livedoor.clip add to livedoor.clip | add to buzzurl add to buzzurl | add to fc2bookmark add to fc2bookmark | add to Yahoo Bookmark add to Yahoo Bookmark | add to Pookmark add to Pookmark | add to NiftyClip add to NiftyClip | この記事へのリンク

2008/01/18

はてな
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氏それっぽい事(書評とか)やってるように見えるんだけど、もしかして手動だろうか?

2008/01/17

はてな
意外と知られていないんですね。ビジュアル選択って
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ってパズルみたいで面白いですよね。方法はこれだけでなく、人によっては私よりも手数の少ない方法を使われる方もいます。
凝ると色んな事が出来ますので、皆さん凄いの見つけたら教えて下さいね。

はてな
まぁ、ただそれだけなんですが...
はてなスターに連射ボタンをつける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);
    }
}
})();

2008/01/16

はてな
以前、個人的なブームで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の時と同様に利用している人は皆無かと思いますが、よかったらどうぞ。

2008/01/15

はてな
追記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)