是非に及ばず

プログラミングに関する話題などを書いていきます(主にRailsやAndroidアプリ開発について)

Node.jsをCentOS5.xにインストールする手順

node.jsをCentOS5.xにインストールする手順のメモ。

検証環境と前提

CentOS 5.8 64bit
Node.js 0.98
Python 2.7.3

インストール手順

最新のPythonをインストール

CentOS 5.8の標準で入るPyhtonのバージョンは、2.4.3となっているが、 Node.jsのビルドでは、2.6以上が必要なのでそのままではうまく行かない。
そこで、Python公式サイトから最新のソースをダウンロードしてビルドする事にする。
ただし、/usr/localなど標準で参照されるパスに入れてしまうと、CentOSの他のツールなどと競合して問題になる。 そこで、$HOME/local以下にインストールする事で影響が出ないようにする。

$ mkdir -p ~/download
$ mkdir -p ~/build
$ cd ~/download
$ wget http://www.python.org/ftp/python/2.7.3/Python-2.7.3.tgz
$ cd ../build/
$ tar xvzf ../download/Python-2.7.3.tgz
$ cd Python-2.7.3
$ sudo yum install bzip2-devel -y
$ ./configure --prefix=$HOME/local/python-2.7.3
$ make
$ make install
$ ln -s $HOME/local/python-2.7.3 $HOME/local/python

.bash_profileのPATHに$HOME/local/python/binを追加する。

$ source ~/.bash_profile
$ python -V
Python 2.7.3

Node.jsをインストール

$ cd ~/download
$ wget http://nodejs.org/dist/v0.8.9/node-v0.8.9.tar.gz
$ cd ../build
$ tar xvzf ../download/node-v0.8.9.tar.gz
$ cd node-v0.8.9/
$ ./configure --prefix=$HOME/local/node-v0.8.9
$ make
$ make install

$HOME/local/node-v0.8.9/binをPATHに追加

$ source ~/.bash_profile
$ node -v
v0.8.9

以上で導入は完了。
jsdomはほとんどの場合で必要になるので、ついでに入れておく。

$ npm install -g jsdom

さらにTypeScriptも入れておく。
npmのパッケージとして提供されているのですぐに利用できる。

$ npm install -g typescript

[Subversion] Subversionリボジトリのバックアップ方法についてまとめてみた

Gitが流行っている中、Subversionリポジトリのバックアップ方法をまとめるという残念なエントリ。
でも、正直自分が使うだけならGitでも構わないがメンバーが多くなってくると、
Gitは説明が面倒だし、Subversionのほうが楽だよなぁ・・・

結論

結論から言うと、svnadmin dumpとsvnsyncの両方を使う。
svnadmin dumpで差分バックアップをするという方法もあるようだけど、
差分だとうまく元に戻せる気がしない。
バックアップの目的は、任意の時点の状態に復元できる事のはず。
それなのに正しく復元できないとしたら、バックアップなど意味がないのだ。
だから、復元が簡単なフルバックアップをするほうがいい。
しかし、フルバックアップでは時間が非常にかかるので、1日に1回とか
そのくらいの頻度にしたほうがいい。
でも、それではデータの損失が大きい・・・
そこで、差分バックアップ的な感じで最新のリポジトリの状態を作れるsvnsyncを併用する。
フルバックアップには、svnadmin hotcopyを使う方法もあるようだけど、
アーキテクチャ依存なので微妙な感じ。
svnadmin dumpにはリビジョン欠けが起きる可能性があるという情報を目にしたが、
そう書いてる人たちは自分で経験した事もなくただネットで見たから・・
という事で書いている事が多いようだ。
そういう事は自分で経験してから書くもんだと思う。
なので、自分が経験するまではsvnadmin dumpを使います。

svndumpでフルバックアップ

※ここでは、リポジトリのパスを/var/svn/sampleとして説明する。

バックアップ方法

svnadmin dump /var/svn/sample > sample.`date +%Y%m%d%H%M%S`.svn.dump

復元方法

まず、現在のリポジトリを移動し、同じパスに空の状態のリポジトリを作成する

$ sudo su -
# mv /var/svn/sample /var/svn/sample.old
# svnadmin create /var/svn/sample
# chown -R apache:apache /var/svn/sample

ダンプファイルから復元する

# svnadmin load /var/svn/sample < sample.`date +%Y%m%d%H%M%S`.svn.dump

svnsync

準備として、バックアップ先のサーバで分かりやすいように同じパスに空のリボジトリを作り、
hookスクリプトにsvnsyncy用のファイルを作る。
ここでは、コピペでファイルを作れるようにechoの内容をファイルにリダイレクトしている。
echoの部分は、1行で出来るはずだけど、よく分からんので2行に分けているのがちょっとかっこ悪い・・・

$ sudo su -
# rep=/var/svn/sample
# svnadmin create $rep
# echo '#!/bin/sh' > $rep/hooks/pre-revprop-change
# echo "exit 0" >> $rep/hooks/pre-revprop-change
# chmod +x $rep/hooks/pre-revprop-change
# chown -R apache:apache $rep

svnsyncの初期化。なお、リモートのリポジトリhttps://example.com/svn/sample として説明する。

# svnsync init file:///var/svn/sample https://example.com/svn/sample --source-username \
ユーザ名 --source-password パスワード

うまく行ったら、次回からは以下のコマンドだけで良い。
これは差分だけ取得して来るので時間はかからないから、例えば5分ごとにcronで動かすとかやっても問題ないと思う。

# svnsync sync file:///var/svn/sample --source-username ユーザ名 \
--source-password パスワード

オブジェクト指向を理解したければRubyを使え!

普通の構造化プログラマーがオブジェクト指向の存在意義を理解するコツ
を読んで脊髄反射してみる。
自分自身がRuby信者(笑)なので、Rubyをおすすめするわけなんだけども、中途半端にオブジェクト指向機能が入っている言語で学習したところで構造化プログラミングから抜け出せないんじゃないかなと思う。
環境が人を作るという事もあるので、まずは全てがオブジェクトであるRubyでしばらくプログラムしていれば、オブジェクトの世界で自分がどう歩くべきか自然と分かるんじゃないかな。
なにしろ、Rubyの世界にはオブジェクトしかないわけで、int型とかなくて1とか2とかの数字は実はFixnumクラスのインスタンスだったりする。だから、1.to_sだとか、1.absなんてのが実行できるし、1.methodsで1が持つメソッド一覧を取得できたりする。
なにそれ、すげー!と感じたら、あなたは分かっている、または分かりかけている人です。
プログラムって論理的なイメージがあるけど、かなり感覚的だと思う。
人間のやる事だし。

例えば、構造化プログラミングだと、1という数字の絶対値を求める処理は、

abs(1);  # 絶対値を求める関数に1を引数として渡して、結果を返してもらう

という感じだけど、オブジェクト指向なら

1.abs  # 1に対して、絶対値を聞く(教えてもらう)

だろ?
という感じなんだけど、この感覚をなるほど!と思えるかどうか。
それがオブジェクトを理解しているかどうかなんだろうな。
オブジェクトの事はオブジェクトに聞けという事でね。
1に関する情報は、1が知ってるだろ?
だから1の絶対値を求める処理(メソッド)は1の方に実装するんだって感じ。

文章力がないため、うまく説明できないが、とりあえず自分の中ではこういう感じなのだ。

Androidでテキストをマーキー表示するカスタムビューの作り方

前置き

Androidアプリでテキストをマーキー表示したいって事あるよね?
でも、そんなに簡単には出来なかったりする(汗
ここでいうマーキーの動作は、HTMLのmarqueeタグのイメージ。
標準のTextViewのandroid:ellipsize="marquee"で実現できるかな?と思ったら全然ダメだった。

TextViewのダメなところ
  • テキストが短いとスクロールしない
  • 文字のスクロール速度を自由に調整できない
  • フォーカスが当たっていないとスクロールしない(つまり、複数のTextViewで同時にマーキー表示とか無理)

以上の欠点により、TextViewでは自分のやりたい事は出来ない事が分かった。
次に、アニメーション機能でTextViewを画面の右から左へ移動させたらどうだろうと考えて試してみた。
見事に玉砕したけど(後述

アニメーション(TranslateAnimation)でTextViewを移動するのがダメな理由
  • TextViewは画面サイズに収まらない長いテキストを設定した場合、自動的に画面サイズに収まる長さにテキストがカットされてしまう仕様となっている

この時点で終了(笑
そこで、最後の切り札。カスタムビューですよ!(無事解決)
Androidアプリの経験が浅い事もあり、マーキー表示のためだけに1週間くらい試行錯誤していたorz
でも、ようやく解決してうれしいので、ブログにまとめておく。
結論からいうと、独自のビューを定義してonDraw()で好きなようにしろって事なんだけど、
たどり着くまでが長かった。分かってしまえば、簡単なんだけどね(当たり前)

ソース

マーキー表示用のビューという事で、シンプルにMarqueeViewという名前にした。
カスタムビューの作り方については、ここが分かりやすい。
また、ここに載せているMaqrueeViewの画面サイズの取得とか初期化のやり方は
リファレンスのLabelViewの例を参考にした。

res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- name : marquee view class name -->
    <declare-styleable name="MarqueeView">
        <attr name="text" format="string" />
        <attr name="textColor" format="color" />
        <attr name="background" format="color" />
        <attr name="textSize" format="dimension" />
        <attr name="repeatLimit" format="integer" />
        <attr name="textMoveSpeed" format="integer" />
    </declare-styleable>
</resources>
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/LinearLayout"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent"
    android:background="@color/white"
    android:orientation="vertical"
>
     <net.easyjp.android.widget.MarqueeView
        xmlns:app="http://schemas.android.com/apk/res/net.easyjp.marquee_view"
        android:id="@+id/marqueeView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:text="test"
        app:textSize="25sp"
        app:textColor="#FFFFFFFF"
        app:background="#FF000000"
        app:textMoveSpeed="15"
        app:repeatLimit="3"
    />
    
    <net.easyjp.android.widget.MarqueeView
        xmlns:marquee="http://schemas.android.com/apk/res/net.easyjp.marquee_view"
        android:id="@+id/marqueeView2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingTop="30dp"
        marquee:text="test"
        marquee:textSize="30sp"
        marquee:textColor="#FFFFFFFF"
        marquee:background="#FFFF0000"
        marquee:textMoveSpeed="15"
        marquee:repeatLimit="1"
    />
</LinearLayout>
MainActivity.java
package net.easyjp.marquee_view;

import net.easyjp.android.widget.MarqueeView;
import net.easyjp.marquee_view.R;
import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        String text1 = "マーキーのテスト(wrap_content)";
        MarqueeView marqueeView1 = (MarqueeView)findViewById(R.id.marqueeView1);
        marqueeView1.setText(text1);
        marqueeView1.startMarquee();

        String text2 = "少し長めのテキスト(fill_parent)。あいうえおかきくけこさしすせそ123456790";
        MarqueeView marqueeView2 = (MarqueeView)findViewById(R.id.marqueeView2);
        marqueeView2.setText(text2);
        marqueeView2.startMarquee();
    }
}
MarqueeView.java
package net.easyjp.android.widget;

/**
============================================
  使用方法およびドキュメント
============================================

(1) res/values/attrs.xmlに以下を記述
ここでMarqueeViewにXMLから設定できるパラメータ名とデータの形式を定義している
実際にこの値を使用しているかどうかは、コンストラクタの
MarqueeView(Context context, AttributeSet attrs)の内容を見れば分かる
---------------------------------
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- name : marquee view class name -->
    <declare-styleable name="MarqueeView">
        <attr name="text" format="string" />
        <attr name="textColor" format="color" />
        <attr name="background" format="color" />
        <attr name="textSize" format="dimension" />
        <attr name="repeatLimit" format="integer" />
        <attr name="textMoveSpeed" format="integer" />
    </declare-styleable>
</resources>
---------------------------------

(2) res/layout/xxx.xml
使用したいアクティビティのレイアウトファイルに以下を記述。
xmlns:app=の行は必須。以下の例のnet.easyjp.marquee_viewは
使用するプロジェクトのパッケージ名になるので、注意する事。
またxmlns:appの部分は任意で良い。xmlns:marquee=""とした場合は、
各属性の指定もmarquee:textSize="25sp"に置き換える必要がある
---------------------------------
<net.easyjp.android.widget.MarqueeView
    xmlns:app="http://schemas.android.com/apk/res/net.easyjp.marquee_view"
    android:id="@+id/marqueeView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:text="test"
    app:textSize="25sp"
    app:textColor="#FFFFFFFF"
    app:background="#FF000000"
    app:textMoveSpeed="15"
    app:repeatLimit="3"
/>
---------------------------------

(3) アクティビティ内でMarqueeViewを取得し、startMarquee()でマーキー開始
---------------------------------
MarqueeView marqueeView = (MarqueeView)findViewById(R.id.marqueeView);
marqueeView.setText("マーキーさせる文字列を設定");
marqueeView.startMarquee();
---------------------------------

*/

import net.easyjp.marquee_view.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.util.Log;


/**
 * テキストのマーキー表示を行うカスタムビュー
 * 
 */
public class MarqueeView extends View {
    private Paint mTextPaint;
    private String mText;
    private int mAscent;
    
    private int mRepeatCount;      // リピートした回数
    private int mRepeatLimit;      // 最大リピート回数
    private int mCurrentX;         // 現在のテキストの位置
    private int mTextMoveSpeed;    // 1フレームで動く距離
    private Thread mThread = null; // テキストを移動させるスレッド

    // マーキー表示処理(テキストの移動+表示)
    private Runnable runnable = new Runnable() {
        public void run() {
            // 左端と判断するX座標
            int lastX = getLastX();

            while(mRepeatCount < mRepeatLimit) {
                mCurrentX = getMarqueeStartX(); // テキスト位置を戻す

                long beforeTime = System.currentTimeMillis();
                long afterTime = beforeTime;
                int fps = 30;
                long frameTime = 1000 / fps;

                // 1回のマーキー処理
                while(true) {               
                    // 左端まで到達したらリピート1回としてカウント
                    if(mCurrentX <= lastX) {
                        mRepeatCount += 1;
                        break;
                    }

                    mCurrentX -= mTextMoveSpeed;
                    postInvalidate();
                
                    afterTime = System.currentTimeMillis();
                    long pastTime = afterTime - beforeTime;
                    
                    long sleepTime = frameTime - pastTime;
                    
                    if(sleepTime > 0) {
                        try {
                            Thread.sleep(sleepTime);
                        }catch(Exception e){}
                    }
                    beforeTime = System.currentTimeMillis();
                }
                
            }
        }
    };
    
    /**
     * マーキー処理を停止する
     */
    public void clearMarquee() {
        mCurrentX = getMarqueeStartX();
        mRepeatCount = 0;
        mThread = null;
    }

    /**
     * マーキー処理を開始する
     */
    public void startMarquee() {
        clearMarquee();
        mThread = new Thread(runnable);
        mThread.start();
    }

    /**
     * コンストラクタ(XMLを使用しない場合)
     * 
     * @param context
     */
    public MarqueeView(Context context) {
        super(context);
        initMarqueeView();
    }

    /**
     * コンストラクタ(XMLを使用する場合)
     * 
     * @param context
     * @param attrs XMLで定義した属性
     */
    public MarqueeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initMarqueeView();

        // XMLから属性を取得
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MarqueeView);

        String s = a.getString(R.styleable.MarqueeView_text);
        if(s != null) {
            setText(s);
        }

        int textSize = a.getDimensionPixelOffset(R.styleable.MarqueeView_textSize, 0);
        if (textSize > 0) {
            setTextSize(textSize);
        }

        setTextColor(a.getColor(R.styleable.MarqueeView_textColor, 0xFFFFFFFF));
        setBackgroundColor(a.getColor(R.styleable.MarqueeView_background, 0xFF000000));
        setRepeatLimit(a.getInteger(R.styleable.MarqueeView_repeatLimit, 1));
        setTextMoveSpeed(a.getInteger(R.styleable.MarqueeView_textMoveSpeed, 5));

        a.recycle();
    }

    /**
     * 初期化処理
     * このメソッドは必ずコンストラクタ内で呼び出す必要がある
     */
    private final void initMarqueeView() {
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(16);
        mTextPaint.setColor(0xFFFFFFFF);
        mTextMoveSpeed = 5;
        mRepeatCount = 0;
        setRepeatLimit(1);
        setText("");
        setPadding(0, 0, 0, 0);
        setBackgroundColor(0xFF000000);
    }
    
    /**
     * リピート回数を設定する
     * @param repeatLimit
     */
    public void setRepeatLimit(int repeatLimit) {
        if(repeatLimit > 0) {
            mRepeatLimit = repeatLimit;
        }else {
            mRepeatLimit = 1;
        }
    }
    
    /**
     * テキストの移動速度(px)を設定する
     * 
     * @param speed 移動速度(ピクセルで指定)
     */
    public void setTextMoveSpeed(int speed) {
        if(speed > 0) {
            mTextMoveSpeed = speed;
        }
    }

    /**
     * テキストを設定する
     * 
     * @param text 表示するテキスト
     */
    public void setText(String text) {
        mText = text;
        requestLayout();
        invalidate();
    }

    /**
     * テキストサイズを設定する
     * 
     * @param size フォントサイズ
     */
    public void setTextSize(int size) {
        mTextPaint.setTextSize(size);
        requestLayout();
        invalidate();
    }

    /**
     * テキストカラーを設定する
     * 
     * @param color
     */
    public void setTextColor(int color) {
        mTextPaint.setColor(color);
        invalidate();
    }

    /**
     * ビューのサイズを設定する
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(
            measureWidth(widthMeasureSpec),
            measureHeight(heightMeasureSpec)
        );
    }

    /**
     * ビューの幅を返す
     */
    private int measureWidth(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            // We were told how big to be
            result = specSize;
        } else {
            // Measure the text
            result = (int) mTextPaint.measureText(mText) + getPaddingLeft()
                    + getPaddingRight();
            if (specMode == MeasureSpec.AT_MOST) {
                // Respect AT_MOST value if that was what is called for by measureSpec
                result = Math.min(result, specSize);
            }
        }

        return result;
    }

    /**
     * ビューの高さを返す
     */
    private int measureHeight(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        mAscent = (int) mTextPaint.ascent();
        if (specMode == MeasureSpec.EXACTLY) {
            // We were told how big to be
            result = specSize;
        } else {
            // Measure the text (beware: ascent is a negative number)
            result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop()
                    + getPaddingBottom();
            if (specMode == MeasureSpec.AT_MOST) {
                // Respect AT_MOST value if that was what is called for by measureSpec
                result = Math.min(result, specSize);
            }
        }
        return result;
    }
    
    /**
     * マーキーの開始位置のX座標を返す
     */
    private int getMarqueeStartX() {
        WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        int measureText = (int)mTextPaint.measureText(mText);
        int measureWidth = getMeasuredWidth();

        if(display.getWidth() == measureWidth) {
            return measureWidth;
        }else if(measureText > display.getWidth()) {
            // テキストが画面サイズを超える場合
            return display.getWidth();
        }else if(measureWidth > measureText) {
            return measureWidth;
        }else {
            return measureText;
        }
    }
    
    /**
     * 左端と判定するX座標
     * @return
     */
    private int getLastX() {
        WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
    
        int measureText = (int)mTextPaint.measureText(mText);
        int measureWidth = getMeasuredWidth();

        if(measureText >= display.getWidth()) {
            // テキストが画面サイズを超える場合
            return -measureText;
        }else if(measureWidth > measureText) {
            // テキストの幅がビューのサイズより小さい
            return -measureWidth;
        }else {
            return -measureText;
        }
    }
    
    /**
     * 描画処理
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        
        int x = getPaddingLeft() + mCurrentX;
        int y = getPaddingTop() - mAscent;
        canvas.drawText(mText, x, y, mTextPaint);
    }
}

Rubyで任意のテキストにふりがなを付ける(Yahooのテキスト解析/ルビ振りを利用する)

概要

ここでは、RubyYahooデベロッパーネットワーク/テキスト解析>ルビ振りAPIを利用して任意の文字列にふりがなを付ける方法を示す。
やる事は非常に単純で以下の3ステップだけなので、非常にお手軽である。

  1. APIにアクセスするためのリクエストURLを作成
  2. Net::HTTPを使ってURLにアクセスし、XMLを取得(UTF-8で返ってくる)
  3. NokogiriでXMLをパースし、ふりがなの部分を取得する

環境

Ruby 1.8.7
Nokogiri 1.4.4
CentOS 5.x
スクリプト、コンソールともにUTF-8を前提とする

準備

  • nokogiriが入っていなければ、gem install nokogiriでインストールする
$ sudo gem install nokogiri

そこからYahooアカウントでログインして登録し、アプリケーションIDを取得する。
なお、今回のサンプルは自分のIDをそのまま載せているので、Yahooに登録しなくても動作させる事が出来る

サンプル

yahoo_api.rb
require 'jcode'
require 'rubygems'
require 'nokogiri'
require 'net/http'
require 'cgi'
module YahooApi
  APP_ID = "6acb699c6333bdb1fd5e189899359935" # アプリケーションID

  # テキスト解析/ルビ振りAPI
  # http://developer.yahoo.co.jp/webapi/jlp/furigana/v1/furigana.html
  class Furigana
    BASE_URL = "http://jlp.yahooapis.jp/FuriganaService/V1/furigana"

    # 指定したテキストのふりがなを返す
    #
    # YahooApi::Furigana.new.parse("東京")
    # => "とうきょう"
    def parse(text, opts={})
      url = get_request_url(text, opts)
      response = nil
      3.times {
        response = get_response(url)
        if response.kind_of?(Net::HTTPOK)
          break
        end
        sleep 0.5
      }
      return nil unless response

      doc = Nokogiri::XML.parse(response.body, nil, "UTF-8")
      return doc.search("Furigana").text
    end

    def get_request_url(text, opts={})
      params = {
        "appid" => APP_ID,
        "sentence" => text,
        "grade" => opts[:grade]
      }
      tmp = []
      params.keys.sort.each do |key|
        next if params[key].nil?
        tmp << "#{key}=#{CGI.escape(params[key])}"
      end
      query = tmp.join('&')
      return "#{BASE_URL}?#{query}"
    end

    def get_response(request_url)
      begin
        headers = {
          'User-Agent' => 'Ruby/WebClient'
        }
        uri = URI.parse(request_url)
        client = Net::HTTP.new(uri.host, uri.port)
        client.open_timeout = 1
        client.read_timeout = 2
        client.start{|http|
          response = http.get(uri.request_uri, headers)
          return response
        }
      rescue Exception => e
      end
      return nil
    end

  end # end of Furigana

end
furigana.rb
#!/opt/ruby/bin/ruby -Ku
require 'yahoo_api'

if ARGV.size == 0
  puts "Usage: #{$0} {text}"
  exit 1
end

puts YahooApi::Furigana.new.parse(ARGV[0])

実行方法

$ chmod +x furigana.rb
$ ./furigana.rb 東京都千代田区日本橋
とうきょうとちよだくにほんばし

Androidのエミュレータ起動時にdisconnected! Cancellingというメッセージが出て起動しない件

EclipseからAndroidプロジェクトを実行またはデバッグを行うと、エミュレータが起動するのだが、
デバッグボタンを押して、エミュレータを起動すると、「disconnected! Cancelling」というメッセージが表示され、接続が切断されてしまう。
ネットではいくつか解決策が見付かるのだが、どれも自分の環境ではうまく行かなかった。
いろいろ試すうちに、解決したのでメモしておく。

AVDを作成する時にいつもAndroid 1.6で作っていたのだが、これを2.2で作ってみたら解決した(謎)。

しかし、これだと1.6を対象としたアプリの開発ができないので、根本的な解決には至っていない。
しかも、2.2のエミュレータ起動は1.6に比べてかなり遅い。

追記
なんか知らないが、いつの間にか直った。1.6でも問題なく動作するようになった。
やった事はVMWareのサービスを停止させた事くらい。
PCの動作が重いと、この症状が出やすい?

Railsのnamed_scopeをまとめて実行するサンプル(Ruby1.9.2 + Rails3版)

説明

前回のエントリーと同じ内容なので、ポイントだけ書く。
Ruby1.9.2+Rails3では前回のサンプルでは動かない部分があるので、それを動くようにする。
具体的には、この部分が動かない。

scope.call p, *args

Ruby1.8.7Rails.2.3では動作するが、Ruby1.9.2 + Rails3ではここでエラーとなる。
理由は、よく分からないがRails3のArelの関係ような気がする。
ここでは、単純なメソッド呼び出しの形に変更して対応してみる。

named_scopeをまとめて実行するメソッド(Ruby1.9.2 + Rail3.0.0で動作確認)

class Member < ActiveRecord::Base
  belongs_to :customer
  named_scope by_email, lambda{|email| {:conditions => ['email = ?', email]} }
  named_scope active,  {:conditions => ['deleted = ?', false]} # 有効
  named_scope deleted, {:conditions => ['deleted = ?', true]}  # 無効(論理削除)

  def self.search(params = {})
    exec_scopes = []  # [実行するスコープ名(シンボル), 引数] を格納する配列

    if params[:deleted] == true
      exec_scopes << [:deleted]
    else
      exec_scopes << [:active]
    end

    if params[:email]
      exec_scopes << [:by_email, params[:email]]
    end
  
    # 実行するスコープがない場合
    if exec_scopes.size == 0
      return Member.scoped
    end

    # named_scopeをまとめて実行
    exec_scopes.reverse.inject(self){|obj, exec_scope|
      scope = exec_scope.shift
      args = exec_scope

      if args.size > 0
        obj = obj.send(scope, *args)
      else
        obj = obj.send(scope)
      end
    }
  end
end