ラベル ruby の投稿を表示しています。 すべての投稿を表示
ラベル ruby の投稿を表示しています。 すべての投稿を表示

2011年12月24日土曜日

Solaris 11でmikutter用ruby環境を構築する

pkgsrcがうまくいかずぐぬぬしていたので、気をとり直してmikutterを動かしてみることにした。 ただ、ホストがXServerを動かせるような環境では無いので、sshでXをforwardして起動する。 また、リポジトリにはruby1.8.7しか無いため、ruby1.9.3はソースからビルドする。

pkg install gcc
pkg install system/header
pkg install library/desktop/gtk2
pkg install x11/session/xauth
pkg install system/font/truetype/ipafont
wget ftp://ftp.ruby-lang.org/pub/ruby/ruby-1.9.3-p0.tar.gz
tar xzf ruby-1.9.3-p0 && cd ruby-1.9.3-p0
./configure --prefix=/usr/ruby && make && make install
/usr/ruby/bin/gem install gtk2

だいたいこんな感じでmikutterを実行できるruby環境が出来上がる。 あとはいつも通りmikutterをcheckoutして実行すればよい。

Solaris 10(SPARC)の時はやたら苦労したのに、 今回はすんなり通ってしまいあっさり終わってしまった。

2011年12月11日日曜日

ruby/dlでの構造体の扱いについて

ruby/dlでlibaoを使うでCのライブラリをCでラッパを書かずにrubyから利用できることは分かった。 ただ、構造体の扱いが少々面倒臭いことが分かったので、纏めておく。

構造体のメンバに構造体が存在する場合

構造体の定義はDL::Importer#structを用いて行うことができるが、 構造体のメンバに構造体が存在する場合、それを自動的に展開はしてくれない。

例えば、以下のような2つの構造体があったとする。

typedef struct info{
  int no;
  char *name;
} info;

typedef struct namelist{
  info first;
  info second;
  info third;
} namelist;

これをDL::Importer#structに渡す際には、以下のように書きたくなるが、 これではnamelist構造体のメンバであるinfoがうまく展開されない。

Info = struct(['int no', 'char *name'])
NameList = struct(['info first', 'info second', 'info third'])

以下のように手動で展開する必要がある。

NameList = struct(['int first_no', 'char *first_name',
  'int second_no', 'char *second_name', 
  'int third_no', 'char *thrd_name'])

関数の引数に構造体のポインタを渡す場合

関数の引数に構造体のポインタを渡す場合は、ruby/dl側でvoid *に自動的に変換するようなので、 そのまま渡して構わない模様。

# int check(namelist *list);
extern('int check(namelist *list)')

関数の引数に構造体そのものを渡す場合

aliasできる型が無いのでDL::Imprter#typealiasも利用できないし、どうしたらいいものか…と悩み続けて数週間。 入れ子の構造体同様に定義してしまえばよいということにようやく気がついた。

# int check(namelist list);
extern('int check(int first_no, char *, int , char *,  int, char *)')

あとはrubyから呼ぶ際にメンバの順番通りに引数を渡せば良い。

ここまでくるのにだいぶ長期間悩んでしまったが、まぁなんとかなったのでよしとしたいところ…orz。

2011年7月31日日曜日

Rubyのio/consoleを試す

Rubyでコンソールアプリケーションを作成する際に、 標準のIOではうまくいかない問題がある。 例えば...

  • リアルタイム入力
  • パスワード入力時等に文字のエコーを停止
  • 端末のサイズを取得
等がある。これはcursesなどを用いれば解決できるが、パスワード入力のみで必要な場合等 cursesでは大げさすぎると感じる場合もある。

そこで、次のruby(1.9.3?)あたりに標準で付属されるらしい、 io/consoleというライブラリを使ってみる。

導入

今回は、rubyのリポジトリから最新開発版を入手し、ビルドする。RVMを用いてもよい。

miku@hatsune% svn co http://svn.ruby-lang.org/repos/ruby/trunk ruby19-svn
miku@hatsune% cd ruby19-svn
miku@hatsune% autoconf
miku@hatsune% ./configure --prefix=/usr/ruby19-svn --program-suffix=19-svn
miku@hatsune% make
miku@hatsune% sudo make install

lib/ruby/1.9.1/x86_64-linux/io/console.so がio/console用のライブラリらしい。

使ってみる

リファレンスなんて無いので、ソース(ext/io/console/console.c) を読みながらとりあえず叩いてみる。 IOクラスに追加されるメソッドは、以下のとおり...らしい。

  • IO.console #=> IO for console device
  • IO#raw
  • IO#raw!
  • IO#getch
  • IO#echo=
  • IO#echo?
  • IO#noecho
  • IO#winsize
  • IO#winsize=
  • IO#iflush
  • IO#oflush
  • IO#ioflush

実際にirbで叩いてみると分かるが、getchでは入力した瞬間にその入力した文字が返される。 また、winsizeではその瞬間のコンソールサイズが配列として返される。 その他のメソッドも、だいたい名前のままな動作をする模様。

irb(main):012:0> IO.console
=> #

irb(main):011:0> IO.console.getch
=> "m"

irb(main):013:0> IO.console.noecho{|io| p io.getch}
"a"
=> "a"

irb(main):014:0> IO.console.winsize
=> [55, 139]

改行の問題

io/consoleで入力を受け付けながら出力すると、 "\n"で正しく改行ができなくなることがある模様。 例えば以下のように書いた場合、LFのみが行われCRが行われず、 行は変わるものの開始位置がおかしくなる。

Thread.start(){
  loop {IO.console.getch}
}

IO.console.noecho{|io|
  loop{
   io.printf("%d\n", rand(1024))
  sleep 0.1
  }
}

これは、"\n"を"\r\n"に変えてやれば解決する。 今後修正されるかもしれないが、一応。

2011年6月30日木曜日

ruby/dlでlibaoを使う

Cなんて一行も書きたくないけれど、Cのダイナミックリンクライブラリをrubyから使いたい。 そんな人のためのライブラリ(?)がruby/dlである。 (Cは一行も書かなくてもよいものの、流石にCの知識がある程度無いと使えないが…)

利点と欠点は以下のとおり。

・利点

  • Cのダイナミックリンクライブラリをコンパイル作業無しで利用できる
  • すべてrubyで書ける
・欠点
  • 環境依存部分の吸収を自前でやらなければならない場合がある
  • 一部無理矢理な定義をしなければならない部分がある(入れ子の構造体、enum型など)

今回は、ruby1.9のdlライブラリを用いて、 マルチプラットフォームなサウンド出力ライブラリであるlibaoを叩き、 オーディオファイルを再生できるようにしてみる。

ヘッダファイルを読む

ruby/dlでlibaoを叩くには、まずlibaoにどのような関数や構造体などがあるのか調べる必要がある。 libaoでは、ほとんどが$PREFIX/include/ao/ao.hで宣言されているので、それを読む。 ao/ao.hで定義されていてruby/dlから叩くのに必要な部分は、以下のとおりである。

/* --- Constants ---*/
#define AO_TYPE_LIVE 1
#define AO_TYPE_FILE 2

#define AO_ENODRIVER   1
#define AO_ENOTFILE    2
#define AO_ENOTLIVE    3
#define AO_EBADOPTION  4
#define AO_EOPENDEVICE 5
#define AO_EOPENFILE   6
#define AO_EFILEEXISTS 7
#define AO_EBADFORMAT  8

#define AO_EFAIL       100

#define AO_FMT_LITTLE 1
#define AO_FMT_BIG    2
#define AO_FMT_NATIVE 4


/* --- Structures --- */
typedef struct ao_info {
int  type; /* live output or file output? */
char *name; /* full name of driver */
char *short_name; /* short name of driver */
        char *author; /* driver author */
char *comment; /* driver comment */
int  preferred_byte_format;
int  priority;
char **options;
int  option_count;
} ao_info;

typedef struct ao_functions ao_functions;
typedef struct ao_device ao_device;

typedef struct ao_sample_format {
int  bits; /* bits per sample */
int  rate; /* samples per second (in a single channel) */
int  channels; /* number of audio channels */
int  byte_format; /* Byte ordering in sample, see constants below */
        char *matrix; /* input channel location/ordering */
} ao_sample_format;

typedef struct ao_option {
char *key;
char *value;
struct ao_option *next;
} ao_option;


/* --- Functions --- */
/* library setup/teardown */
void ao_initialize(void);
void ao_shutdown(void);

/* device setup/playback/teardown */
int   ao_append_global_option(const char *key,
                              const char *value);
int          ao_append_option(ao_option **options,
                              const char *key,
                              const char *value);
void          ao_free_options(ao_option *options);
ao_device*       ao_open_live(int driver_id,
                              ao_sample_format *format,
                              ao_option *option);
ao_device*       ao_open_file(int driver_id,
                              const char *filename,
                              int overwrite,
                              ao_sample_format *format,
                              ao_option *option);

int                   ao_play(ao_device *device,
                              char *output_samples,
                              uint_32 num_bytes);
int                  ao_close(ao_device *device);

/* driver information */
int              ao_driver_id(const char *short_name);
int      ao_default_driver_id(void);
ao_info       *ao_driver_info(int driver_id);
ao_info **ao_driver_info_list(int *driver_count);
char       *ao_file_extension(int driver_id);

/* miscellaneous */
int          ao_is_big_endian(void);

さっと眺めると、定数・構造体を定義した後、 関数を定義してあるシンプルな物であることが分かる。

rubyのAOモジュールを作成する

ao/ao.hの情報から、ruby/dlを用いてrubyからlibaoを叩くためのモジュール、 AOを作成する。 基本的な使用例は リファレンスにサッと書いてあるので、それを参考にする。

module AO
  # DL::Importerのインスタンスメソッドを
  # AOモジュールの特異メソッドとして追加する
  extend(DL::Importer)

  # libao.so(Windowsならlibao.dll)を
  # ライブラリパスから検索しロードする
  dlload('libao.so')


  # ao/ao.hで定義されていた定数を定義する
  # ao/ao.h
  AO_TYPE_LIVE   = 1
  AO_TYPE_FILE   = 2

  AO_ENODRIVER   = 1
  AO_ENOTFILE    = 2
  AO_ENOTLIVE    = 3
  AO_EBADOPTION  = 4
  AO_EOPENDEVICE = 5
  AO_EOPENFILE   = 6
  AO_EFILEEXISTS = 7
  AO_EBADFORMAT  = 8
  
  AO_EFAIL       = 100

  AO_FMT_LITTLE  = 1
  AO_FMT_BIG     = 2
  AO_FMT_NATIVE  = 4


  # ao/ao.hで定義されていた構造体を定義する
  AO_Info =
    struct(['int  type',
            'char *name',
            'char *short_name',
            'char *author',
            'char *comment',
            'int  preferrd_byte_format',
            'int  priority',
            'char **options',
            'int  option_count'])
  AO_Sample_Format =
    struct(['int  bits',
            'int  rate',
            'int  channels',
            'int  byte_format',
            'char *matrix'])
  AO_Option =
    struct(['char   *key',
            'char   *value',
            'struct ao_option *next'])


  # 空の構造体は、voidのaliasとして定義する
  typealias('ao_functions', 'void')
  typealias('ao_device', 'void')


  # 未定義の型uint_32を、
  # unsignd intのaliasとして定義する
  typealias('uint_32', 'unsigned int')


  # 関数を定義する
  # (仮引数は型のみ記入する)

  # /* --- Functions --- */
  # /* library setup/teardown */
  # void ao_initialize(void);
  extern('void ao_initialize(void)')

  # void ao_shutdown(void);
  extern('void ao_shutdown(void)')

  # /* device setup/playback/teardown */
  # int   ao_append_global_option(const char *key,
  #                               const char *value);
  extern('int ao_append_global_option(const char *, const char *)')

  # int          ao_append_option(ao_option **options,
  #                               const char *key,
  #                               const char *value);
  extern('int ao_append_option(ao_option **, const char *, const char *)')

  # void          ao_free_options(ao_option *options);
  extern('void ao_free_options(ao_option *)')

  # ao_device*       ao_open_live(int driver_id,
  #                               ao_sample_format *format,
  #                               ao_option *option);
  extern('ao_device * ao_open_live(int, ao_sample_format *, ao_option *)')

  # ao_device*       ao_open_file(int driver_id,
  #                               const char *filename,
  #                               int overwrite,
  #                               ao_sample_format *format,
  #                               ao_option *option);
  extern(' ao_device* ao_open_file(int, const char *, int, ao_sample_format *, ao_option *)')

  # int                   ao_play(ao_device *device,
  #                               char *output_samples,
  #                               uint_32 num_bytes);
  extern('int ao_play(ao_device *, char *, uint_32)')

  # int                  ao_close(ao_device *device);
  extern('int ao_close(ao_device *)')

  # /* driver information */
  # int              ao_driver_id(const char *short_name);
  extern('int ao_driver_id(const char *)')

  # int      ao_default_driver_id(void);
  extern('int ao_default_driver_id(void)')

  # ao_info       *ao_driver_info(int driver_id);
  extern('ao_info *ao_driver_info(int)')

  # ao_info **ao_driver_info_list(int *driver_count);
  extern('ao_info **ao_driver_info_list(int *)')

  # char       *ao_file_extension(int driver_id);
  # extern('char *ao_file_extension(int)')

  # /* miscellaneous */
  # int          ao_is_big_endian(void);
  extern('int ao_is_big_endian(void)')
end

比較してみると分かるが、ao/ao.hで定義されているものをruby/dlに合わせ 機械的に定義し直しているだけである。

実際に使用する

作成したAOモジュールを実際に使用してみる。この時、関数の戻り値等はC言語で使用する際と同様に 適切に処理しなければならない。またerrnoの値を取得するには、 DL::CFunc.last_errorを用いる。

puts 'initialize'
AO.ao_initialize

puts 'setup default driver id'
drv = AO.ao_default_driver_id
if drv < 0
  puts 'usable audio output device is not found.'
  exit 1
end

puts 'setup sample format structure'
format = AO::AO_Sample_Format.malloc
format.bits     = 16
format.rate     = 44100
format.channels = 2
format.byte_format = AO::AO_FMT_LITTLE
format.matrix   = nil

puts 'open live device'
dev = AO.ao_open_live(drv, format, nil)
unless dev
  printf("errno: %d\n", DL::CFunc.last_error)
  exit DL::CFunc.last_error
end

puts 'play'
ARGV.each{|argv|
  if File.file?(argv)
    File.open(argv){|f|
      while buf = f.read(4096)
        if AO.ao_play(dev, buf, buf.size) == 0
          puts 'ao_play() failure.'
          exit 1
        end
      end
    }
  end
}

puts 'close'
if AO.ao_close(dev) == 0
  puts 'ao_close() failure.'
end

puts 'shutdown'
AO.ao_shutdown

あとは、作成したスクリプトを、引数に16bit・44.1KHz・2ch・Little Endianな音声の RAWファイルへのパスを付けて実行すれば、それを再生してくれる筈である。 WAVファイルはヘッダがあるため先頭に少々ノイズが入るが、再生することはできる。

参考文献

・xiph.org - libao
http://www.xiph.org/ao/

・Ruby 1.9.2 Reference Manual - dl
http://rurema.clear-code.com/1.9.2/library/dl.html

2011年4月29日金曜日

ruby 1.9.*でURL短縮サービスを作る

rubyでは、URL短縮サービスをWebサーバごと、しかも簡単に作ってしまうことができる。 その上、必要なライブラリはruby1.9.*標準添付ライブラリのみ。

ここでは、以下の機能を実装してみる。
  • /regのクエリにURLを渡しGETすると、元のURLとJSONで短縮したURLを返す
  • 短縮したURLはgdbmのデータベースに保存し、サーバ再起動後も参照できるものにする
  • 短縮にはMD5ハッシュを用いる(このままでは長いがとりあえず)

ソースコードは以下のとおり。
#!/usr/bin/env ruby1.9.2
#-*- coding: utf-8 -*-

require 'webrick'
require 'uri'
require 'gdbm'
require 'json'
require 'digest/md5'

# このサーバのURLを指定
BASE_URL = 'http://localhost:3939/'
# 短縮URLのデータベースファイル
db = GDBM.new('url.db')

# Webサーバを作成
sv = WEBrick::HTTPServer.new(:Port => URI.parse(BASE_URL).port)
trap(:INT){sv.stop}

# 短縮されたURLを受け取り、転送するServletを/にmount
sv.mount_proc('/'){|req, res|
  digest = req.path.gsub(/^\//, '')
  if digest =~ /^[0-9a-z]+$/i &&
      url = db[digest]
    res.set_redirect(WEBrick::HTTPStatus::Found, url)
  else
    res.body = 'unknown digest'
  end
}

# URLを短縮してJSONで返すServletを/regにmount
sv.mount_proc('/reg'){|req, res|
  url    = req.query['url']
  if url =~ URI.regexp && 
      URI.parse(url).scheme =~ /^(https?|ftp)$/
    digest = Digest::MD5.hexdigest(url)
    db[digest] = url
    res.body   = JSON.dump({'digest'=> digest,
                             'turl' => BASE_URL + digest,
                             'url'  => url})
  else
    res.body = 'bad url'
  end
}

# サーバを開始
sv.start
これだけでOK。
http://localhost:3939/add?url=http://hachune.net/でhttp://hachune.net/を短縮URLサービスに登録し、返されたhttp://localhost:3939/fd9f9871bca66e8b49a919138982c9c1にアクセスすると、http://hachune.net/に転送される。
Apacheの用意も新規ライブラリの導入も要らず、とってもお手軽。
あとは、MD5のままだとどう見ても長すぎるので、可能な限り衝突が起きにくくかつ文字列の長さが短くなる関数を作る必要があるが、まぁその辺は適当に。

2011年4月21日木曜日

debianやubuntuでmikutterビルド環境を作る

あまりに何度もmikutterビルド環境を構築しているので、そろそろ纏めてみる。

ベースシステムを構築

/var/rootにビルド用のubuntu nattyをdebootstrapにて用意する。 この中で全ての作業を行うことになるので、バグがあったり操作ミスしたりしてもシステム全体に被害は及びにくくなる。また、それぞれ違うディストリビューションやバージョンを用いることもできる(debian sidやubuntu lucidなど)。
ホストがamd64な環境ならば、debootstrapにオプション--arch i386を追加することで、i386な環境を構築することも可能。
# aptitude install debootstrap
# mkdir /var/root
# cd /var/root
# debootstrap natty ./natty

基本システムをmikutterビルド用にセットアップ

新たに作ったubuntu nattyの中にchrootする。 chroot後は、必要なファイルやパッケージを、通常のシステム同様にapt等を用いて準備する。
# chroot /var/root/natty /bin/bash
# apt-get update
# apt-get install \
gcc make  pkg-config subversion libnotify-bin wget \
libgtk2.0-dev libsqlite3-dev libssl-dev zlib1g-dev
もしsysやprocが見つけられないことが原因でエラーになるようであれば、 chrootから一度抜けだしてマウントしておく。
必要以上にマウントすると、なんらかのミスでとんでもないことになる可能性もあるので注意。
# mount -t proc proc ./natty/proc
# mount -o bind /dev ./natty/dev
# mount -t devpts devpts ./natty/dev/pts
# mount -t sysfs sysfs ./natty/sys

Rubyをビルド

そしてchroot内でruby1.9.2をビルドし、必要なrubyパッケージをgemで追加する。
# cd /usr/src
# wget ftp://ftp.ruby-lang.org/pub/ruby/ruby-1.9.2-p180.tar.gz
# tar xzf ruby-1.9.2-p180.tar.gz
# cd ruby-1.9.2-p180
# ./configure --prefix=/usr/ruby192 --program-suffix=192
# make && make install
# /usr/ruby192/bin/gem192 install ruby-hmac gtk2 sqlite3

mikutterをcheckout

最後にmikutterをcheckoutすれば、mikutterビルド環境のできあがり。
# cd /usr/src
# svn co svn://mikutter.hachune.net/mikutter/trunk/ mikutter
あとはsshのX転送やvnc等などを利用して、mikutterが動くかどうか試す。
おそらく日本語がまともに表示されずガッカリすることになるので、
# apt-get install ttf-vlgothic
などとしておくとよいかもしれない。