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プログラミングの道具箱: プロフェッショナルが明かす研ぎ澄まされたツール群の使いこなし
工藤 智行
技術評論社 単行本 / ¥53 (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,740 (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編集部
技術評論社 大型本 / ¥350 (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