1<?php 2 3/** @file clicommand.inc 4 * @ingroup Phar 5 * @brief class CLICommand 6 * @author Marcus Boerger 7 * @date 2007 - 2008 8 * 9 * Phar Command 10 */ 11 12/** @ingroup Phar 13 * @brief Abstract base console command implementation 14 * @author Marcus Boerger 15 * @version 1.0 16 */ 17abstract class CLICommand 18{ 19 protected $argc; 20 protected $argv; 21 protected $cmds = array(); 22 protected $args = array(); 23 protected $typs = array(); 24 25 function __construct($argc, array $argv) 26 { 27 $this->argc = $argc; 28 $this->argv = $argv; 29 $this->cmds = self::getCommands($this); 30 $this->typs = self::getArgTyps($this); 31 32 if ($argc < 2) { 33 self::error("No command given, check ${argv[0]} help\n"); 34 } elseif (!isset($this->cmds[$argv[1]]['run'])) { 35 self::error("Unknown command '${argv[1]}', check ${argv[0]} help\n"); 36 } else { 37 $command = $argv[1]; 38 } 39 40 if (isset($this->cmds[$command]['arg'])) { 41 $this->args = call_user_func(array($this, $this->cmds[$command]['arg'])); 42 $i = 1; 43 $missing = false; 44 while (++$i < $argc) { 45 if ($argv[$i][0] == '-') { 46 if (strlen($argv[$i]) == 2 && isset($this->args[$argv[$i][1]])) { 47 $arg = $argv[$i][1]; 48 if (++$i >= $argc) { 49 self::error("Missing argument to parameter '$arg' of command '$command', check ${argv[0]} help\n"); 50 } else { 51 $this->args[$arg]['val'] = $this->checkArgTyp($arg, $i, $argc, $argv); 52 } 53 } else { 54 self::error("Unknown parameter '${argv[$i]}' to command $command, check ${argv[0]} help\n"); 55 } 56 } else { 57 break; 58 } 59 } 60 61 if (isset($this->args[''])) { 62 if ($i >= $argc) { 63 if (isset($this->args['']['require']) && $this->args['']['require']) { 64 self::error("Missing default trailing arguments to command $command, check ${argv[0]} help\n"); 65 } 66 } else { 67 $this->args['']['val'] = array(); 68 while($i < $argc) { 69 $this->args['']['val'][] = $argv[$i++]; 70 } 71 } 72 } else if ($i < $argc) { 73 self::error("Unexpected default arguments to command $command, check ${argv[0]} help\n"); 74 } 75 76 foreach($this->args as $arg => $inf) { 77 if (strlen($arg) && !isset($inf['val']) && isset($inf['required']) && $inf['required']) { 78 $missing .= "Missing parameter '-$arg' to command $command, check ${argv[0]} help\n"; 79 } 80 } 81 82 if (strlen($missing)) { 83 self::error($missing); 84 } 85 } 86 87 call_user_func(array($this, $this->cmds[$command]['run']), $this->args); 88 } 89 90 static function notice ($msg) 91 { 92 fprintf(STDERR, $msg); 93 } 94 95 static function error ($msg, $exit_code = 1) 96 { 97 self::notice($msg); 98 exit($exit_code); 99 } 100 101 function checkArgTyp($arg, $i, $argc, $argv) 102 { 103 $typ = $this->args[$arg]['typ']; 104 105 if (isset($this->typs[$typ]['typ'])) { 106 return call_user_func(array($this, $this->typs[$typ]['typ']), $argv[$i], $this->args[$arg], $arg); 107 } else { 108 return $argv[$i]; 109 } 110 } 111 112 static function getSubFuncs(CLICommand $cmdclass, $prefix, array $subs) 113 { 114 $a = array(); 115 $r = new ReflectionClass($cmdclass); 116 $l = strlen($prefix); 117 118 foreach($r->getMethods() as $m) { 119 if (substr($m->name, 0, $l) == $prefix) { 120 foreach($subs as $sub) { 121 $what = substr($m->name, $l+strlen($sub)+1); 122 $func = $prefix . $sub . '_' . $what; 123 $what = str_replace('_', '-', $what); 124 if ($r->hasMethod($func)) { 125 if (!isset($a[$what])) { 126 $a[$what] = array(); 127 } 128 $a[$what][$sub] = /*$m->class . '::' .*/ $func; 129 } 130 } 131 } 132 } 133 return $a; 134 } 135 136 static function getCommands(CLICommand $cmdclass) 137 { 138 return self::getSubFuncs($cmdclass, 'cli_cmd_', array('arg','inf','run')); 139 } 140 141 static function getArgTyps(CLICommand $cmdclass) 142 { 143 return self::getSubFuncs($cmdclass, 'cli_arg_', array('typ')); 144 } 145 146 static function cli_arg_typ_bool($arg, $cfg, $key) 147 { 148 return (bool)$arg; 149 } 150 151 static function cli_arg_typ_int($arg, $cfg, $key) 152 { 153 if ((int)$arg != $arg) { 154 self::error("Argument to -$key must be an integer.\n"); 155 } 156 157 return (int)$arg; 158 } 159 160 static function cli_arg_typ_regex($arg, $cfg, $key) 161 { 162 if (strlen($arg)) { 163 if (strlen($arg) > 1 && $arg[0] == $arg[strlen($arg)-1] && strpos('/,', $arg) !== false) { 164 return $arg; 165 } else { 166 return '/' . $arg . '/'; 167 } 168 } else { 169 return NULL; 170 } 171 } 172 173 static function cli_arg_typ_select($arg, $cfg, $key) 174 { 175 if (!in_array($arg, array_keys($cfg['select']))) { 176 self::error("Parameter value '$arg' not one of '" . join("', '", array_keys($cfg['select'])) . "'.\n"); 177 } 178 return $arg; 179 } 180 181 static function cli_arg_typ_dir($arg, $cfg, $key) 182 { 183 $f = realpath($arg); 184 185 if ($f===false || !file_exists($f) || !is_dir($f)) { 186 self::error("Requested path '$arg' does not exist.\n"); 187 } 188 return $f; 189 } 190 191 static function cli_arg_typ_file($arg) 192 { 193 $f = new SplFileInfo($arg); 194 $f = $f->getRealPath(); 195 if ($f===false || !file_exists($f)) { 196 echo "Requested file '$arg' does not exist.\n"; 197 exit(1); 198 } 199 return $f; 200 } 201 202 static function cli_arg_typ_filenew($arg, $cfg, $key) 203 { 204 $d = dirname($arg); 205 $f = realpath($d); 206 207 if ($f === false) { 208 self::error("Path for file '$arg' does not exist.\n"); 209 } 210 return $f . '/' . basename($arg); 211 } 212 213 static function cli_arg_typ_filecont($arg, $cfg, $key) 214 { 215 return file_get_contents(self::cli_arg_typ_file($arg, $cfg, $key)); 216 } 217 218 function cli_get_SP2($l1, $arg_inf) 219 { 220 return str_repeat(' ', $l1 + 2 + 4 + 8); 221 } 222 223 function cli_get_SP3($l1, $l2, $arg_inf) 224 { 225 return str_repeat(' ', $l1 + 2 + 4 + 8 + 2 + $l2 + 2); 226 } 227 228 static function cli_cmd_inf_help() 229 { 230 return "This help or help for a selected command."; 231 } 232 233 private function cli_wordwrap($what, $l, $sp) 234 { 235 $p = max(79 - $l, 40); // minimum length for paragraph 236 $b = substr($what, 0, $l); // strip out initial $l 237 $r = substr($what, $l); // remainder 238 $r = str_replace("\n", "\n".$sp, $r); // in remainder replace \n's 239 return $b . wordwrap($r, $p, "\n".$sp); 240 } 241 242 private function cli_help_get_args($func, $l, $sp, $required) 243 { 244 $inf = ""; 245 foreach(call_user_func($func, $l, $sp) as $arg => $conf) { 246 if ((isset($conf['required']) && $conf['required']) != $required) { 247 continue; 248 } 249 250 if (strlen($arg)) { 251 $arg = "-$arg "; 252 } else { 253 $arg = "... "; 254 } 255 256 $sp2 = $this->cli_get_SP2($l, $inf); 257 $l2 = strlen($sp2); 258 $inf .= $this->cli_wordwrap($sp . $arg . $conf['inf'], $l2, $sp2) . "\n"; 259 260 if (isset($conf['select']) && count($conf['select'])) { 261 $ls = 0; 262 foreach($conf['select'] as $opt => $what) { 263 $ls = max($ls, strlen($opt)); 264 } 265 $sp3 = $this->cli_get_SP3($l, $ls, $inf); 266 $l3 = strlen($sp3); 267 foreach($conf['select'] as $opt => $what) { 268 $inf .= $this->cli_wordwrap($sp2 . " " . sprintf("%-${ls}s ", $opt) . $what, $l3, $sp3) . "\n"; 269 } 270 } 271 } 272 if (strlen($inf)) { 273 if ($required) { 274 return $sp . "Required arguments:\n\n" . $inf; 275 } else { 276 return $sp . "Optional arguments:\n\n". $inf; 277 } 278 } 279 } 280 281 function cli_cmd_arg_help() 282 { 283 return array('' => array('typ'=>'any','val'=>NULL,'inf'=>'Optional command to retrieve help for.')); 284 } 285 286 function cli_cmd_run_help() 287 { 288 $argv = $this->argv; 289 $which = $this->args['']['val']; 290 if (isset($which)) { 291 if (count($which) != 1) { 292 self::error("More than one command given.\n"); 293 } 294 295 $which = $which[0]; 296 if (!array_key_exists($which, $this->cmds)) { 297 if (strtolower($which) == 'commands') { 298 self::cli_cmd_run_help_list(); 299 exit(0); 300 } 301 self::error("Unknown command, cannot retrieve help.\n"); 302 } 303 304 $l = strlen($which); 305 $cmds = array($which => $this->cmds[$which]); 306 } else { 307 echo "\n$argv[0] <command> [options]\n\n"; 308 $l = 0; 309 ksort($this->cmds); 310 foreach($this->cmds as $name => $funcs) { 311 $l = max($l, strlen($name)); 312 } 313 $inf = "Commands:"; 314 $lst = ""; 315 $ind = strlen($inf) + 1; 316 foreach($this->cmds as $name => $funcs) { 317 $lst .= ' ' . $name; 318 } 319 echo $this->cli_wordwrap($inf.$lst, $ind, str_repeat(' ', $ind)) . "\n\n"; 320 $cmds = $this->cmds; 321 } 322 $sp = str_repeat(' ', $l + 2); 323 foreach($cmds as $name => $funcs) { 324 $inf = $name . substr($sp, strlen($name)); 325 if (isset($funcs['inf'])) { 326 $inf .= $this->cli_wordwrap(call_user_func(array($this, $funcs['inf'])), $l, $sp) . "\n"; 327 if (isset($funcs['arg'])) { 328 $inf .= "\n"; 329 $inf .= $this->cli_help_get_args(array($this, $funcs['arg']), $l, $sp, true); 330 $inf .= "\n"; 331 $inf .= $this->cli_help_get_args(array($this, $funcs['arg']), $l, $sp, false); 332 } 333 } 334 echo "$inf\n\n"; 335 } 336 exit(0); 337 } 338 339 static function cli_cmd_inf_help_list() 340 { 341 return "Lists available commands."; 342 } 343 344 function cli_cmd_run_help_list() 345 { 346 ksort($this->cmds); 347 echo join(' ', array_keys($this->cmds)) . "\n"; 348 } 349} 350 351?> 352