2009년 12월 23일 수요일

시험 아이폰


참 좋네요
아이폰에서 블로깅 해 봅니다




사진도 잘 입력됩니다

2009년 1월 27일 화요일

몇몇 WIKI 소프트웨어를 비교하다. 2009년초 기준

몇몇 WIKI 소프트웨어를 비교하다. 2009년초 기준.

위로부터 추천 순이다.


* DokuWiki ( http://www.dokuwiki.org/dokuwiki )
- DB를 사용하지 않고 설치가 아주 간단하다.
- PlugIn도 다양하고 장착이 편하다. http://www.dokuwiki.org/plugins
- 테마/스킨도 다양하고 예쁘다. http://www.dokuwiki.org/Template
- 다국어 지원도 아주 잘 돼 있다.
日本語対応 예) http://www.higuchi.com/dokuwiki/dokuwiki:localize
- 글쓰기 및 편집 기능이 아주 편해서 컨텐츠의 작성이 용이하다.

* TikiWiki ( http://www.tikiwiki.org )
- DB를 필요로 한다. 그만큼 대용량 위키에도 반응이 좋다.
- PlugIn도 다양하다. CMS 기능도 보유하고 있다.
- 테마/스킨도 다양하고 예쁘다. http://themes.tikiwiki.org
- 기능 면에서는 가장 많은 기능을 보유하고 있다.

* MediaWiki ( http://www.mediawiki.org/wiki/MediaWiki )
- 예쁘기로는 둘째라면 서러운 위키다.
- DB를 사용한다. 무조건 공개 베이스인 위키인지라 ACL가 약하다.

* PukiWiki ( http://pukiwiki.sourceforge.jp )
- 일본산 위키로 개발이 2006년경 중단됐다. 그래서 상대적으로 기능이 약하다.
그래도 일본에서는 네임밸류가 가장 높은 위키다.
- 테마/스킨이 약했는데 최근에는 다양한 스킨을 지원한다.
http://pukiwiki.sourceforge.jp/?%E8%87%AA%E4%BD%9C%E3%82%B9%E3%82%AD%E3%83%B3

* 기타 WackoWiki (DB사용, 다국어화 부족), PMWiki (스킨 부족) 등이
있었지만 위키매트릭스를 참고해서 상위의 것만 간단히 비교한다.



참고) 위키 소프트웨어를 비교한 사이트를 안내하자면,
위키매트릭스가 제일 좋다. 아래는 몇몇 위키를 비교한 테이블이다.
- http://www.wikimatrix.org/compare/DokuWiki+MediaWiki+PmWiki+PukiWiki+TikiWiki-CMS-

Groupware+WackoWiki

PHP 작은 팁 - 변수,배열, 객체를 구별해서 화면에 디버그 출력하는 함수

변수,배열, 객체를 구별해서 화면에 디버그 출력하는 함수다.


function debug3( $mixed ) {
if ( $mixed == NULL ) { echo "
debug3 value is NULL"; }
else if ( is_array( $mixed ) ) echo "
".print_r( $mixed, true )."</pre>";<br />    else if ( is_object($mixed ) )     echo "<br /><pre>".var_export( $mixed, true )."</pre>";<br />    else                               echo "<br /><pre>".$mixed."
";
}

8자리의 랜덤 문자열을 작성하는 함수 rand8()

8자리의 랜덤 문자열을 작성하는 함수 rand8()

특정 자리수의 랜덤한 문자열을 20만건 작성해야 할 일이 생겨서 급하게 만들어봤다.
제한 조건으로는 절대로 겹치면 안된다는 것.
겹치지 않는 조건을 충족하기 위해 MySQL까지 동원해서 Unique성을 체크했다.

의외로 넷 상의 많은 공개 함수들이 엉터리였다.
또한 문자열 속에 포함되어야 하는 문자, 포함안될 문자를 설정하는 기능도 약했다.
우연히 구한 잘 된 함수 rand8()을 기록해 둔다.

정말 아쉽게도, 원래 출처였던 외국사이트의 공개 주소를 잃어버린 것 같아 죄송하다.
아래에 8자리의 랜덤 문자열을 작성하는 함수 rand8()에 대한 예제이다.
상단부 소스는 실패한 여타 함수들도 함께 있다.


set_time_limit( 10000 );

function make_seed()
{
list($usec, $sec) = explode(' ', microtime());
return (float) (($sec + $usec ) * 100);
}

function VISION_TO_RANDOM
(
// autor Femi Hasani [www.vision.to]
$length=7, // string length
$uselower=1, // use lowercase letters
$useupper=1, // use uppercase letters
$usespecial=1, // use special characters
$usenumbers=1, // use numbers
$prefix=''
)
{

$key = $prefix;
// Seed random number generator
// mt_srand((double)microtime() * rand(1000000, 9999999));
mt_srand( make_seed()*100 );

$charset = "";
if ($uselower == 1) $charset .= "abcdefghjkmnpqrstuvwxyz";
// if ($useupper == 1) $charset .= "ABCDEFGHJKMNPQRSTUVWXYZ";
if ($usenumbers == 1) $charset .= "23456789";
// if ($usespecial == 1) $charset .= "~#$%^()_+-={}][";
while ($length > 0) {
$key .= $charset[rand(0, strlen($charset)-1)];
$length--;
}
return $key;
}


function range2($start, $end, $length)
{
$str_array = range($start, $end);
srand((double) microtime() * 1000000);
$text = '';
for($i = 0; $i < $length; $i++)
{
$text .= $str_array[rand(0, count($str_array) - 1)];
}
return $text;
}



function createRandomPassword() {
$chars = "abcdefghjkmnpqrstuvwxyz23456789";

srand((double)microtime()*1000000);

$i = 0;

$pass = '' ;
while ($i <= 7) {

$num = rand() % 33;
$tmp = substr($chars, $num, 1);
$pass = $pass . $tmp;
$i++;
}

if ( strlen( $pass) == 8 ) return $pass;
else false;
}

// SUCCESS!
function rand8() {
$len = 7;
$base='abcdefghjkmnpqrstuvwxyz23456789';
$max=strlen($base)-1;
$activatecode='';
mt_srand((double)microtime()*10000000); //// IMPORTANT!!!!! 10000000
while (strlen($activatecode)<$len+1)
$activatecode.=$base{mt_rand(0,$max)};

if ( strlen( $activatecode ) == 8 ) return $activatecode;
else false;
}


$conn = mysql_connect('localhost', 'www', 'www');
mysql_select_db( "test" );


while ( $ii < 10000000 ) {
// echo VISION_TO_RANDOM( 8 )."
";

// echo "insert into `serial8`(`sn`,`randvalue`) values ( NULL,'".VISION_TO_RANDOM( 8 )."');
";
// insert into `serial8`(`sn`,`randvalue`) values ( NULL,'8pawcvmh');

// $strSQL = "insert into `serial8`(`sn`,`randvalue`) values ( NULL,'".VISION_TO_RANDOM( 8 )."')";
$str = rand8();
if ( $str != false ) {
$strSQL = "insert into `serial8`(`sn`,`randvalue`) values ( NULL,'".$str."')";
}
// $strSQL = "insert into `serial8`(`sn`,`randvalue`) values ( NULL,'".range2( '2', 'z', 8 )."')";

$result = mysql_query( $strSQL );
if (!$result) {
// echo ('Invalid query: ' . mysql_error() . "
");
}
else
{
echo ('Inserted: ' . $strSQL . "
");
}

$ii++;
}

mysql_close( $conn );

/***
検証用SQL : randvalueフィールドはUnique条件
select count(1) from serial8 where length( randvalue )!=8
or randvalue like '%l%'
or randvalue like '%1%'
or randvalue like '%o%'
or randvalue like '%0%'
or randvalue like '%i%'
***/
?>



다음에 기회가 되면 이를 클래스화해서 공개하는 것도 좋을 것 같다.

우선 시간이 없어서 클래스화 했을 경우의 예제 소스를 기록해 둔다.


/**
* Uniqueな文字列をランダム生成するクラス
*
* PHP5.x用、PHP4.1.x系にも対応。
* このファイルはUTF-8フォーマット、TABの幅は4。
*
* @access public
* @create 2008/11/28
* @version v0.1
**/

/**********************************
************ 使用例 ************
**********************************

ini_set('memory_limit', '64M'); // 使用メモリの制限を伸ばす。(php.iniの設定が優先)
set_time_limit( 3600 ); // 実行時間を3600秒(1時間)まで伸ばす。
require_once "class.RandomUnique.php";

// 使用例 #1
$objRU = new RandomUnique(); // クラスのInstanceを生成。ランダム文字列のDefault桁数は8である。
$str = $objRU->getString( 10000 ); // 1万件のUniqueなランダム文字列を返す。文字列の各結果は改行(\n)で区別。
echo nl2br( $str );
$arr = $objRU->getArray( 200000 ); // 20万件のUniqueなランダム文字列を配列(以下'文字配列')で返す。

// 使用例 #2
$objRU2 = new RandomUnique( "abxy123789", 6 ); // クラスのInstanceを生成する際に「文字リスト」と「桁」を設定。
$arr2 = $objRU2->getArray( 10000000 ); // 1千万件のUniqueなランダム文字配列を返す。メモリ制限を越えるとエラーを配列で返す。

// 使用例 #3
$listChar = "abcdefghjkmnpqrstuvwxyz23456789"; // 「文字リスト」の準備。
$objRU3 = new RandomUnique( $listChar ); // クラスのInstanceを生成する際に「文字リスト」を設定。Default桁数は8である。
$objRU3->make( 30000 ); // 3万件のUniqueなランダム文字配列で生成する。既存のラン

ダム文字値は廃棄される。
$objRU3->save( "./output.txt" ); // ランダム文字列をファイル名output.txtで保存する。文字列の各結果は改行(\n)で区別。

// 使用例 #4
$objRU4 = new RandomUnique( null , 12 ); // ランダム文字列の桁数は12で設定。「文字リスト」がnullであればDefault値を使用。
$objRU4->setChar( "abcdfgxyz23456789!#%*@" ); // ランダム文字列の生成のため使う「文字リスト」を設定。Default値は a~z, 0~9。
$objRU4->setWidth( 10 ); // ランダム文字列を(12桁から)10桁で変更。最大ランダム数は「文字

リスト」の長さ^10件。
$objRU4->make( 40000 ); // 4万件のUniqueなランダム文字配列で生成する。既存のラン

ダム文字値は廃棄される。
$bUniq = $objRU4->isValid(); // 配列で生成されたランダム文字列がUnique性と桁数を満たすか検証(結果はtrue /

false)。
if ( !$bUniq ) var_dump($objRU4->getInvalid()); // ランダム文字列がUnique性を満たない場合、その原因である文字配列などを取得・表示。

**********************************/


class RandomUnique {


} // end of class RandomUnique

?>

Flash管理画面作成する際のCache問題解決方法

Flash管理画面作成する際のCache問題解決方法を共有します。


1.Flash-Player(例:動画)を呼ぶURL.phpの後ろにランダムParamを追加。
例) rotation-banner.php?293808237401984

2.Flash-Player(例:動画)を呼ぶURL.phpの本文に下記のタグを追加。







3.IEの場合、下記のScriptも追加。



배열 안에서 2중 정렬을 하려는 경우에 유효한 함수

배열 안에서 2중 정렬을 하려는 경우에 유효한 함수
를 소개한다.

http://jp2.php.net/manual/ja/function.array-multisort.php 로부터 인용.

------------

php a-t-the-r-a-t-e chir.ag
06-Jan-2006 07:10
Re: phu at kungphu, 19-Dec-2005 11:36

asort($test) will not let me specify which columns to sort ASC/DESC, NUMERIC/STRING etc.

I have data similar to what you specified. Now I want to sort $test by points DESC and name ASC. Here's my function that does it, based on suggestions on this page. It uses array_multisort (and hence acts just like it: preserving string-keys etc.)


function arrayColumnSort()
{
$n = func_num_args();
$ar = func_get_arg($n-1);
if(!is_array($ar))
return false;

for($i = 0; $i < $n-1; $i++)
$col[$i] = func_get_arg($i);

foreach($ar as $key => $val)
foreach($col as $kkey => $vval)
if(is_string($vval))
${"subar$kkey"}[$key] = $val[$vval];

$arv = array();
foreach($col as $key => $val)
$arv[] = (is_string($val) ? ${"subar$key"} : $val);
$arv[] = $ar;

call_user_func_array("array_multisort", $arv);
return $ar;
}

$test["pete"]['points']=1;
$test["pete"]['name']='Peter';

$test["mike"]['points']=5;
$test["mike"]['name']='Mike';

$test["zoo"]['points']=2;
$test["zoo"]['name']='John Zoo';

$test["ab"]['points']=2;
$test["ab"]['name']='John Ab';

$test1 = $test;

asort($test1);

$test2 = arrayColumnSort("points", SORT_DESC, SORT_NUMERIC, "name", SORT_ASC, SORT_STRING, $test);

print_r($test1); // asort
print_r($test2); // arrayColumnSort

?>



Output from asort:

Array
(
[pete] => Array
(
[points] => 1
[name] => Peter
)

[ab] => Array
(
[points] => 2
[name] => John Ab
)

[zoo] => Array
(
[points] => 2
[name] => John Zoo
)

[mike] => Array
(
[points] => 5
[name] => Mike
)

)

Output from arrayColumnSort:

Array
(
[mike] => Array
(
[points] => 5
[name] => Mike
)

[ab] => Array
(
[points] => 2
[name] => John Ab
)

[zoo] => Array
(
[points] => 2
[name] => John Zoo
)

[pete] => Array
(
[points] => 1
[name] => Peter
)

)

PHP로 Design Patterns 예제 - Strategy / Observer / Command 패턴을 섞다.

PHP로 Design Patterns 예제 - Strategy / Observer / Command 패턴을 섞다.


// http://www.ibm.com/developerworks/library/os-php-designptrns/
////////////////////////////////////////////////
////////////////////////////////////////////////

interface IStrategy
{
function filter( $record );
}

class FindAfterStrategy implements IStrategy
{
private $_name;

public function __construct( $name )
{
$this->_name = $name;
}

public function filter( $record )
{
return strcmp( $this->_name, $record ) <= 0;
}
}

class RandomStrategy implements IStrategy
{
public function filter( $record )
{
return rand( 0, 1 ) >= 0.5;
}
}

////////////////////////////////////////////////
////////////////////////////////////////////////

interface IObserver
{
function onAdded( $sender, $args );
function onDeleted( $sender, $args );
}

interface IObservable
{
function addObserver( $observer );
}

////////////////////////////////////////////////
////////////////////////////////////////////////

interface ICommand
{
function onCommand( $name, $args );
}

class CommandChain
{
private $_commands = array();

public function addCommand( $cmd )
{
$this->_commands []= $cmd;
}

public function runCommand( $name, $args )
{
foreach( $this->_commands as $cmd )
{
if ( $cmd->onCommand( $name, $args ) )
return;
}
}
}

////////////////////////////////////////////////
////////////////////////////////////////////////


class UserList implements IObservable
{
private $_list = array();
private $_observers = array();

public function addObserver( $observer )
{
$this->_observers []= $observer;
}

public function __construct( $names )
{
if ( $names != null )
{
foreach( $names as $name )
{
$this->_list []= $name;
}
}
}

public function add( $name )
{
$this->_list []= $name;
foreach( $this->_observers as $obs ) $obs->onAdded( $this, $name );
}

public function del( $name )
{
deleteFromArray( $this->_list, $name, false );
foreach( $this->_observers as $obs ) $obs->onDeleted( $this, $name );
}

public function find( $filter )
{
$recs = array();
foreach( $this->_list as $user )
{
if ( $filter->filter( $user ) ) $recs []= $user;
}
return $recs;
}
}

class UserListObserver implements IObserver
{
private $command;

public function __construct()
{
$this->command = new CommandChain();
$this->command->addCommand( new UserListCommand() );
}

public function onAdded( $sender, $args )
{
echo( "'$args' added user list\n
" );
$this->command->runCommand( 'add', $args );
}

public function onDeleted( $sender, $args )
{
echo( "'$args' deleted from user list\n
" );
$this->command->runCommand( 'del', $args );
}
}

class UserListCommand implements ICommand
{
public function onCommand( $name, $args )
{
$arrValidCommand = array( 'add', 'del' );

if ( !in_array( $name, $arrValidCommand ) ) return false;

if ( $name == 'add' ) echo( "Say to all users : added $args \n
" );
if ( $name == 'del' ) echo( "Say to all users : deleted $args \n
" );
return true;
}
}


$ul = new UserList( array( "Andy", "Jack", "Lori", "Megan" ) );
$ul->addObserver( new UserListObserver() );

$f1 = $ul->find( new FindAfterStrategy( "J" ) );
echo "
".print_r( $f1, true )."</pre>";<br /><br />$ul->add( "Lori2" );<br />$f2 = $ul->find( new FindAfterStrategy( "J" ) );<br />echo "<pre>".print_r( $f2, true )."</pre>";<br /><br />$ul->del( "Lori" );<br />$f3 = $ul->find( new FindAfterStrategy( "J" ) );<br />echo "<pre>".print_r( $f3, true )."</pre>";<br /><br />// $f2 = $ul->find( new RandomStrategy() );<br />// echo "<pre>".print_r( $f2, true )."
";

/////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////

/*
* This function deletes the given element from a one-dimension array
* Parameters: $array: the array (in/out)
* $deleteIt: the value which we would like to delete
* $useOldKeys: if it is false then the function will re-index the array (from 0, 1, ...)
* if it is true: the function will keep the old keys
* Returns true, if this value was in the array, otherwise false (in this case the array is same as before)
*/
function deleteFromArray(&$array, $deleteIt, $useOldKeys = FALSE)
{
$tmpArray = array();
$found = FALSE;
foreach($array as $key => $value)
{
if($value !== $deleteIt)
{
if(FALSE === $useOldKeys)
{
$tmpArray[] = $value;
}
else
{
$tmpArray[$key] = $value;
}
}
else
{
$found = TRUE;
}
}

$array = $tmpArray;

return $found;
}
?>


PHP5 이상에서의 실행결과는 다음과 같다.

Array
(
[0] => Jack
[1] => Lori
[2] => Megan
)

'Lori2' added user list
Say to all users : added Lori2

Array
(
[0] => Jack
[1] => Lori
[2] => Megan
[3] => Lori2
)

'Lori' deleted from user list
Say to all users : deleted Lori

Array
(
[0] => Jack
[1] => Megan
[2] => Lori2
)

Debian系列のLinuxにRedMineを設定する手順

Debian系列のLinuxにRedMineを設定する手順
========================================

1. 下記のURLにある、「rubyとrubygems、そしてrailsの導入」を参考。

http://d.hatena.ne.jp/takaxi/20080126/1201334126

※ MySQLは既にInstallされたと想定。


2. 下記のURLにある、「railsとRedMineのインストール」を参考。

http://www.srcw.net/blog/2007/09/redmine.html

※ RedMineのSVNアドレスは、下記のURLに記載されています。
http://www.redmine.org/wiki/redmine/Download


3. RedMindの初期管理者IDとPWは、admin : adminです。
  日本語化も管理画面とアカウント設定画面から個別設定可能!



4. 自動起動のため、下記のURLを参考。

------------

「簡単」http://groups.google.com/group/redmine-users-ja/browse_thread/thread/212dd9f92f9356f5/f659c85b7a31a63a?lnk=gst&q=mongrel_cluster#f659c85b7a31a63a

※ 簡単な設定は、~$ crontab -e で以下のコマンドを書く。
(redmineのディレクトリが /home/rails/redmine だとして。)
@reboot (cd /home/rails/redmine && ruby script/server -e production -p 3000 -d)

------------

「複雑」http://rubyist.g.hatena.ne.jp/muscovyduck/20070402/p1

※ 注意:下記の命令でconfig/mongrel_cluster.ymlファイル生成する。

mongrel_rails cluster::configure \
-e production -p 3000 -a 0.0.0.0 -l /home/rails/redmine/log/mongrel.log \
-P /var/run/mongrel/redmine.pid -c /home/rails/redmine \
-r /home/rails/redmine/public -N 1 --user rails --group www-data

※ "extconf.rb:1:in `require': no such file to load -- mkmf (LoadError)"エラーが
  が発生すると、下記のURLの5. Troubleshootingを参考して解決する。

  ヒント) apt-get install ruby1.8-dev

※ /etc/init.dに入れるScriptサンプルを検索、find / -name 'mongrel_cluster' -print

  結果例)/usr/lib/ruby/gems/1.8/gems/mongrel_cluster-1.0.5/resources/mongrel_cluster



エラー解決 (手順1と2の間)
==========

※ "uninitialized constant Gem::GemRunner(NameError)"のエラー対応

参考 - http://d.hatena.ne.jp/bottleneck/20080112/1200135756

/usr/bin/gemの10行目(require 'rubygems'の下の行)に
require 'rubygems/gem_runner'を追加する。


※ "rake aborted! No such file or directory - /tmp/mysql.sock"のエラー対応

参考 - http://crunchlife.com/articles/2007/10/06/rake-aborted-no-such-file-or-directory-tmp-mysql-sock

config/database.ymlのproductionセクションに下記の情報を追加。

socket: /var/run/mysqld/mysqld.sock

その後、ruby script/server -e productionでRUBYをSTOP。
rake db:migrate RAILS_ENV="production" 命令で環境更新。
その後、ruby script/server -e productionでRUBYを再起動。


※ DBへRedMine情報する際、文字化け問題。

config/database.ymlのproductionセクションに下記の情報を追加。

encoding: utf8

例)
production:
adapter: mysql
database: redminedb
host: localhost
username: redmineuser
password: redminepasswd
encoding: utf8

その後、ruby script/server -e productionでRUBYをSTOP。
rake db:migrate RAILS_ENV="production" 命令で環境更新。
その後、ruby script/server -e productionでRUBYを再起動。


※ メール送信ができない場合。

参考 - http://redmine.jp/faq/general/mail_notification/

config/environment.rbのSMTP設定にある
:authentication, :user_name, :passwordをコメントアウトしてみてください。

その後、ruby script/server -e productionでRUBYをSTOP。
rake db:migrate RAILS_ENV="production" 命令で環境更新。
その後、ruby script/server -e productionでRUBYを再起動。



※ RedMineの日本語化問題はまだ解決しない状態。

emm-dev:/home/dev/redmine# rake load_default_data RAILS_ENV="production"
(in /home/dev/redmine-0.6.3)

Select language: bg, cs, de, en, es, fr, he, it, ja, ko, nl, pl, pt, pt-br, ro, ru, sr, sv, zh, zh-tw [en] ja
====================================
Loading default configuration data for language: ja
Error: Mysql::Error: #HY000Illegal mix of collations (sjis_japanese_ci,IMPLICIT) and (latin1_swedish_ci,COERCIBLE) for operation '=': SELECT * FROM roles WHERE (roles.name = '管理 者') LIMIT 1
Default configuration data can't be loaded.

Note: The rake task load_default_data has been deprecated, please use the replacement version redmine:load_default_data
emm-dev:/home/dev/redmine# rake load_default_data RAILS_ENV="production"
(in /home/dev/redmine-0.6.3)

Select language: bg, cs, de, en, es, fr, he, it, ja, ko, nl, pl, pt, pt-br, ro, ru, sr, sv, zh, zh-tw [en]
====================================
Loading default configuration data for language: en

Note: The rake task load_default_data has been deprecated, please use the replacement version redmine:load_default_data






その他
======

※ 臨時的なRailsの起動

emm-dev:/home/dev/redmine# ruby script/server -e production
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2008-10-10 11:47:07] INFO WEBrick 1.3.1
[2008-10-10 11:47:07] INFO ruby 1.8.5 (2006-08-25) [i486-linux]
[2008-10-10 11:47:07] INFO WEBrick::HTTPServer#start: pid=17975 port=3000


※ gem environment 命令で環境確認。

emm-dev:/usr/bin# gem environment
/usr/bin/gem:11:Warning: Gem::manage_gems is deprecated and will be removed on or after March 2009.
RubyGems Environment:
- RUBYGEMS VERSION: 1.3.0
- RUBY VERSION: 1.8.5 (2006-08-25) [i486-linux]
- INSTALLATION DIRECTORY: /usr/lib/ruby/gems/1.8
- RUBY EXECUTABLE: /usr/bin/ruby1.8
- EXECUTABLE DIRECTORY: /usr/bin
- RUBYGEMS PLATFORMS:
- ruby
- x86-linux
- GEM PATHS:
- /usr/lib/ruby/gems/1.8
- /root/.gem/ruby/1.8
- GEM CONFIGURATION:
- :update_sources => true
- :verbose => true
- :benchmark => false
- :backtrace => false
- :bulk_threshold => 1000
- REMOTE SOURCES:
- http://gems.rubyforge.org/




以上です。