OSDN Git Service

- added Ethna_Getopt.php as replacement of PEAR::Console_Getopt.
authormumumu-org <mumumu-org@2ef88817-412d-0410-a32c-8029a115e976>
Sun, 13 Jul 2008 23:37:04 +0000 (23:37 +0000)
committermumumu-org <mumumu-org@2ef88817-412d-0410-a32c-8029a115e976>
Sun, 13 Jul 2008 23:37:04 +0000 (23:37 +0000)
CHANGES
class/Ethna_Getopt.php [new file with mode: 0644]
test/Ethna_Getopt_Test.php [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index 08497f7..6af6524 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,12 @@
 * 変更点一覧
 
+** 2.5.0-preview2
+
+*** features
+
+- PEAR依存を排除するための変更
+-- Console_Getopt の代替として、Ethna_Getopt.php を追加
+
 ** 2.5.0-preview1
 
 *** features
diff --git a/class/Ethna_Getopt.php b/class/Ethna_Getopt.php
new file mode 100644 (file)
index 0000000..09f9a12
--- /dev/null
@@ -0,0 +1,329 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Getopt.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
+ *  @package    Ethna
+ *  @version    $Id$
+ */
+
+if (!defined('ETHNA_OPTVALUE_IS_DISABLED')) {
+    define('ETHNA_OPTVALUE_IS_DISABLED', 1);
+}
+if (!defined('ETHNA_OPTVALUE_IS_REQUIRED')) {
+    define('ETHNA_OPTVALUE_IS_REQUIRED', 2);
+}
+if (!defined('ETHNA_OPTVALUE_IS_OPTIONAL')) {
+    define('ETHNA_OPTVALUE_IS_OPTIONAL', 3);
+}
+
+// {{{ Ethna_Getopt
+/**
+ *  コマンドラインオプション解釈クラス
+ *  PEAR への依存を排除するため、 Console_Getopt クラスを最実装したもの
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @access     public
+ *  @package    Ethna
+ *  @see        http://pear.php.net/manual/en/package.console.console-getopt.php
+ */
+class Ethna_Getopt
+{
+    /**
+     *  PHP 設定を考慮して、$argv 配列を読みます。 
+     *  ini ディレクティブ中の register_argc_argv を考慮します。
+     *
+     *  注意: PHP 4.2.0 以前では、$argv を読むためには
+     *         register_globals が ON になっている必要が
+     *         ありました。Ethna は この設定がoffであるこ
+     *         とを前提にして書かれているため、ここでは考
+     *         慮していません。
+     *
+     *  @return array - オプションとパラメータを含む配列、
+     *                  もしくは Ethna_Error
+     */
+    function readPHPArgv()
+    {
+        global $argv;
+
+        if (ini_get('register_argc_argv') == false) {
+            return Ethna::raiseError(
+                       'Could not read cmd args (register_argc_argv=Off?'
+                   );
+        }
+        return $argv;
+    }
+
+    /**
+     *  コマンドラインオプションをパースし、結果を返します。
+     *
+     *  @param array  $args - コマンドライン引数の配列
+     *  @param string $shortoptions - 使用できる短いオプション目録を指定します。
+     *  @param array  $longoptions - 使用できる長いオプション目録を指定します。
+     *
+     *  @return array - パースされたオプションと非オプションのコマンドライン引数
+     *                  の 2つの要素からなる配列、もしくは Ethna_Error 。 
+     */
+    function getopt($args, $shortoptions, $longoptions = NULL)
+    {
+        $shortopts = $this->_parseShortOption($shortoptions);
+        if (Ethna::isError($shortopts)) {
+            return $shortopts;
+        }
+        $longopts = $this->_parseLongOption($longoptions);
+        if (Ethna::isError($longopts)) {
+            return $longopts;
+        }
+
+        $parsed_arguments = array();
+        $nonparsed_arguments = array();
+
+        for ($pos = 0; $pos < count($args); $pos++) {
+
+             $arg = $args[$pos];
+             $next_arg = isset($args[$pos + 1]) ? $args[$pos + 1] : NULL;
+             $is_nextarg_is_value = false;
+             $required = false;
+
+             if (strpos($arg, '--') === 0) { //  long option
+
+                 //
+                 // GNU getopt(3) の場合は、長いオプションは他と重なら
+                 // ない限りにおいて短縮できる。たとえば --foo, --fuji
+                 // というオプションが定義された場合、 --fo や --fu と
+                 // いう短縮指定も可能というものである。
+                 //
+                 // PEAR の Console_Getopt はこの短縮指定に対応していな
+                 // い。よって、それを使用してきた Ethna でもそこまでは
+                 // 頑張らないことにする。
+                 //
+
+                 //    オプションの値を処理する
+                 $lopt = str_replace('--', '', $arg);
+                 $opt_and_value = explode('=', $lopt);
+                 $opt = $opt_and_value[0];
+                 if (!array_key_exists($opt, $longopts)) {
+                     return Ethna::raiseError("unrecognized option --$opt");
+                 }
+                 
+                 //  オプションの値を取り出す 
+                 $value = NULL;
+                 if (count($opt_and_value) == 2) {
+                     $value = $opt_and_value[1];   // --foo=bar
+                 } elseif (strpos('-', $next_arg) !== 0) {
+                     if (!empty($next_arg)) {      // --foo bar
+                         // 次の $argv を値として解釈
+                         $value = $next_arg;
+                         $pos++;
+                     }
+                 }
+
+                 //  オプション設定チェック 
+                 $required = $longopts[$opt];
+                 switch ($required) {
+                     case ETHNA_OPTVALUE_IS_REQUIRED:
+                         if ($value === NULL) {
+                             return Ethna::raiseError(
+                                        "option --$opt requires an argument"
+                                    );
+                         }
+                         break;
+                     case ETHNA_OPTVALUE_IS_DISABLED:
+                         if ($value !== NULL) {
+                             return Ethna::raiseError(
+                                        "option --$opt doesn't allow an argument"
+                                    );
+                         }    
+                         break;
+                 }
+
+                 //  長いオプションの場合は、-- 付きでオプション名を記録する
+                 //  Console_Getopt 互換にするため。
+                 $parsed_arguments[] = array("--$opt", $value);
+
+             } elseif (strpos($arg, '-') === 0) {  // short option
+
+                 //
+                 // -abcd のように、オプションと値が続けて
+                 // 入力される場合がある。この場合どうオプションを解釈
+                 // するかの仕様は、GNU getopt(3) の仕様に従う
+                 //
+                 // 1. abcd を1文字ずつに分解し、a, b, c, d にする
+                 //
+                 // 2. ':' (値必須) として設定されていた場合は、次の文字以降は
+                 //    全て値として解釈する。この場合は次のargvは値として解釈し
+                 //    ない。また、次の文字がなく、次の argv が値だった場合は、
+                 //    それを値として解釈する
+                 // 3. '::'(値が任意) として設定されていた場合も次の文字以降を
+                 //    全て値として解釈するが、次の文字がない場合でも次のargvは
+                 //    値として解釈「しない」
+                 //
+                 // 4. 無設定(値設定禁止)の場合は、次の文字もオプションとして解
+                 //    釈する。また、次のargvは値として解釈しない
+                 //
+                 // @see LANG=C; man 3 getopt (日本語マニュアルは見ない方がいいかも)
+                 // @see http://www.gnu.org/software/libtool/manual/libc/Using-Getopt.html
+                 //
+                 //  TODO: ambiguous なオプションを検出できるようにする
+                 //
+                 $sopt = str_replace('-', '', $arg);
+                 $sopt_len = strlen($sopt);
+
+                 for ($sopt_pos = 0; $sopt_pos < $sopt_len; $sopt_pos++) {
+
+                     //  オプションを取り出す
+                     $opt = $sopt[$sopt_pos];
+
+                     $value = NULL;
+                     $do_next_arg = false;
+                     $required = isset($shortopts[$opt]) ? $shortopts[$opt] : NULL;
+                     switch ($required) {
+                         case ETHNA_OPTVALUE_IS_REQUIRED:
+                         case ETHNA_OPTVALUE_IS_OPTIONAL:
+                             if ($sopt_len == 1
+                              && $required == ETHNA_OPTVALUE_IS_REQUIRED) {
+                                 if ($next_arg[0] != '-') { // -a hoge
+                                     // 次の $argv を値として解釈
+                                     $value = $next_arg;
+                                     $pos++;
+                                 }
+                             } else {
+                                 //  残りの文字を値として解釈
+                                 $value = substr($sopt, $sopt_pos + 1);
+                                 $value = (empty($value)) ? NULL : $value;
+                             } 
+                             if ($required == ETHNA_OPTVALUE_IS_REQUIRED
+                              && empty($value)) {
+                                 return Ethna::raiseError(
+                                            "option -$opt requires an argument"
+                                        );
+                             }
+                             // ':' または '::' が設定された場合は、次の文字
+                             // 以降を全て値として解釈するため、次のargv要素に
+                             // 解釈を移す
+                             $do_next_arg = true;
+                             break;
+                         case ETHNA_OPTVALUE_IS_DISABLED:
+
+                             //  値が設定禁止の場合は、次のargv の値が
+                             //  値であるかを調べるが、それは -a のよう
+                             //  な1文字オプションに限る。2文字以上の場合
+                             //  は一貫して次の「文字」をオプションとして
+                             //  解釈する
+                             if ($sopt_len == 1
+                              && $next_arg[0] != '-' && $next_arg !== NULL) {
+                                 return Ethna::raiseError(
+                                            "option -$opt doesn't allow an argument"
+                                        );
+                             }
+                             break;
+                         default:
+                             return Ethna::raiseError("unrecognized option -$opt");
+                             break;
+                     }
+
+                     //  短いオプションの場合は、- を付けないでオプション名を記録する
+                     //  Console_Getopt 互換にするため。
+                     $parsed_arguments[] = array($opt, $value);
+
+                     if ($do_next_arg === true) {
+                         break;
+                     }
+                 } 
+
+             } else {  // オプションとして解釈されない
+                 $nonparsed_arguments[] = $arg;
+             }
+        }
+  
+        return array($parsed_arguments, $nonparsed_arguments);
+    }
+
+    /**
+     *  短いオプション目録を解析します。
+     *
+     *  @param  string $sopts 短いオプション目録
+     *  @return array  オプションと引数指定種別の配列
+     *                 エラーの場合は Ethna_Error
+     *  @access private
+     */
+    function _parseShortOption($sopts)
+    {
+        if (empty($sopts)) {
+            return array();
+        }
+
+        if (!preg_match('/^[A-Za-z:]+$/', $sopts)) {
+            return Ethna::raiseError('invalid short options.');
+        }
+
+        $analyze_result = array();
+
+        for ($pos = 0; $pos < strlen($sopts); $pos++) {
+            $char = $sopts[$pos];
+            $next_char = (isset($sopts[$pos + 1]))
+                       ? $sopts[$pos + 1]
+                       : NULL;
+            $next2_char = (isset($sopts[$pos + 2]))
+                        ? $sopts[$pos + 2]
+                        : NULL;
+
+            if ($char == ':') {
+                continue;
+            }
+
+            //   $sopts[$pos] is character.
+            if ($next_char == ':' && $next2_char == ':') {
+                $analyze_result[$char] = ETHNA_OPTVALUE_IS_OPTIONAL; // 値は任意
+            } elseif ($next_char == ':' && $next2_char != ':') { 
+                $analyze_result[$char] = ETHNA_OPTVALUE_IS_REQUIRED; // 値は必須
+            } else {
+                $analyze_result[$char] = ETHNA_OPTVALUE_IS_DISABLED; // 値は不要
+            }
+        }
+
+        return $analyze_result;
+    }
+
+    /**
+     *  長いオプション目録を解析します。
+     *
+     *  @param  array $lopts 長いオプション目録
+     *  @return array オプションと引数指定種別の配列
+     *                エラーの場合は Ethna_Error
+     *  @access private
+     */
+    function _parseLongOption($lopts)
+    {
+        if (empty($lopts)) {
+            return array();
+        }
+
+        if (!is_array($lopts)) {
+            return Ethna::raiseError('invalid long options.');
+        }
+
+        $analyze_result = array();
+         
+        foreach ($lopts as $opt) {
+            if (preg_match('/==$/', $opt) > 0) {
+                $opt = substr($opt, 0, -2); 
+                $analyze_result[$opt] = ETHNA_OPTVALUE_IS_OPTIONAL; // 値は任意
+            } elseif (preg_match('/=$/', $opt) > 0) {
+                $opt = substr($opt, 0, -1); 
+                $analyze_result[$opt] = ETHNA_OPTVALUE_IS_REQUIRED; // 値は必須
+            } else {
+                $analyze_result[$opt] = ETHNA_OPTVALUE_IS_DISABLED; // 値は不要
+            }
+        }
+
+        return $analyze_result;
+    }
+}
+
+// }}}
+
+?>
diff --git a/test/Ethna_Getopt_Test.php b/test/Ethna_Getopt_Test.php
new file mode 100644 (file)
index 0000000..4bd9c0a
--- /dev/null
@@ -0,0 +1,399 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_Getopt_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+require_once ETHNA_BASE . '/class/Ethna_Getopt.php';
+
+/**
+ *  Test Case For Ethna_Getopt
+ *
+ *  @access public
+ */
+class Ethna_Getopt_Test extends Ethna_UnitTestBase
+{
+    var $opt;
+
+    function setUp()
+    {
+        $this->opt = new Ethna_Getopt();
+    }
+
+    // {{{ readPHPArgv
+    function test_readPHPArgv()
+    {
+        global $argv;
+        $argv = array('test.php', 'a', '-b=c', '--c=d', 'e');
+        
+        $r = $this->opt->readPHPArgv();
+        $this->assertEqual('test.php', $argv[0]);
+        $this->assertEqual('a', $argv[1]);
+        $this->assertEqual('-b=c', $argv[2]);
+        $this->assertEqual('--c=d', $argv[3]);
+        $this->assertEqual('e', $argv[4]);
+    }
+    // }}}
+
+    //{{{ short option test
+    function test_shortopt_required()
+    {
+        // no args
+        $args = array();
+        $shortopt = 'a:';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        // option -a is defined, but no args.
+        $args = array('-a');
+        $shortopt = 'a:';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('option -a requires an argument', $r->getMessage());
+
+        // unknown option 
+        $args = array('-c'); // -c is unknown.
+        $shortopt = 'a:';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option -c', $r->getMessage());
+
+        // unknown option part 2.
+        $args = array('--foo'); // -foo is unknown.
+        $shortopt = 'a:';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option --foo', $r->getMessage());
+
+        // -a option value is b. c is nonparsed.
+        $args = array('-a', 'b', 'c');
+        $shortopt = 'a:';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertEqual('b', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('c', $nonparsed_arg[0]);
+
+        // -a value is bcd, e is nonparsed.
+        $args = array('-abcd', 'e');
+        $shortopt = 'a:';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertEqual('bcd', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('e', $nonparsed_arg[0]);
+    }
+
+    function test_shortopt_optional()
+    {
+        // no args
+        $args = array();
+        $shortopt = 'a::';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        // option -a is defined, but no args.
+        $args = array('-a');
+        $shortopt = 'a::';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        // -a value is bcd, e is nonparsed arg.
+        $args = array('-abcd', 'e');
+        $shortopt = 'a::';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertEqual('bcd', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('e', $nonparsed_arg[0]);
+        // -a option value is none. b, c is nonparsed.
+        $args = array('-a', 'b', 'c');
+        $shortopt = 'a::';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertNULL($parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('b', $nonparsed_arg[0]);
+        $this->assertEqual('c', $nonparsed_arg[1]);
+    }
+
+    function test_shortopt_disabled()
+    {
+        // no args
+        $args = array();
+        $shortopt = 'a';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        // option -a is defined, but no args.
+        $args = array('-a');
+        $shortopt = 'a';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertNULL($parsed_arg[0][1]);
+
+        // option -a is defined, but value is disabled.
+        $args = array('-a', 'b');
+        $shortopt = 'a';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual("option -a doesn't allow an argument", $r->getMessage());
+
+        // successive option definition, but unrecognized option. :)
+        $args = array('-ab');
+        $shortopt = 'a';
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual("unrecognized option -b", $r->getMessage());
+    }
+
+    function test_shortopt_complex()
+    {
+        //  complex option part 1.
+        $args = array();
+        $shortopt = 'ab:c::';
+        $args = array('-abc', '-cd');
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertNULL($parsed_arg[0][1]);
+
+        $this->assertEqual('b', $parsed_arg[1][0]);
+        $this->assertEqual('c', $parsed_arg[1][1]);
+
+        $this->assertEqual('c', $parsed_arg[2][0]);
+        $this->assertEqual('d', $parsed_arg[2][1]);
+
+        //  complex option part 2.
+        $args = array('-a', '-c', 'd', 'e');
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('a', $parsed_arg[0][0]);
+        $this->assertNULL($parsed_arg[0][1]);
+
+        $this->assertEqual('c', $parsed_arg[1][0]);
+        $this->assertNULL($parsed_arg[1][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('d', $nonparsed_arg[0]);
+        $this->assertEqual('e', $nonparsed_arg[1]);
+
+        $args = array('-cd', '-ad');
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual("unrecognized option -d", $r->getMessage());
+
+        $args = array('-cd', '-ad');
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual("unrecognized option -d", $r->getMessage());
+    }
+    // }}}
+
+    // {{{  long option test
+    function test_longopt_required()
+    {
+        // no args
+        $args = array();
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+    
+        // option -a is defined, but no args.
+        $args = array('--foo');
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('option --foo requires an argument', $r->getMessage());
+
+        // unknown option.
+        $args = array('--bar'); // -bar is unknown.
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option --bar', $r->getMessage());
+
+        // unknown option part 1.
+        $args = array('--bar'); // -bar is unknown.
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option --bar', $r->getMessage());
+
+        // unknown option part 2.
+        $args = array('-a'); // -a is unknown.
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt);
+        $this->assertTrue(Ethna::isError($r));
+        $this->assertEqual('unrecognized option -a', $r->getMessage());
+
+        // --foo option value is bar. hoge is nonparsed. 
+        $args = array('--foo=bar', 'hoge');
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('--foo', $parsed_arg[0][0]);
+        $this->assertEqual('bar', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('hoge', $nonparsed_arg[0]);
+        // --foo option value is bar. hoge is nonparsed.
+        $args = array('--foo', 'bar', 'hoge');
+        $shortopt = NULL;
+        $longopt = array("foo=");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('--foo', $parsed_arg[0][0]);
+        $this->assertEqual('bar', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('hoge', $nonparsed_arg[0]);
+    }
+
+    function test_longopt_optional()
+    {
+        // no args
+        $args = array();
+        $shortopt = NULL;
+        $longopt = array("foo==");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+        // option --foo is defined, but no args.
+        $args = array('--foo');
+        $shortopt = NULL;
+        $longopt = array("foo==");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        // -foo value is bar, hoge is nonparsed arg.
+        $args = array('--foo', 'bar', 'hoge');
+        $shortopt = NULL;
+        $longopt = array("foo==");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('--foo', $parsed_arg[0][0]);
+        $this->assertEqual('bar', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('hoge', $nonparsed_arg[0]);
+
+        // -foo value is bar, hoge, moge is nonparsed arg.
+        $args = array('--foo=bar', 'hoge', 'moge');
+        $shortopt = NULL;
+        $longopt = array("foo==");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertEqual('--foo', $parsed_arg[0][0]);
+        $this->assertEqual('bar', $parsed_arg[0][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('hoge', $nonparsed_arg[0]);
+        $this->assertEqual('moge', $nonparsed_arg[1]);
+    }
+
+    function test_longopt_disabled()
+    {
+        // no args
+        $args = array();
+        $shortopt = NULL;
+        $longopt = array("foo");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+
+        // option -foo is defined, but no args.
+        $args = array('--foo');
+        $shortopt = null;
+        $longopt = array("foo");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertfalse(ethna::iserror($r));
+
+        $parsed_arg = array_shift($r);
+        $this->assertequal('--foo', $parsed_arg[0][0]);
+        $this->assertnull($parsed_arg[0][1]);
+
+        // option -foo is defined, but value is disabled.
+        $args = array('--foo=bar');
+        $shortopt = null;
+        $longopt = array("foo");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertTrue(ethna::iserror($r));
+        $this->assertEqual("option --foo doesn't allow an argument", $r->getMessage());
+
+        $args = array('--foo', 'bar');
+        $shortopt = null;
+        $longopt = array("foo");
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertTrue(ethna::iserror($r));
+        $this->assertEqual("option --foo doesn't allow an argument", $r->getMessage());
+    }
+    // }}}
+
+    // {{{  short option, long option mixed.
+    function test_mixed_option()
+    {
+        // no args
+        $shortopt = 'ab:c::';
+        $longopt = array('foo=', 'bar==', 'hoge');
+
+        $args = array();
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+        
+        $args = array('-a', '--foo', 'bar', '--bar=moge', 'hoge');
+        $r = $this->opt->getopt($args, $shortopt, $longopt);
+        $this->assertFalse(Ethna::isError($r));
+        $parsed_arg = array_shift($r);
+        $this->assertequal('a', $parsed_arg[0][0]);
+        $this->assertNull($parsed_arg[0][1]);
+        $this->assertequal('--foo', $parsed_arg[1][0]);
+        $this->assertEqual('bar', $parsed_arg[1][1]);
+        $this->assertequal('--bar', $parsed_arg[2][0]);
+        $this->assertEqual('moge', $parsed_arg[2][1]);
+
+        $nonparsed_arg = array_shift($r);
+        $this->assertEqual('hoge', $nonparsed_arg[0]);
+    }
+    // }}}
+}
+
+?>