2010-01-27

Perl における配列を返す式の n 番目の要素を表す記法

例えば今,配列を返す関数があるとする.

sub f {
    (1, 2, 3);
}

f() の返り値の n 番目の要素を得るには次のようにすればよい.

@a = f();
print $a[$n];

この時,一時変数 @a を使わないようにするにはどうすればよいだろうか?

以下のようにすればうまくいくが不自然だ.

print [f()]->[$n];

あるいは,

print ${[f()]}[$n];

ともできるが,いずれにせよ不自然.

もっと素直な記法は無いのか?

勘違い (2010/7/2 追記)

上のような変なことをしなくても,

(f())[$n]

でよい.ただし,

print (f())[$n];

は print 関数と括弧の関係上文法エラーになるので,

print ((f())[$n]);

とする必要がある.上の print がうまくいかなかったので勘違いしていた .

http://d.hatena.ne.jp/metalglue/20090614/1244971305 も参考にせよ.

2010-01-26

Perl で HTML ファイルの内容を書き換える

ネットからダウンロードした Web ページの内容を少しだけエディタで書き換えることがよくある,これを Perl を使ってプログラムからやるにはどのようにすればよいだろうか.

HTML::TreeBuilder, HTML::Element あたりを使うことになるだろう.

HTML をパーズし,HTML に戻す

まずは単に HTML をパーズし,HTML に戻すプログラムを書いてみよう.

use HTML::TreeBuilder::XPath;
my $html = HTML::TreeBuilder::XPath->new_from_file('sample.html');
print $html->as_HTML('&<>');

as_HTML() の引数としてエンティティ化する文字のリストを明示的にを渡さないと,漢字などは全てエンティティにされてしまうので注意.

ここで問題となるのは $html->as_HTML('&<>') の文字コードは何なのか,そもそも sample.html の文字コードの扱いはどうなっているのかということである.HTML::TreeBuilder や HTML::Element には文字コードに関する記述は無い.

sample.html の文字コードを変えていろいろやってみたところ,どうやら入力をそのまま出力しているようである.当然 utf8::is_utf8($html->as_HTML('&<>')) は偽となる.

sample.html の文字コードを UTF-16 にするとうまくいかなかったので,結局タグが ASCII である文字コードのときにうまくいくだけのようだ.

あちこち検索などした結果,求める情報は man HTML::Parser の METHODS > Unicode にあることが判明した.これによると utf8 文字列なら扱えるとあるので,次に得られた文字列を utf8 文字列に変換する方法について書くことにする.

文字コードが不明の文字列を utf8 文字列に変換する

perl の utf8 文字列についての詳しい話は別の場所を参照してほしい.

ここでは Encode::Guess を用いて,文字コードが不明の文字列を utf8 文字列に変換する方法について書くことにする.

Encode::Guess の guess_encoding 関数を用いて以下のように書けばよい.

use Encode::Guess;

sub content_of_file {
    my ($fname) = @_;
    open my $fh, '<', $fname;
    local $/;
    my $content = <$fh>;
    close $fh;
    return $content;
}

while () {
    my $content = content_of_file($_);
    my $decoder = guess_encoding($content, qw{shift_jis euc-jp jis});
    print "$_ => ", $decoder->name(), "\n";
    my $decoded = $decoder->decode($content);
}

HTML をパーズし,HTML に戻す (again)

これまで説明してきたことを合体すれば HTML をパーズし,HTML に元の文字コードのまま戻すコードを書くことができる.

use Encode::Guess;
use HTML::TreeBuilder::XPath;

sub content_of_file { ... }

my $content = content_of_file('sample.html');
my $decoder = guess_encoding($content, qw{shift_jis euc-jp jis});
my $decoded = $decoder->decode($content);
my $html = HTML::TreeBuilder::XPath->new_from_content($decoded);
open my $fh, '>', "/tmp/sample.html";
print $fh $decoder->encode($html->as_HTML('&<>'));
close $fh;

HTML の書き換え

HTML を書き換えるには,delete_content(), push_content(), attr() などを用いる.

use utf8;

use Encode::Guess;
use HTML::TreeBuilder::XPath;

sub content_of_file { ... }

my $content = content_of_file('sample.html');
my $decoder = guess_encoding($content, qw{shift_jis euc-jp jis});
my $decoded = $decoder->decode($content);
my $html = HTML::TreeBuilder::XPath->new_from_content($decoded);

my $nodeset = $html->findnodes('//a[@href]');
for my $element (grep { $_->attr('href') =~ /^index\.html$/ &&
                        $_->look_down('_tag', 'img'); }
                 $nodeset->get_nodelist()) {
    my $href = $element->attr('href');
    $href =~ s/index\.html/default.html/;
    $element->attr('href', $href);
    $element->delete_content();
    $element->push_content('画像は削除されました.');
}

open my $fh, '>', "/tmp/sample.html";
print $fh $decoder->encode($html->as_HTML('&<>'));
close $fh;

情報源

2010-01-19

Git の根本

Git の SHA-1 名のことを調べていたら Git の根本について書いてあったのでまとめておく.簡単のため正確さを犠牲にしているので注意.

オブジェクト

Git のオブジェクトは,(type, length) というヘッダと,データ部分をつなげたバイナリである.このバイナリから SHA-1 ハッシュを計算して得られた 160 bit の値(表記は 40 桁の 16 進数)を名前とする.

オブジェクトには以下のものがある.

blob. これはファイルを表す.データ部分はファイルの内容そのものである.

tree. これはディレクトリを表す.データ部分は (type, name, object) をレコードとするリストで,name でソートされている.type に blob と tree とを取ることによりディレクトリツリーを表現できる.

commit. これはあるディレクトリツリーから別のディレクトリツリーへの変更を表す.データ部分は (before-tree, after-tree, time, author) である.

オブジェクトデータベースとリファレンス

これらのオブジェクトは全て .git/objects に格納されている.この中から所望のデータを取り出す手がかりが必要だが,それらは .git/refs/heads に commit オブジェクトの SHA-1 名を内容とするファイルとして格納されている.これらを,そのファイル名を名前とするブランチと呼ぶ.

その他

  • .git/objects に格納されているオブジェクトの名前は,ディレクトリ名とファイル名をつなげたものである.例えばオブジェクト .git/objects/4d/97bf636e78366658f2aaa80389c581e4cee9c0 の名前は 4d97bf636e78366658f2aaa80389c581e4cee9c0.
  • オブジェクトの情報,内容を表示するにはコマンド git show を用いる.
  • blob の定義から,同一内容を持つファイルのオブジェクトの SHA-1 名は常に同じとなる.
  • commit オブジェクトの before-tree は複数存在する場合がある.merge した場合におきるが,conflict のない merge の場合などには複数にならないこともあって正確にはまだよくわからない.→ git merge --squash とすると merge しても before-tree が複数にならないことが判明 hmmm...
    • 著者 JCH さんのコメントにより判明.「conflict のない merge の場合など...」は fast-forward のケースで,この場合には merge-commit とならず before-tree が複数にならない.
    • merge --squash は man git-merge の当該項目に記述してあるとおり merge-commit とならない仕様である.

情報源

入門Git pp. 8--19

Git の SHA-1 名

現在,ある Subversion リポジトリを Git によって追跡しているが,各コミットに関するベンチマーク値を Git 側のコミットの SHA-1 名をキーとして共有ディレクトリに格納している.

そこで疑問が生じるわけであるが,別の git svn clone で追跡を開始した Git リポジトリの各コミットの SHA-1 名は,元の subversion のコミットが同じなら同じになるのだろうか?それとも異なるものになるのだろうか?

予想としては,コミットというのはそもそもコミットした時の時刻情報を含んでいるので,コミット情報から生成される SHA-1 ハッシュは当然異なるものになるだろう,ということである.

しかし実験してみると,svn clone によって生成されたコミットの時刻情報は元の Subversion のコミットの時刻情報を使っているようで,SHA-1 値は全く同一となった.

2010-01-15

Git における clone の clone から origin への push

オリジナルのレポジトリ repos があるとする.

$ cd repos
$ git branch
* master

これを clone に clone する.

$ cd ..
$ git clone repos clone
$ cd clone
$ git branch
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
$ git checkout -b branch
$ git branch
* branch
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
(ファイルを編集)
$ git commit -a
$ git checkout master

これをさらに clone する.

$ cd ..
$ git clone clone cloneofclone
$ cd cloneofclone
$ git branch
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/branch
  remotes/origin/master

さて,このブランチ remotes/origin/branch を repos の branch というブランチとして push するにはどうすればよいか?

$ git push ../repos remotes/origin/branch:refs/heads/branch

とすればよい...

本当か?ブランチのネーミングの本質がわかってないので確信できない.

(2012/3/1 追記)上のやり方で正しい。ただし、refs/heads/branch と branch は同値なので、

$ git push ../repos remotes/origin/branch:branch

としてもよい。参照: Pro Git - Pro Git 9.5 Git Internals The Refspec

2010-01-13

Debian パッケージ名の規則

Q: Debian のパッケージ名の規則はどこに書いてあるのか?

ADebian Policy Manual - Control files and their fields (5.6.1 Source) にある.誰もが 3.1 The package name に書いてあると思うだろうが,そこの参照先の参照先である上記に記述してある.

これによると,[a-z][a-z0-9.+-]+ である.