OSDN Git Service

- fixed disabled option's value interpretation.
[ethna/ethna.git] / class / Ethna_Getopt.php
1 <?php
2 // vim: foldmethod=marker
3 /**
4  *  Ethna_Getopt.php
5  *
6  *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
7  *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
8  *  @package    Ethna
9  *  @version    $Id$
10  */
11
12 if (!defined('ETHNA_OPTVALUE_IS_DISABLED')) {
13     define('ETHNA_OPTVALUE_IS_DISABLED', 1);
14 }
15 if (!defined('ETHNA_OPTVALUE_IS_REQUIRED')) {
16     define('ETHNA_OPTVALUE_IS_REQUIRED', 2);
17 }
18 if (!defined('ETHNA_OPTVALUE_IS_OPTIONAL')) {
19     define('ETHNA_OPTVALUE_IS_OPTIONAL', 3);
20 }
21
22 // {{{ Ethna_Getopt
23 /**
24  *  コマンドラインオプション解釈クラス
25  *  PEAR への依存を排除するため、 Console_Getopt クラスを最実装したもの
26  *
27  *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
28  *  @access     public
29  *  @package    Ethna
30  *  @see        http://pear.php.net/manual/en/package.console.console-getopt.php
31  */
32 class Ethna_Getopt
33 {
34     /**
35      *  PHP 設定を考慮して、$argv 配列を読みます。 
36      *  ini ディレクティブ中の register_argc_argv を考慮します。
37      *
38      *  注意: PHP 4.2.0 以前では、$argv を読むためには
39      *         register_globals が ON になっている必要が
40      *         ありました。Ethna は この設定がoffであるこ
41      *         とを前提にして書かれているため、ここでは考
42      *         慮していません。
43      *
44      *  @return array - オプションとパラメータを含む配列、
45      *                  もしくは Ethna_Error
46      */
47     function readPHPArgv()
48     {
49         global $argv;
50
51         if (ini_get('register_argc_argv') == false) {
52             return Ethna::raiseError(
53                        'Could not read cmd args (register_argc_argv=Off?'
54                    );
55         }
56         return $argv;
57     }
58
59     /**
60      *  コマンドラインオプションをパースし、結果を返します。
61      *
62      *  @param array  $args - コマンドライン引数の配列
63      *  @param string $shortoptions - 使用できる短いオプション目録を指定します。
64      *  @param array  $longoptions - 使用できる長いオプション目録を指定します。
65      *
66      *  @return array - パースされたオプションと非オプションのコマンドライン引数
67      *                  の 2つの要素からなる配列、もしくは Ethna_Error 。 
68      */
69     function getopt($args, $shortoptions, $longoptions = NULL)
70     {
71         $shortopts = $this->_parseShortOption($shortoptions);
72         if (Ethna::isError($shortopts)) {
73             return $shortopts;
74         }
75         $longopts = $this->_parseLongOption($longoptions);
76         if (Ethna::isError($longopts)) {
77             return $longopts;
78         }
79
80         $parsed_arguments = array();
81         $nonparsed_arguments = array();
82
83         for ($pos = 0; $pos < count($args); $pos++) {
84
85              $arg = $args[$pos];
86              $next_arg = isset($args[$pos + 1]) ? $args[$pos + 1] : NULL;
87              $is_nextarg_is_value = false;
88              $required = false;
89
90              if (strpos($arg, '--') === 0) { //  long option
91
92                  //
93                  // GNU getopt(3) の場合は、長いオプションは他と重なら
94                  // ない限りにおいて短縮できる。たとえば --foo, --fuji
95                  // というオプションが定義された場合、 --fo や --fu と
96                  // いう短縮指定も可能というものである。
97                  //
98                  // PEAR の Console_Getopt はこの短縮指定に対応していな
99                  // い。よって、それを使用してきた Ethna でもそこまでは
100                  // 頑張らないことにする。
101                  //
102
103                  //    オプションの値を処理する
104                  $lopt = str_replace('--', '', $arg);
105                  $opt_and_value = explode('=', $lopt);
106                  $opt = $opt_and_value[0];
107                  if (!array_key_exists($opt, $longopts)) {
108                      return Ethna::raiseError("unrecognized option --$opt");
109                  }
110                  
111                  //  オプションの値を取り出す 
112                  $required = $longopts[$opt];
113                  $value = NULL;
114                  if (count($opt_and_value) == 2) {
115                      $value = $opt_and_value[1];   // --foo=bar
116                  } elseif (strpos('-', $next_arg) !== 0
117                         && $required == ETHNA_OPTVALUE_IS_REQUIRED) {
118                      if (!empty($next_arg)) {      // --foo bar
119                          // 次の $argv を値として解釈
120                          // == が設定されていた場合は値として解釈「しない」
121                          $value = $next_arg;
122                          $pos++;
123                      }
124                  }
125
126                  //  オプション設定チェック 
127                  switch ($required) {
128                      case ETHNA_OPTVALUE_IS_REQUIRED:
129                          if ($value === NULL) {
130                              return Ethna::raiseError(
131                                         "option --$opt requires an argument"
132                                     );
133                          }
134                          break;
135                      case ETHNA_OPTVALUE_IS_DISABLED:
136                          if ($value !== NULL) {
137                              return Ethna::raiseError(
138                                         "option --$opt doesn't allow an argument"
139                                     );
140                          }    
141                          break;
142                  }
143
144                  //  長いオプションの場合は、-- 付きでオプション名を記録する
145                  //  Console_Getopt 互換にするため。
146                  $parsed_arguments[] = array("--$opt", $value);
147
148              } elseif (strpos($arg, '-') === 0) {  // short option
149
150                  //
151                  // -abcd のように、オプションと値が続けて
152                  // 入力される場合がある。この場合どうオプションを解釈
153                  // するかの仕様は、GNU getopt(3) の仕様に従う
154                  //
155                  // 1. abcd を1文字ずつに分解し、a, b, c, d にする
156                  //
157                  // 2. ':' (値必須) として設定されていた場合は、次の文字以降は
158                  //    全て値として解釈する。この場合は次のargvは値として解釈し
159                  //    ない。また、次の文字がなく、次の argv が値だった場合は、
160                  //    それを値として解釈する
161                  // 3. '::'(値が任意) として設定されていた場合も次の文字以降を
162                  //    全て値として解釈するが、次の文字がない場合でも次のargvは
163                  //    値として解釈「しない」
164                  //
165                  // 4. 無設定(値設定禁止)の場合は、次の文字もオプションとして解
166                  //    釈する。また、次のargvは値として解釈しない
167                  //
168                  // @see LANG=C; man 3 getopt (日本語マニュアルは見ない方がいいかも)
169                  // @see http://www.gnu.org/software/libtool/manual/libc/Using-Getopt.html
170                  //
171                  //  TODO: ambiguous なオプションを検出できるようにする
172                  //
173  
174                  $sopt = str_replace('-', '', $arg);
175                  $sopt_len = strlen($sopt);
176
177                  for ($sopt_pos = 0; $sopt_pos < $sopt_len; $sopt_pos++) {
178
179                      //  オプションを取り出す
180                      $opt = $sopt[$sopt_pos];
181
182                      $value = NULL;
183                      $do_next_arg = false;
184                      $required = isset($shortopts[$opt]) ? $shortopts[$opt] : NULL;
185                      switch ($required) {
186                          case ETHNA_OPTVALUE_IS_REQUIRED:
187                          case ETHNA_OPTVALUE_IS_OPTIONAL:
188                             if ($sopt_len == 1
189                              && $required == ETHNA_OPTVALUE_IS_REQUIRED) {
190                                 if ($next_arg[0] != '-') { // -a hoge
191                                     // 次の $argv を値として解釈
192                                     // 但し、:: の場合は解釈しない
193                                     $value = $next_arg;
194                                     $pos++;
195                                 }
196                             } else {
197                                 //  残りの文字を値として解釈
198                                 $value = substr($sopt, $sopt_pos + 1);
199                                 $value = (empty($value)) ? NULL : $value;
200                             } 
201                             if ($required == ETHNA_OPTVALUE_IS_REQUIRED
202                               && empty($value)) {
203                                  return Ethna::raiseError(
204                                             "option -$opt requires an argument"
205                                         );
206                              }
207                              // ':' または '::' が設定された場合は、次の文字
208                              // 以降を全て値として解釈するため、次のargv要素に
209                              // 解釈を移す
210                              $do_next_arg = true;
211                              break;
212                          case ETHNA_OPTVALUE_IS_DISABLED:
213                              //   値を設定禁止にした場合は、値が解釈されなく
214                              //   なるので、値設定のチェックは不要
215                              break;
216                          default:
217                              return Ethna::raiseError("unrecognized option -$opt");
218                              break;
219                      }
220
221                      //  短いオプションの場合は、- を付けないでオプション名を記録する
222                      //  Console_Getopt 互換にするため。
223                      $parsed_arguments[] = array($opt, $value);
224
225                      if ($do_next_arg === true) {
226                          break;
227                      }
228                  } 
229
230              } else {  // オプションとして解釈されない
231                  $nonparsed_arguments[] = $arg;
232              }
233         }
234   
235         return array($parsed_arguments, $nonparsed_arguments);
236     }
237
238     /**
239      *  短いオプション目録を解析します。
240      *
241      *  @param  string $sopts 短いオプション目録
242      *  @return array  オプションと引数指定種別の配列
243      *                 エラーの場合は Ethna_Error
244      *  @access private
245      */
246     function _parseShortOption($sopts)
247     {
248         if (empty($sopts)) {
249             return array();
250         }
251
252         if (!preg_match('/^[A-Za-z:]+$/', $sopts)) {
253             return Ethna::raiseError('invalid short options.');
254         }
255
256         $analyze_result = array();
257
258         for ($pos = 0; $pos < strlen($sopts); $pos++) {
259             $char = $sopts[$pos];
260             $next_char = (isset($sopts[$pos + 1]))
261                        ? $sopts[$pos + 1]
262                        : NULL;
263             $next2_char = (isset($sopts[$pos + 2]))
264                         ? $sopts[$pos + 2]
265                         : NULL;
266
267             if ($char == ':') {
268                 continue;
269             }
270
271             //   $sopts[$pos] is character.
272             if ($next_char == ':' && $next2_char == ':') {
273                 $analyze_result[$char] = ETHNA_OPTVALUE_IS_OPTIONAL; // 値は任意
274             } elseif ($next_char == ':' && $next2_char != ':') { 
275                 $analyze_result[$char] = ETHNA_OPTVALUE_IS_REQUIRED; // 値は必須
276             } else {
277                 $analyze_result[$char] = ETHNA_OPTVALUE_IS_DISABLED; // 値は不要
278             }
279         }
280
281         return $analyze_result;
282     }
283
284     /**
285      *  長いオプション目録を解析します。
286      *
287      *  @param  array $lopts 長いオプション目録
288      *  @return array オプションと引数指定種別の配列
289      *                エラーの場合は Ethna_Error
290      *  @access private
291      */
292     function _parseLongOption($lopts)
293     {
294         if (empty($lopts)) {
295             return array();
296         }
297
298         if (!is_array($lopts)) {
299             return Ethna::raiseError('invalid long options.');
300         }
301
302         $analyze_result = array();
303          
304         foreach ($lopts as $opt) {
305             if (preg_match('/==$/', $opt) > 0) {
306                 $opt = substr($opt, 0, -2); 
307                 $analyze_result[$opt] = ETHNA_OPTVALUE_IS_OPTIONAL; // 値は任意
308             } elseif (preg_match('/=$/', $opt) > 0) {
309                 $opt = substr($opt, 0, -1); 
310                 $analyze_result[$opt] = ETHNA_OPTVALUE_IS_REQUIRED; // 値は必須
311             } else {
312                 $analyze_result[$opt] = ETHNA_OPTVALUE_IS_DISABLED; // 値は不要
313             }
314         }
315
316         return $analyze_result;
317     }
318 }
319
320 // }}}
321
322 ?>