gem2arch

実家に帰ってきているんだが,対応を誤って輪番停電により下宿のマシンが起きてこなくなってしまったためやることが無くなり,なんとなく gem を Arch のパッケージマネージャで管理しようとした.
同じような PKGBUILD を手動で書くのはタルいので cabal2arch のように gem から自動生成することを考えて書いたのがこれ.ローカルにある gem のパスを引数として渡すとそれの PKGBUILD を生成する.
自動といっても特にC向けのライブラリに依存するようなライブラリの depends まで知る術は無いので,必要に応じて適切に手書きする必要がある.*1
あと Gem::Specification のマニュアル によると license(s) というフィールドが (important ではないが) ちゃんとあるのに,これを設定している gem が手元だと rake-compiler しかなく,今回いくつか生成した PKGBUILD については全部 license を手動で書く羽目になった.

#!/usr/bin/ruby
# coding: utf-8
require 'rubygems'
require 'rubygems/format'
require 'fileutils'
require 'erb'

class Gem2Arch
  def initialize(gem)
    @gem = gem
    @spec = Gem::Format.from_file_by_path(@gem).spec
  end

  def name
    @spec.name
  end

  def pkgname
    'ruby-' + name
  end

  def version
    @spec.version.to_s
  end

  def pkgdesc
    #@spec.description
    @spec.summary
  end

  def arch
    @spec.extensions.empty? ? ['any'] : ['i686', 'x86_64']
  end

  def url
    @spec.homepage || "http://rubygems.org/gems/#{@spec.name}"
  end

  def license
    if @spec.licenses
      @spec.licenses
    else
      $stderr.puts "#{pkgname}: License not found!"
      []
    end
  end

  def depends
    ['ruby'] + @spec.runtime_dependencies.map do |dep|
      s = 'ruby-' + dep.name
      if dep.requirement and not dep.requirement.none?
        s + Gem::Requirement.parse(dep.requirement.to_s).join('')
      else
        s
      end
    end
  end

  ERB.new(DATA.read).def_method self, 'to_pkgbuild'
end

ARGV.each do |gem|
  arch = Gem2Arch.new gem
  Dir.mkdir arch.pkgname unless Dir.exist? arch.pkgname
  Dir.chdir arch.pkgname do
    if File.exist? 'PKGBUILD'
      FileUtils.mv 'PKGBUILD', 'PKGBUILD.bak'
    end
    open('PKGBUILD', 'w') do |f|
      f.write arch.to_pkgbuild
    end
  end
end

__END__
pkgname=<%= pkgname %>
_realname=<%= name %>
pkgver=<%= version %>
pkgrel=1
pkgdesc='<%= pkgdesc %>'
arch=(<%= arch.map(&:inspect).join(' ') %>)
url='<%= url %>'
license=(<%= license.map(&:inspect).join(' ') %>)
depends=(<%= depends.map(&:inspect).join(' ') %>)
makedepends=('rubygems')
source=(http://rubygems.org/downloads/$_realname-$pkgver.gem)
noextract=($_realname-$pkgver.gem)

build() {
  cd "$srcdir"
  local _gemdir="$(ruby -rubygems -e 'puts Gem.default_dir')"
  gem install --ignore-dependencies -i "$pkgdir$_gemdir" $_realname-$pkgver.gem
}

# vim:set ts=2 sw=2 et:

*1:例えば ruby-nokogiri が libxslt に依存しているとか

Arch Linux で PT2

PT2 を手に入れてしまったので Arch Linux で使えるようにした作業メモ.
マシン環境は

% uname -a
Linux reinforce 2.6.37-ARCH #1 SMP PREEMPT Fri Feb 25 07:53:43 CET 2011 x86_64 Intel(R) Core(TM) i3 CPU 530 @ 2.93GHz GenuineIntel GNU/Linux

カードリーダは http://www.amazon.co.jp/dp/B00117VJ7O

arib25

まずこれをインストールする.
PKGBUILD 書いておいたので makepkg -si で.
https://github.com/eagletmt/PKGBUILDs/tree/master/arib25

pt1_drv, recpt1

次にドライバと視聴・録画のためのプログラムをインストール.
これも PKGBUILD 書いておいたので makepkg -si で.
https://github.com/eagletmt/PKGBUILDs/tree/master/pt1-hg
うまくいっていれば /dev/pt1video[0-3] ができているはず.
lspci で

08:01.0 Multimedia controller: Xilinx Corporation Device 222a (rev 01)

みたいなのが表示されないときは,そもそも PT2 がハードウェア的にも認識されていないので接続を確かめる.

B-CAS

pcsc-tools, pcsc-perl パッケージをインストール.
それと ccid パッケージが必要だが,最新の 1.4.2 では B-CAS カードを認識してくれないので 1.3.13 をインストールする.
1.3.13 用の PKGBUILD.
https://github.com/eagletmt/PKGBUILDs/tree/master/ccid13
/etc/rc.d/pcscd start で pcsc のデーモンを立ち上げる.
必要に応じて /etc/rc.conf に DAEMONS=(... pcscd) を追加しておく.
うまくいっていれば pcsc_scan を起動して B-CAS カードを抜き差しするとそれっぽい表示がされるはず.
Unresponsive card と表示されるときは B-CAS カードの向きが間違ってる*1

これで recpt1 は動いたんだけど,recpt1ctl --channel でチャンネルを変えようとすると

Cannot tune to the specified channel
Tuner cannot start recording

と出力して recpt1 が死ぬ.
recpt1ctl --extend はちゃんと動作してる模様.

追記 2011-03-29T00:40:32

現時点で Arch の標準的なカーネルである kernel26-2.6.37.* では DVB 版の PT1 ドライバがモジュールとして含まれている.

% zgrep -B3 CONFIG_DVB_PT1 /proc/config.gz
#
# Supported Earthsoft PT1 Adapters
#
CONFIG_DVB_PT1=m

なぜか俺の環境ではこのモジュールは使えなかったんだが,念のため pt1_drv と競合するのを避けるために earth_pt1 をブラックリストに入れてロードしないようにした.
Arch でブラックリストに入れるには /etc/rc.conf に

MODULES=(!earth_pt1 ...)

というように書き加えればいい.

*1:これ絶対多くの人が表裏反対に差すと思うんだけど…

EM::DefaultDeferrable の使い道

というわけで
http://d.hatena.ne.jp/eagletmt/20110211/1297413439

class HtmlDocument
  include EM::Deferrable
  ...

で良いのであった…


では EM::DefaultDeferrable は何のために提供されているのかというと

DefaultDeferrable is an otherwise empty class that includes Deferrable. This is very useful when you just need to return a Deferrable object as a way of communicating deferred status to some other part of a program.

http://eventmachine.rubyforge.org/EventMachine/DefaultDeferrable.html

とあるので,単に完了/失敗を伝えたいときに使うとよさそう.
em-http-request のようにクライアント用途で EventMachine を使うと,いつ EM.stop するかがめんどくさかったりするので,例えばこういう使い方があるのかもしれない.

#!/usr/bin/ruby
# coding: utf-8
require 'em-http'
require 'nokogiri'

class HtmlDocument
  include EM::Deferrable
  attr_reader :http, :parser

  def initialize(http)
    @http = http
  end

  def succeed
    @parser = Nokogiri::HTML.parse @http.response
    super
  end
end

def get_document(uri)
  http = EM::HttpRequest.new(uri).get
  doc = HtmlDocument.new http
  http.callback { doc.succeed }
  http.errback { doc.fail }
  doc
end

EM.run do
  multi = EM::MultiRequest.new

  [
    'http://www.google.co.jp/',
    'http://www.hatena.ne.jp/h/o/g/e',
    'http://eagletmt.com/',
  ].each do |uri|
    defer = EM::DefaultDeferrable.new
    multi.add defer
    doc = get_document uri
    doc.errback do
      puts "ng: #{doc.http.error}: #{doc.http.uri}"
      defer.fail
    end
    doc.callback do
      puts "ok: #{doc.http.uri}: #{doc.http.response_header.status}: #{doc.parser.xpath('//title').inner_text}"
      m = EM::MultiRequest.new
      doc.parser.xpath('//a[@href]').each do |a|
        href = a['href']
        if href.start_with? 'http://'
          doc2 = get_document href
          doc2.errback do
            puts "  ng: #{doc2.http.error}: #{doc2.http.uri}"
          end
          doc2.callback do
            puts "  ok: #{doc2.http.uri}: #{doc2.http.response_header.status}: #{doc2.parser.xpath('//title').inner_text}"
          end
          m.add doc2
        end
      end
      if m.requests.empty?
        defer.succeed
      else
        m.callback do
          defer.succeed
        end
      end
    end
  end

  multi.callback do
    EM.stop
  end
end

include / extend と super

勘違いしてた.include / extend したメソッド(?)を上書きしたときも,上書きされたメソッドを super で参照できるのか.
extend と super を使ったどーでもいい例.

def mod(i)
  Module.new do
    define_method :f do
      super() + [i]
    end
  end
end

module B
  def f
    []
  end
end

module M
  extend B
  5.times do |i|
    extend mod(i)
  end
  def self.f
    super() + [-1]
  end
end

p M.f # => [0, 1, 2, 3, 4, -1]

o = Object.new
o.extend B
5.times do |i|
  o.extend mod(i)
end
p o.f # => [0, 1, 2, 3, 4]

/proc/$PID/fd とテンポラリファイル

http://memo.officebrook.net/20110211.html を見て,なるほどそうやってファイルを取り出せるのかと感心した.


UNIX では open(2) で既に開かれているファイルを unlink(2) してもファイルディスクリプタは有効であり続け,実際に消えるのは close(2) されたときになる.
このことを利用して open(2) した直後に unlink(2) することで,tmpfile(3) のようにプロセスが終了したときに勝手に消えるテンポラリファイルを作ることができる.


で,この unlink(2) されたけどファイルディスクリプタは生きているようなファイルはディレクトリからは見えないけど,/proc/$PID/fd でどうやら参照できるために↑のような形で取り出せるようだ.
この様子を確認するためにこんなコードを書いて実行してみた.

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(void)
{
  const char path[] = "/tmp/hello.txt";

  int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
  system("ls -l /proc/self/fd");
  system("ls -l /tmp/hello.txt");
  unlink(path);
  system("ls -l /proc/self/fd");
  system("ls -l /tmp/hello.txt");
  write(fd, "hello\n", 6);
  system("cat /proc/self/fd/3");
  close(fd);
  system("ls -l /proc/self/fd");
  return 0;
}

実行結果.0 が /dev/null になってたり 1,2 がファイルになっているのは quickrun で実行したから.

合計 0
lrwx------ 1 eagletmt eagletmt 64  2月 12 01:11 0 -> /dev/null
l-wx------ 1 eagletmt eagletmt 64  2月 12 01:11 1 -> /tmp/v9d5oqW/5
l-wx------ 1 eagletmt eagletmt 64  2月 12 01:11 2 -> /tmp/v9d5oqW/5
l-wx------ 1 eagletmt eagletmt 64  2月 12 01:11 3 -> /tmp/hello.txt
lr-x------ 1 eagletmt eagletmt 64  2月 12 01:11 4 -> /proc/18885/fd
-rw------- 1 eagletmt eagletmt 0  2月 12 01:11 /tmp/hello.txt
合計 0
lrwx------ 1 eagletmt eagletmt 64  2月 12 01:11 0 -> /dev/null
l-wx------ 1 eagletmt eagletmt 64  2月 12 01:11 1 -> /tmp/v9d5oqW/5
l-wx------ 1 eagletmt eagletmt 64  2月 12 01:11 2 -> /tmp/v9d5oqW/5
l-wx------ 1 eagletmt eagletmt 64  2月 12 01:11 3 -> /tmp/hello.txt (deleted)
lr-x------ 1 eagletmt eagletmt 64  2月 12 01:11 4 -> /proc/18887/fd
ls: /tmp/hello.txt にアクセスできません: そのようなファイルやディレクトリはありません
hello
合計 0
lrwx------ 1 eagletmt eagletmt 64  2月 12 01:11 0 -> /dev/null
l-wx------ 1 eagletmt eagletmt 64  2月 12 01:11 1 -> /tmp/v9d5oqW/5
l-wx------ 1 eagletmt eagletmt 64  2月 12 01:11 2 -> /tmp/v9d5oqW/5
lr-x------ 1 eagletmt eagletmt 64  2月 12 01:11 3 -> /proc/18889/fd

既存の EventMachine::Deferrable なクラスを薄くラップする

本格的に Deferrable なクラスを作りたいときは include EM::Deferrable するけど,そうではなく既存の Deferrable の上にちょっと乗せるだけみたいなときは EM::DefaultDeferrable を利用するといいらしい.
em-http-request を利用して非同期にリクエストを投げ,結果を Nokogiri でパースして例えばタイトルを表示する例.こんなかんじでいいんだろうか.

#!/usr/bin/ruby
# coding: utf-8
require 'em-http'
require 'nokogiri'

class HtmlDocument < EM::DefaultDeferrable
  attr_reader :http, :parser

  def initialize(http)
    @http = http
  end

  def succeed
    @parser = Nokogiri::HTML.parse @http.response
    super
  end
end

def get_document(uri)
  http = EM::HttpRequest.new(uri).get
  doc = HtmlDocument.new http
  http.callback { doc.succeed }
  http.errback { doc.fail }
  doc
end

EM.run do
  multi = EM::MultiRequest.new

  [
    'http://www.google.co.jp/',
    'http://www.hatena.ne.jp/h/o/g/e',
    'http://eagletmt.com/',
  ].each do |uri|
    doc = get_document uri
    doc.callback do
      puts "ok: #{doc.http.response_header.status}: #{doc.parser.xpath('//title').inner_text}"
    end
    doc.errback do
      puts "ng: #{doc.http.error}: #{doc.http.uri}"
    end
    multi.add doc
  end

  multi.callback do
    puts "succeeded #{multi.responses[:succeeded].size}/#{multi.requests.size}"
    EM.stop
  end
end

実行結果(最後の行以外の順番はバラつく可能性あり)

ng: unable to resolve server address: http://eagletmt.com:80/
ok: 200: Google
ok: 404: おさがしのページは見つかりませんでした - はてな
succeeded 2/3

コマンドラインで proxy の設定をできるようにするプラグイン

コードの最初に怪しい英語での説明が書いてあるけど,ようは Edit -> Preferences -> Advanced -> Settings のとこにある設定を Vimperator のコマンドラインから行えるようにしたもの.
manual に設定したときの -ssl とか -no-proxy とかのオプションの扱いが適当なのでまずいところがあるかも.
自分が manual を使わないのでよくわからない…
http://code.google.com/p/vimperator-labs/source/detail?r=85e98ededcc43bb2df66ca1b004cf2ea7aff1f61
で入ったサブコマンドのサポートを利用しているので,現時点ではリポジトリからとってきてビルドした Vimperator じゃないと動かないです.


https://gist.github.com/814452