Entry: Perl - クラスの定数引数はインスタンスメソッドで指定する
Perl - クラスの定数引数はインスタンスメソッドで指定する
Perlでやたら定数風関数を使うようにしてみた。最近この方針を徹底しているけど、後で後悔しなきゃいいが。
use strict; use warnings; use Switch; $a = MyClass1->new; $a->crud($a->MODE_CREATE); $a->crud($a->MODE_READ); $a->crud($a->MODE_UPDATE); $a->crud($a->MODE_DELETE); package MyClass1; use strict; use warnings; use Switch; sub MODE_CREATE() {1} sub MODE_READ() {2} sub MODE_UPDATE() {3} sub MODE_DELETE() {4} sub new { return bless {}, shift; } sub crud { my ($self, $mode) = @_; switch ($mode) { case MODE_CREATE {print "create\n"} case MODE_READ {print "read\n"} case MODE_UPDATE {print "update\n"} case MODE_DELETE {print "delete\n"} } }
名前つき引数としてのハッシュ引数は可読性が高いんだけど、名前を文字列で入力するとミスしやすい。なので下記のようにする。
use strict; use warnings; my $ins = MyClass2->new(); print $ins->some_method( $ins->ARG_NAME => 'saito', $ins->ARG_ADDR => 'sapporo' ); print "\n"; package MyClass2; use strict; use warnings; sub ARG_NAME() {1} sub ARG_ADDR() {2} sub new { return bless {}, shift; } sub some_method { my ($self, %hash) = @_; return $hash{ARG_NAME()}. ' lives in '. $hash{ARG_ADDR()}; }
定数風関数はメソッド呼び出しできるので
- アロー演算子により、クラスが期待する値だけがIDEの補完候補にあがる(クラスを取り違える心配がない)。
- パッケージ修飾が不要(本当の定数だと$Path::To::Module::ARG_NAMEとか長い)。
- 継承もできる(ただし主要なIDEでは継承先まで補完できない)。
Perl Best Practiceが届いた。けど、まだ読めてない。
Entry: ついったらータグs API(仮)
ついったらータグs API(仮)
「ついったらータグs API(仮)」は、ついったらータグs(仮)のデータベースへの問い合わせをアプリケーションから利用するためのインターフェースを提供します。ついったらータグs(仮)のデータベースにはTwitterの各ユーザーページに対する、はてなブックマークタグの情報を格納しています。本APIを利用することで、任意のユーザーに対するタグ付けの情報はもちろんのこと、任意のタグがどのユーザーに適用されているかという情報を取得することができます。また、対象となるデータベースとして、独自のルールでタグ文字列の正規化を行ったテーブルを対象にすることもできます(任意)。
全てのAPIは下記のURLで提供されます。
http://jamadam.com/th/
現在サポートされている操作は、タグ-ユーザー検索(パラメータt=api/tag.json)とユーザー-タグ検索(パラメータt=api/user.json)です。いずれもレスポンスとしてJSONを返します。また、クエリーとしてcallbackを渡すことでJSONPを受け取ることもできます。
タグ-ユーザー検索
任意のタグ文字列を指定することで、このタグがつけられたユーザーのスクリーンネームのリストを得ます。
パラメータ
- t (必須)
"api/tag.json"を指定します。 - str (必須)
任意のタグを指定します。 - nn (オプション)
正規化されたテーブルを使用しないためのパラメータです。1を指定すると、はてなブックマークに登録された通りのタグ文字列が対象となります。デフォルトは0です。
リクエスト例
/th/?t=api/tag.json&str=it
レスポンス例
{"result":[
{"user":"takapon_jp","occurance":"2"},
{"user":"m_kumagai","occurance":"1"},
{"user":"huehara88","occurance":"1"}
]}
※実際はレスポンスに改行は含まれません。
ユーザー-タグ検索
任意のスクリーンネームを指定することで、このユーザーに付与されたタグの一覧を得ます。
パラメータ
- t (必須)
"api/user.json"を指定します。 - str (必須)
任意のスクリーンネームを指定します。 - nn (オプション)
正規化されたテーブルを使用しないためのパラメータです。1を指定すると、はてなブックマークに登録された通りのタグ文字列が対象となります。デフォルトは0です。
リクエスト例
/th/?t=api/user.json&str=jamadam
レスポンス例
{"result":[
{"tag":"twitter","occurance":"38"},
{"tag":"音楽","occurance":"9"},
{"tag":"有名人","occurance":"4"},
{"tag":"webサービス","occurance":"4"}
]}
※実際はレスポンスに改行は含まれません。
実装例
ユーザー-タグ検索を利用してタグ一覧を取得するPerlコードは以下のようになります。
use LWP::UserAgent; use JSON::XS; my $tags = _twitterer_tags('jamadam'); foreach my $entry (@$tags) { print($entry->{tag}); print(":"); print($entry->{occurance}."回"); print("¥n"); } sub _twitterer_tags { my $user = shift; my $ua = LWP::UserAgent->new; my $res = $ua->get('http://jamadam.com/th/?t=api/user.json&str='. $user); my $obj = decode_json $res->content(); return $obj->{result}; }
Entry: 縮.jpに待望の「縮AGAIN」ボタンを搭載
縮.jpに待望の「縮AGAIN」ボタンを搭載
縮.jp。7月に作って放置してたら10月になって話題になった。人生で一番集客した。そんな訳で、ネタを必要以上に膨らませるべく、システムを改修しました。見た目的にはあまり変化ありませんが、中身はほぼ丸々変わってます。特筆すべき変化は。。
待望の「縮AGAIN」ボタンを搭載
生成された短縮URLが気に入らなかった場合、このボタンを押すと別のURLを生成してくれます。なお、縮AGAINした場合、古いURLはしばらくして解放されます。
既設の短縮URLがあれば、短い順にご提案
今までは湯水のように新規作成してましたが、縮ボタンを押した際、まずは既設の短縮URLを提示するようになりました。縮AGEINすると短い順に既設URLが表示され、なくなれば新規作成されます。
転送時、末尾のゴミを可能な限り取り除く
Twitterでhttp://縮.jp/上これすげーみたいなつぶやきが多発していたので、こういう場合はhttp://縮.jp/上を検知して転送します。Perlでいうと、
$id =~ s/¥P{Han}.*//;
となっています。漢字を表す正規表現なんて初めて知りました。
ちなみに、ファイルベースからPostgresqlに移行
パフォーマンスいいかなと思って、1件1ファイルなどというデータ管理をしてたんですが、IDがシーケンシャルに固定されるとか、逆引きできないとか、色々問題あったのでPostgresqlにしました。なお、ファイルベースのキャッシュの仕組みを導入したので転送時のパフォーマンスは以前と変わらない。はず。
APIにも変更あり
offsetというパラメータが新設されました。これは前述の既設URLの再利用と関連するもので、任意の既設URLを取り出すためのパラメータです。offsetを十分に大きくすると自動的に新規作成されます。通常は指定しないでください。
既知の問題
- 元URLがbit.ly等ですでに短縮済みだった場合、iPhoneで転送されない。確かソフトバンクの仕様でiPhoneで多段リダイレクトできない。
- Twitter用のいくつかのGreaseMonkeyスクリプトやアドオンで縮.jpへ飛べない。FirefoxのJavascriptのバグっぽい挙動が原因と思われる。対処法はこちら
- Twitter周辺サービスで日本語ドメインが誤ってパーセントエンコードされているケースも見かけます。サーバサイドでの処理に問題があるのではないか(憶測)。
- iPhoneの多くのアプリで開けない。おそらくアプリからwebkitだかのAPI的なものに渡すURLをpunycode変換してないのではないか(憶測)。対処法はこちらの12月15日の記事くらいしか見当たらなかった。
今後の予定
任意のIDを指定可能に- 不人気文字を避ける仕組み
- 元URLがすでに短縮URLだった場合に展開してから短縮
- スパム対策
Entry: 開発小ネタ集その2 - 任意の文字で数値を表現したりその逆だったり
開発小ネタ集その2 - 任意の文字で数値を表現したりその逆だったり
開発小ネタ集その2。任意の文字の組み合わせで数値を表現したり、数値から文字に戻したりするPerlスクリプト。暗号化ではありませんので注意。str2numはもっといい方法ないかなあ。
use strict; use warnings; ### 文字リスト our @ascii = qw( a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 1 2 3 4 5 6 7 8 9 0 _ + - = ); my $num1 = str2num('a'); ### 1 my $num2 = str2num('='); ### 66 my $num3 = str2num('aa'); ### 67 my $num4 = str2num('aabcae'); ### 1271895443 my $str = num2str($num4); ### aabcae ### -------------- ### 文字列を数値に変換 ### -------------- sub str2num { my $hash = shift; my $num = 0; my @id_array = split(//, $hash); @id_array = reverse(@id_array); for (my $i = 0; $i < scalar @id_array; $i++) { for (my $j = 0; $j < scalar @ascii; $j++) { if ($ascii[$j] eq $id_array[$i]) { $num += (scalar @ascii ** $i) * ($j + 1); last; } if ($j == scalar @ascii - 1) { return; } } } return $num; } ### -------------- ### 数値を文字列に変換 ### -------------- sub num2str { my $idx = $_[0] - 1; my $result = ''; my $sho = int($idx / scalar @ascii); if ($sho > 0) { $result .= &num2str($sho); } return $result. $ascii[($idx % scalar @ascii)]; }
Entry: 縮 REST API
縮 REST API
「縮 REST API」は、URL短縮/転送サービス「縮.jp」の機能を、アプリケーションから利用するためのインターフェースを提供します。
全てのAPIは下記のURL以下で提供されます。
http://api.xn--jj0a.jp/
現在サポートされている操作は、短縮URLの生成(generate.jsonへのPOST)と短縮URLの展開(lookup.jsonへのGET)です。いずれもレスポンスとしてJSONを返します。また、クエリーとしてcallbackを渡すことでJSONPを受け取ることもできるかもしれません。
GENERATE
任意のURLを渡すことで、新規に短縮URLを生成します。
パラメータ
- fUrl(必須)
短縮対象のURL。http://かhttps://で始まる必要があります。 - customId(オプション)
任意の漢字の組み合わせで短縮URLの識別子を指定します。utf8でパーセントエンコードしてください。 - offset(オプション)
fUrlで指定したURLに対する既設の短縮URLがひとつ以上存在する場合、このパラメータに0以上の数値を指定することで任意の候補を選択することができます。既設URLは文字数の短い順に並んでいます。offsetが既設URLの数より大きいとき、新しいURLが生成されます。このパラメータは特殊な目的がない場合は指定しないでください。
リクエスト例
POST /generate.json
fUrl=http://example.com&id=%E4%B8%83
レスポンス例
{"result":{"fUrl":"http://example.com/","generated":"http://縮.jp/七"}}
LOOKUP
既存の短縮URLの識別子を受け取り、元のURLを返します。識別子はUTF-8でパーセントエンコードしてください。
パラメータ
- id(必須)
URLの識別子を指定します。 - hash
後方互換性のための、idの別名です。(廃止の予定)
リクエスト
GET /lookup.json?id=%E4%B8%83
レスポンス
{"result":{"fUrl":"http://example.com"}}
共通
HTTPレスポンスコード
正常/エラーに関わらず「200 OK」を返します。
エラー時のレスポンスボディ
下記の書式でJSONデータを返します。
{"error":"[エラーコード] [説明]"}
エラーコード一覧
- 400: generateの際、fUrlの書式が正しくない、customIdに不正な文字が含まれる、など。
- 409: generateの際、customIdがすでに使用されている、など。
- 404: lookupの際、指定のidが存在しない、など。
実装例
generate.jsonを利用して短縮URLを取得するPerlコードは以下のようになります。
use LWP::UserAgent; use JSON::XS; my $generated = _shuku('http://example.com'); sub _shuku { my $ua = LWP::UserAgent->new; my $res = $ua->post('http://api.xn--jj0a.jp/generate.json', {'fUrl' => $_[0]}); my $obj = decode_json $res->content(); my $decoded = $obj->{result}->{generated}; utf8::decode($decoded); return $decoded; }
Entry: JavaとPerlの挙動の違いでハマった2
JavaとPerlの挙動の違いでハマった2
こういうことなのかな。Javaって用心深いのね。
class superClass{ protected String name = "superClass"; public String who() { return name; } public String who2() { return getClass().toString(); } } class subClass extends superClass { subClass() { name = "subClass"; } }
Entry: JavaとPerlの挙動の違いでハマった
JavaとPerlの挙動の違いでハマった
javaでちょっとハマった。Perlでやってたときの感覚とちょっと違う。Javaむずい。
Perlの場合
package main; my $super = superClass->new(); my $sub = subClass->new(); print $super->who; # superClass print $super->who2; # superClass=HASH(0x817f9f0) print $sub->who; # subClass print $sub->who2; # subClass=HASH(0x8198210) package superClass; sub new { bless {name => 'superClass'}, shift; } sub who { return shift->{name}; } sub who2 { return shift; } package subClass; use base qw(superClass); sub new { bless {name => 'subClass'}, shift; }
javaの場合
public class java_project { public static void main(String[] args) { superClass super_instance = new superClass(); subClass sub_instance = new subClass(); System.out.println(super_instance.who()); // superClass System.out.println(super_instance.who2()); // class superClass System.out.println(sub_instance.who()); // superClass System.out.println(sub_instance.who2()); // class subClass } } class superClass{ private String name = "superClass"; public String who() { return this.name; } public String who2() { return this.getClass().toString(); } } class subClass extends superClass { private String name = "subClass"; }
Entry: Perlで差分バックアップスクリプト
Perlで差分バックアップスクリプト
[2009-08-15] バキューム機能つけた
2006年にpdumpfsっぽいPerlスクリプトを書いた。glastreeの存在は知っていたけど、敢えて自分で作ってみた。それ以来、仕事でもプライベートでも重宝していたんだけど、最近問題発生。職場のファイルサーバーには百万越えのファイル数があって、毎日数万の差分が発生し、バックアップに5時間かかっていた。こいつはまずいということで手直しした。結果、取りあえず、自宅では以前の10倍高速になった。
ちなみに、今回はglastreeのソースを追って参考にしてみたらほぼ同じ内容になってしまったんだけど、glasstreeとの違いは下記のとおり。
- バックアップの管理が日付に依存しないので任意の頻度(例えば2時間おきとか)で実行できる
- コピーされたログを残す
- コアモジュール以外に依存しない
- 差分がない場合はバックアップを自動削除
ログが残るってのはなかなか便利です。差分リスト=作業履歴なので。
使い方
perl ddump.pl [バックアップ対象] [保存場所]
バックアップポイントの数の上限を指定。あぶれた分は古い方から削除される。
perl ddump.pl --vacuum=400 [バックアップ対象] [保存場所]
ソース
use strict; use warnings; use utf8; use English; use Getopt::Long; use File::stat; use File::Copy; use File::Path; binmode(STDIN, ":utf8"); binmode(STDOUT, ":utf8"); binmode(STDERR, ":utf8"); use open IO => ":utf8"; use vars qw(%QUERY %stat_idx); # 設定 use vars qw($log $copy_count $file_count $symlink_count); # ログ関係変数 use vars qw($tgt_path $newdir $olddir); # ディレクトリ main(); sub main { %stat_idx = ('mode' => 2, 'size' => 7, 'utime' => 9); %QUERY = ('mode' => 1, 'size' => 1, 'utime' => 1); GetOptions(\%QUERY, 'mode=s', 'size=s', 'utime=s', 'vacuum=i'); ### 引数の整備と検証 $ARGV[0] = &fixPath($ARGV[0]); $ARGV[1] = &fixPath($ARGV[1]); -d $ARGV[0] or die "$ARGV[0] not found"; -d $ARGV[1] or die "$ARGV[1] not found"; ### 引数を分離 ($tgt_path, my $tgt_dir) = ($ARGV[0] =~ m!(.*)?/([^/]+)$!); if (opendir(DIR, "$ARGV[1]")) { my @dirs = sort grep{ -d "$ARGV[1]/$_" && /^\d+$/} readdir( DIR ); close(DIR); ### 古いディレクトリを削除 if ($QUERY{vacuum}) { for (my $i = 0; $i < $#dirs - $QUERY{vacuum}; $i++) { #print $dirs[$i] . " "; rmtree("$ARGV[1]/$dirs[$i]"); } } ### 前回バックアップの時刻ディレクトリ名を取得 $olddir = "$ARGV[1]/". (pop(@dirs) or ''); } ### 時刻ディレクトリの作成 my ($sec, $min, $hour, $mday, $mon, $year) = localtime($BASETIME); my $new_time = sprintf('%04d%02d%02d%02d%02d%02d', $year+1900, $mon+1, $mday, $hour, $min, $sec); $newdir = "$ARGV[1]/$new_time~"; umask 0; mkdir($newdir, 0755) or die "cannnot make $newdir"; ### ログファイル生成 open($log, ">>$newdir/ddumplog.txt") or die "cannnot make log file"; print $log <<EOF; ----------------------------------------------------------------- -- ddump -- $new_time ----------------------------------------------------------------- EOF ### ログ記録用変数 $copy_count = 0; $file_count = 0; $symlink_count = 0; ### バックアップ &backup($tgt_dir); ### ログファイルに結果出力 print $log "$copy_count/$symlink_count/$file_count (Copyed/symlink/Total)\n"; print $log (time() - $BASETIME). "sec passed\n"; close($log); if (! $copy_count) { ### コピーなしならディレクトリ削除 rmtree($newdir); } else { ### ファイル名確定 rename($newdir, substr($newdir, 0 ,length($newdir) - 1)); } } ### -------------------------------------------- ### 再帰的ディレクトリバックアップ ### -------------------------------------------- sub backup { my $cwd = shift; my $tgt_cwd = "$tgt_path/$cwd"; my $old_cwd = "$olddir/$cwd"; my $new_cwd = "$newdir/$cwd"; ### ディレクトリの作成 ©dirstat($tgt_cwd, $new_cwd); ### カレントディレクトリ内の全ファイルを配列に格納 opendir(DIR, $tgt_cwd) or return 0; my @files_and_dirs = readdir(DIR); close(DIR); my @files = grep { -f "$tgt_cwd/$_" or -l "$tgt_cwd/$_" } @files_and_dirs; ### @files_and_dirsのうち、ファイルを処理 foreach my $a_file (@files) { my $old_file = "$old_cwd/$a_file"; my $new_file = "$new_cwd/$a_file"; my $tgt_file = "$tgt_cwd/$a_file"; my $command = 1; # 0: nothing, 1: hard link 2: copy if (-l $tgt_file) { symlink(readlink($tgt_file), $new_file); $symlink_count++; $command = 0; } elsif (-f $old_file) { my $old_stat = stat($old_file); my $crnt_stat = stat($tgt_file); ### 更新ありならコピー foreach my $key (keys %stat_idx) { if ($QUERY{$key} and $old_stat->[$stat_idx{$key}] ne $crnt_stat->[$stat_idx{$key}]) { $command = 2; last; } } } else { ### ファイルがなければコピー $command = 2; } ### コピー if ($command == 2) { copystat($tgt_file, $new_file); $copy_count++; ### ログ出力 utf8::decode($new_file); print $log "$new_file\n"; } ### ハードリンク elsif ($command == 1) { link($old_file, $new_file); } $file_count++; } ### @files_and_dirsのうち、ディレクトリを再帰処理 my @dirs = grep { -d "$tgt_cwd/$_" and not -l "$tgt_cwd/$_" and $_ ne '.' and $_ ne '..' } @files_and_dirs; foreach my $a_dir (@dirs) { &backup("$cwd/$a_dir") or print "Error at $cwd/$a_dir\n"; } return 1; } ### -------------------------------------------- ### 属性丸ごとファイルコピー ### -------------------------------------------- sub copystat { my ($from, $to) = @_; my $stat = stat $from; copy($from, $to) or die "cannot copy $from to $to"; chown($stat->uid, $stat->gid, $to) if $EUID == 0; chmod($stat->mode, $to); utime($stat->mtime, $stat->mtime, $to); } ### -------------------------------------------- ### 属性真似てディレクトリ作成 ### -------------------------------------------- sub copydirstat { my ($from, $to) = @_; my $stat = stat $from; mkdir $to, 0555 or die "cannot mkdir $to"; chown ($stat->uid, $stat->gid, $to) if $EUID == 0; chmod ($stat->mode, $to); utime ($stat->mtime, $stat->mtime, $to); } ### -------------------------------------------- ### ファイルパスの整備 ### -------------------------------------------- sub fixPath { my $in = $_[0]; $in =~ s!^!\./! unless ($in =~ m!^\.*/!); $in =~ s!/$!!; return $in; }
Entry: マルチプラットフォームなアプリ一覧
マルチプラットフォームなアプリ一覧
いつでもMacOSとLinuxとWindowsを行き来できるように。
| ブラウザ | Firefox 定番ブラウザ。下記はおすすめプラグイン。
|
|---|---|
| メーラー | Thunderbird 定番メーラー。下記はおすすめプラグイン。
|
| テキストエディタ | jEdit Mac版は割と軽い。下記はおすすめプラグイン。
Scintilla Text Editor EUC-JP不可。 Komodo Edit jEdit使うようになってから出番がめっきり減った。 |
| ファイル転送 |
Filezilla mac版は驚くほど使いづらい。主な用途はファイラであるmuCommanderで事足りるので、Filezillaは今のところ不要。 |
| 波形編集 | Audacity |
| オフィススィート | OpenOffice 定番。下記はおすすめ機能拡張。
OPENPROJ プロジェクト管理 |
| アーカイバ | |
| ファイラ | muCommander |
| 動画エンコード | HandBrake |
| マインドマップ | XMind 中国製。結構おいしい操作性。でも、win版はよく落ちる。Mac版はちょっと前まで起動すらしなかったけど、最近のバージョンで改善。将来に期待。 |
| メディアプレーヤー | Songbird 最近本格的にiTunesから乗り換えたけど、特に問題ないんじゃないかと思う Miro 動画専用かな。今のところ満足な出来。 Amarok |
| DTP | Scribus インストールが激しく難しい GIMP たいていの人はPhotoshopなんて要らないんじゃないかと思う。Perlでマクロも組める。 |
| フォント | IPAフォント OOoのPDF出力時にはTTFフォントが必要なようで、日本語用にIPAフォントがおすすめ。 |
| UML | Jude/community 国産。MacOSX版もざっと見たところ、普通に動く。 |
Entry: 非root権限でCPANモジュールをインストールする(焼増し)
非root権限でCPANモジュールをインストールする(焼増し)
非root権限でCPANモジュールを使う方法を調べた。Debian lenny(最小限のセットアップ)にて。2006年頃までにすべての情報が出尽くしてるのに何を今さら。
cpanの初期設定をする。設定項目「makepl_arg」はモジュールのインストール先ディレクトリ。PREFIX=~/perlと記載することで~/perl下に配置される。設定ファイルは下記のように書き換える。
$ vi ~/.cpan/CPAN/MyConfig.pm- 'makepl_arg' => q[],
+ 'makepl_arg' => q[PREFIX=~/perl],
依存関係のあるモジュールを芋づる式にインストールするとき、後に入るモジュールのテストスクリプトが、直前の依存モジュールをincludeできなくてつまづく。これを回避するため、ユーザーの環境変数PERL5LIBにパスを設定する。~/.bashrcに下記を追記すれば楽。どうやらものによって下記のような2つのパスにインストールされるらしい。
$ vi ~/.bashrc
export PERL5LIB=~/perl/lib/perl/5.10.0:~/perl/share/perl/5.10.0
最初の「~/perl」は先ほどMyConfigのmakepl_argに追加したパス、それ以降は環境に依存するようなので一度失敗してみてパスを確認して再試行。もっといい方法ないのか。そのためのlocal::libなのかどうなのか、またの機会に調べてみよう。
参考サイト
一般ユーザ環境におけるCPANモジュールの使い方
Using CPAN with a non-root account
Subscribe to my RSS feed