9 * @author Stig Bakken <ssb@php.net>
10 * @author Greg Beaver <cellog@php.net>
11 * @copyright 1997-2009 The Authors
12 * @license http://opensource.org/licenses/bsd-license.php New BSD License
13 * @version CVS: $Id: CLI.php 313023 2011-07-06 19:17:11Z dufuz $
14 * @link http://pear.php.net/package/PEAR
15 * @since File available since Release 0.1
20 require_once 'PEAR/Frontend.php';
23 * Command-line Frontend for the PEAR Installer
26 * @author Stig Bakken <ssb@php.net>
27 * @author Greg Beaver <cellog@php.net>
28 * @copyright 1997-2009 The Authors
29 * @license http://opensource.org/licenses/bsd-license.php New BSD License
30 * @version Release: 1.9.4
31 * @link http://pear.php.net/package/PEAR
32 * @since Class available since Release 0.1
34 class PEAR_Frontend_CLI extends PEAR_Frontend
37 * What type of user interface this frontend is for.
42 var $lp = ''; // line prefix
44 var $params = array();
50 function PEAR_Frontend_CLI()
53 $term = getenv('TERM'); //(cox) $_ENV is empty for me in 4.1.1
54 if (function_exists('posix_isatty') && !posix_isatty(1)) {
55 // output is being redirected to a file or through a pipe
57 if (preg_match('/^(xterm|vt220|linux)/', $term)) {
58 $this->term['bold'] = sprintf("%c%c%c%c", 27, 91, 49, 109);
59 $this->term['normal'] = sprintf("%c%c%c", 27, 91, 109);
60 } elseif (preg_match('/^vt100/', $term)) {
61 $this->term['bold'] = sprintf("%c%c%c%c%c%c", 27, 91, 49, 109, 0, 0);
62 $this->term['normal'] = sprintf("%c%c%c%c%c", 27, 91, 109, 0, 0);
64 } elseif (OS_WINDOWS) {
65 // XXX add ANSI codes here
70 * @param object PEAR_Error object
72 function displayError($e)
74 return $this->_displayLine($e->getMessage());
78 * @param object PEAR_Error object
80 function displayFatalError($eobj)
82 $this->displayError($eobj);
83 if (class_exists('PEAR_Config')) {
84 $config = &PEAR_Config::singleton();
85 if ($config->get('verbose') > 5) {
86 if (function_exists('debug_print_backtrace')) {
87 debug_print_backtrace();
92 foreach (debug_backtrace() as $i => $frame) {
94 if (isset($frame['class'])
95 && strtolower($frame['class']) == 'pear'
96 && strtolower($frame['function']) == 'raiseerror'
104 $frame['class'] = !isset($frame['class']) ? '' : $frame['class'];
105 $frame['type'] = !isset($frame['type']) ? '' : $frame['type'];
106 $frame['function'] = !isset($frame['function']) ? '' : $frame['function'];
107 $frame['line'] = !isset($frame['line']) ? '' : $frame['line'];
108 $this->_displayLine("#$i: $frame[class]$frame[type]$frame[function] $frame[line]");
117 * Instruct the runInstallScript method to skip a paramgroup that matches the
118 * id value passed in.
120 * This method is useful for dynamically configuring which sections of a post-install script
121 * will be run based on the user's setup, which is very useful for making flexible
122 * post-install scripts without losing the cross-Frontend ability to retrieve user input
125 function skipParamgroup($id)
127 $this->_skipSections[$id] = true;
130 function runPostinstallScripts(&$scripts)
132 foreach ($scripts as $i => $script) {
133 $this->runInstallScript($scripts[$i]->_params, $scripts[$i]->_obj);
138 * @param array $xml contents of postinstallscript tag
139 * @param object $script post-installation script
140 * @param string install|upgrade
142 function runInstallScript($xml, &$script)
144 $this->_skipSections = array();
145 if (!is_array($xml) || !isset($xml['paramgroup'])) {
146 $script->run(array(), '_default');
150 $completedPhases = array();
151 if (!isset($xml['paramgroup'][0])) {
152 $xml['paramgroup'] = array($xml['paramgroup']);
155 foreach ($xml['paramgroup'] as $group) {
156 if (isset($this->_skipSections[$group['id']])) {
157 // the post-install script chose to skip this section dynamically
161 if (isset($group['name'])) {
162 $paramname = explode('::', $group['name']);
163 if ($lastgroup['id'] != $paramname[0]) {
167 $group['name'] = $paramname[1];
168 if (!isset($answers)) {
172 if (isset($answers[$group['name']])) {
173 switch ($group['conditiontype']) {
175 if ($answers[$group['name']] != $group['value']) {
180 if ($answers[$group['name']] == $group['value']) {
185 if (!@preg_match('/' . $group['value'] . '/',
186 $answers[$group['name']])) {
197 if (isset($group['instructions'])) {
198 $this->_display($group['instructions']);
201 if (!isset($group['param'][0])) {
202 $group['param'] = array($group['param']);
205 if (isset($group['param'])) {
206 if (method_exists($script, 'postProcessPrompts')) {
207 $prompts = $script->postProcessPrompts($group['param'], $group['id']);
208 if (!is_array($prompts) || count($prompts) != count($group['param'])) {
209 $this->outputData('postinstall', 'Error: post-install script did not ' .
210 'return proper post-processed prompts');
211 $prompts = $group['param'];
213 foreach ($prompts as $i => $var) {
214 if (!is_array($var) || !isset($var['prompt']) ||
215 !isset($var['name']) ||
216 ($var['name'] != $group['param'][$i]['name']) ||
217 ($var['type'] != $group['param'][$i]['type'])
219 $this->outputData('postinstall', 'Error: post-install script ' .
220 'modified the variables or prompts, severe security risk. ' .
221 'Will instead use the defaults from the package.xml');
222 $prompts = $group['param'];
227 $answers = $this->confirmDialog($prompts);
229 $answers = $this->confirmDialog($group['param']);
233 if ((isset($answers) && $answers) || !isset($group['param'])) {
234 if (!isset($answers)) {
238 array_unshift($completedPhases, $group['id']);
239 if (!$script->run($answers, $group['id'])) {
240 $script->run($completedPhases, '_undoOnError');
244 $script->run($completedPhases, '_undoOnError');
251 * Ask for user input, confirm the answers and continue until the user is satisfied
252 * @param array an array of arrays, format array('name' => 'paramname', 'prompt' =>
253 * 'text to display', 'type' => 'string'[, default => 'default value'])
256 function confirmDialog($params)
258 $answers = $prompts = $types = array();
259 foreach ($params as $param) {
260 $prompts[$param['name']] = $param['prompt'];
261 $types[$param['name']] = $param['type'];
262 $answers[$param['name']] = isset($param['default']) ? $param['default'] : '';
269 foreach ($answers as $var => $value) {
270 if (!strlen($value)) {
271 echo $this->bold("* Enter an answer for #" . $i . ": ({$prompts[$var]})\n");
277 $answers = $this->userDialog('', $prompts, $types, $answers);
279 } while (is_array($answers) && count(array_filter($answers)) != count($prompts));
284 function userDialog($command, $prompts, $types = array(), $defaults = array(), $screensize = 20)
286 if (!is_array($prompts)) {
290 $testprompts = array_keys($prompts);
294 if (count($prompts) === 1) {
295 foreach ($prompts as $key => $prompt) {
296 $type = $types[$key];
297 $default = @$defaults[$key];
304 $line = fgets(STDIN, 2048);
305 $result[$key] = ($default && trim($line) == '') ? $default : trim($line);
313 $descLength = max(array_map('strlen', $prompts));
314 $descFormat = "%-{$descLength}s";
315 $last = count($prompts);
318 foreach ($prompts as $n => $var) {
319 $res = isset($result[$n]) ? $result[$n] : null;
320 printf("%2d. $descFormat : %s\n", ++$i, $prompts[$n], $res);
322 print "\n1-$last, 'all', 'abort', or Enter to continue: ";
324 $tmp = trim(fgets(STDIN, 1024));
329 if ($tmp == 'abort') {
333 if (isset($testprompts[(int)$tmp - 1])) {
334 $var = $testprompts[(int)$tmp - 1];
335 $desc = $prompts[$var];
336 $current = @$result[$var];
337 print "$desc [$current] : ";
338 $tmp = trim(fgets(STDIN, 1024));
340 $result[$var] = $tmp;
342 } elseif ($tmp == 'all') {
343 foreach ($prompts as $var => $desc) {
344 $current = $result[$var];
345 print "$desc [$current] : ";
346 $tmp = trim(fgets(STDIN, 1024));
347 if (trim($tmp) !== '') {
348 $result[$var] = trim($tmp);
359 function userConfirm($prompt, $default = 'yes')
361 trigger_error("PEAR_Frontend_CLI::userConfirm not yet converted", E_USER_ERROR);
362 static $positives = array('y', 'yes', 'on', '1');
363 static $negatives = array('n', 'no', 'off', '0');
364 print "$this->lp$prompt [$default] : ";
365 $fp = fopen("php://stdin", "r");
366 $line = fgets($fp, 2048);
368 $answer = strtolower(trim($line));
369 if (empty($answer)) {
372 if (in_array($answer, $positives)) {
375 if (in_array($answer, $negatives)) {
378 if (in_array($default, $positives)) {
384 function outputData($data, $command = '_default')
388 foreach ($data as $type => $section) {
389 if ($type == 'main') {
390 $section['data'] = array_values($section['data']);
393 $this->outputData($section);
399 if (is_array($data) && isset($data['release_warnings'])) {
400 $this->_displayLine('');
401 $this->_startTable(array(
403 'caption' => 'Release Warnings'
405 $this->_tableRow(array($data['release_warnings']), null, array(1 => array('wrap' => 55)));
407 $this->_displayLine('');
410 $this->_displayLine(is_array($data) ? $data['data'] : $data);
413 $this->_startTable($data);
414 if (isset($data['headline']) && is_array($data['headline'])) {
415 $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55)));
419 foreach($data['data'] as $category) {
420 foreach($category as $name => $pkg) {
421 $packages[$pkg[0]] = $pkg;
425 $p = array_keys($packages);
427 foreach ($p as $name) {
428 $this->_tableRow($packages[$name], null, array(1 => array('wrap' => 55)));
434 if (!isset($data['data'])) {
435 $this->_displayLine('No packages in channel');
439 $this->_startTable($data);
440 if (isset($data['headline']) && is_array($data['headline'])) {
441 $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55)));
445 foreach($data['data'] as $category) {
446 foreach($category as $name => $pkg) {
447 $packages[$pkg[0]] = $pkg;
451 $p = array_keys($packages);
453 foreach ($p as $name) {
454 $pkg = $packages[$name];
455 unset($pkg[4], $pkg[5]);
456 $this->_tableRow($pkg, null, array(1 => array('wrap' => 55)));
462 $data['border'] = false;
464 0 => array('wrap' => 30),
465 1 => array('wrap' => 20),
466 2 => array('wrap' => 35)
469 $this->_startTable($data);
470 if (isset($data['headline']) && is_array($data['headline'])) {
471 $this->_tableRow($data['headline'], array('bold' => true), $opts);
474 foreach ($data['data'] as $group) {
475 foreach ($group as $value) {
476 if ($value[2] == '') {
477 $value[2] = "<not set>";
480 $this->_tableRow($value, null, $opts);
489 'caption' => 'Package details:',
492 array("Latest", $data['stable']),
493 array("Installed", $data['installed']),
494 array("Package", $data['name']),
495 array("License", $data['license']),
496 array("Category", $data['category']),
497 array("Summary", $data['summary']),
498 array("Description", $data['description']),
502 if (isset($d['deprecated']) && $d['deprecated']) {
503 $conf = &PEAR_Config::singleton();
504 $reg = $conf->getRegistry();
505 $name = $reg->parsedPackageNameToString($d['deprecated'], true);
506 $data['data'][] = array('Deprecated! use', $name);
509 if (is_array($data)) {
510 $this->_startTable($data);
511 $count = count($data['data'][0]);
513 $opts = array(0 => array('wrap' => 25),
514 1 => array('wrap' => 48)
516 } elseif ($count == 3) {
517 $opts = array(0 => array('wrap' => 30),
518 1 => array('wrap' => 20),
519 2 => array('wrap' => 35)
524 if (isset($data['headline']) && is_array($data['headline'])) {
525 $this->_tableRow($data['headline'],
526 array('bold' => true),
530 if (is_array($data['data'])) {
531 foreach($data['data'] as $row) {
532 $this->_tableRow($row, null, $opts);
535 $this->_tableRow(array($data['data']), null, $opts);
539 $this->_displayLine($data);
545 function log($text, $append_crlf = true)
548 return $this->_displayLine($text);
551 return $this->_display($text);
556 if (empty($this->term['bold'])) {
557 return strtoupper($text);
560 return $this->term['bold'] . $text . $this->term['normal'];
563 function _displayHeading($title)
565 print $this->lp.$this->bold($title)."\n";
566 print $this->lp.str_repeat("=", strlen($title))."\n";
569 function _startTable($params = array())
571 $params['table_data'] = array();
572 $params['widest'] = array(); // indexed by column
573 $params['highest'] = array(); // indexed by row
574 $params['ncols'] = 0;
575 $this->params = $params;
578 function _tableRow($columns, $rowparams = array(), $colparams = array())
581 for ($i = 0; $i < count($columns); $i++) {
582 $col = &$columns[$i];
583 if (isset($colparams[$i]) && !empty($colparams[$i]['wrap'])) {
584 $col = wordwrap($col, $colparams[$i]['wrap']);
587 if (strpos($col, "\n") !== false) {
588 $multiline = explode("\n", $col);
590 foreach ($multiline as $n => $line) {
591 $len = strlen($line);
596 $lines = count($multiline);
601 if (isset($this->params['widest'][$i])) {
602 if ($w > $this->params['widest'][$i]) {
603 $this->params['widest'][$i] = $w;
606 $this->params['widest'][$i] = $w;
609 $tmp = count_chars($columns[$i], 1);
610 // handle unix, mac and windows formats
611 $lines = (isset($tmp[10]) ? $tmp[10] : (isset($tmp[13]) ? $tmp[13] : 0)) + 1;
612 if ($lines > $highest) {
617 if (count($columns) > $this->params['ncols']) {
618 $this->params['ncols'] = count($columns);
623 'height' => $highest,
624 'rowparams' => $rowparams,
625 'colparams' => $colparams,
627 $this->params['table_data'][] = $new_row;
632 extract($this->params);
633 if (!empty($caption)) {
634 $this->_displayHeading($caption);
637 if (count($table_data) === 0) {
641 if (!isset($width)) {
644 for ($i = 0; $i < $ncols; $i++) {
645 if (!isset($width[$i])) {
646 $width[$i] = $widest[$i];
652 if (empty($border)) {
664 foreach ($width as $w) {
665 $borderline .= str_repeat('-', $w + strlen($cellstart) + strlen($cellend) - 1);
671 $this->_displayLine($borderline);
674 for ($i = 0; $i < count($table_data); $i++) {
675 extract($table_data[$i]);
676 if (!is_array($rowparams)) {
677 $rowparams = array();
680 if (!is_array($colparams)) {
681 $colparams = array();
686 for ($c = 0; $c < count($data); $c++) {
687 $rowlines[$c] = preg_split('/(\r?\n|\r)/', $data[$c]);
688 if (count($rowlines[$c]) < $height) {
689 $rowlines[$c] = array_pad($rowlines[$c], $height, '');
693 for ($c = 0; $c < count($data); $c++) {
694 $rowlines[$c] = array($data[$c]);
698 for ($r = 0; $r < $height; $r++) {
700 for ($c = 0; $c < count($data); $c++) {
701 if (isset($colparams[$c])) {
702 $attribs = array_merge($rowparams, $colparams);
704 $attribs = $rowparams;
707 $w = isset($width[$c]) ? $width[$c] : 0;
709 $cell = $rowlines[$c][$r];
712 $cell = substr($cell, 0, $w);
715 if (isset($attribs['bold'])) {
716 $cell = $this->bold($cell);
720 // not using str_pad here because we may
721 // add bold escape characters to $cell
722 $cell .= str_repeat(' ', $w - $l);
725 $rowtext .= $cellstart . $cell . $cellend;
729 $rowtext = rtrim($rowtext);
733 $this->_displayLine($rowtext);
738 $this->_displayLine($borderline);
742 function _displayLine($text)
744 print "$this->lp$text\n";
747 function _display($text)