xref: /php-src/ext/phar/phar/clicommand.inc (revision 9a90bd70)
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