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 * @link http://pear.php.net/package/PEAR
14 * @since File available since Release 0.1
19 require_once 'PEAR/Frontend.php';
22 * Command-line Frontend for the PEAR Installer
25 * @author Stig Bakken <ssb@php.net>
26 * @author Greg Beaver <cellog@php.net>
27 * @copyright 1997-2009 The Authors
28 * @license http://opensource.org/licenses/bsd-license.php New BSD License
29 * @version Release: 1.10.1
30 * @link http://pear.php.net/package/PEAR
31 * @since Class available since Release 0.1
33 class PEAR_Frontend_CLI extends PEAR_Frontend
36 * What type of user interface this frontend is for.
41 var $lp = ''; // line prefix
43 var $params = array();
49 function __construct()
51 parent::__construct();
52 $term = getenv('TERM'); //(cox) $_ENV is empty for me in 4.1.1
53 if (function_exists('posix_isatty') && !posix_isatty(1)) {
54 // output is being redirected to a file or through a pipe
56 if (preg_match('/^(xterm|vt220|linux)/', $term)) {
57 $this->term['bold'] = sprintf("%c%c%c%c", 27, 91, 49, 109);
58 $this->term['normal'] = sprintf("%c%c%c", 27, 91, 109);
59 } elseif (preg_match('/^vt100/', $term)) {
60 $this->term['bold'] = sprintf("%c%c%c%c%c%c", 27, 91, 49, 109, 0, 0);
61 $this->term['normal'] = sprintf("%c%c%c%c%c", 27, 91, 109, 0, 0);
63 } elseif (OS_WINDOWS) {
64 // XXX add ANSI codes here
69 * @param object PEAR_Error object
71 function displayError($e)
73 return $this->_displayLine($e->getMessage());
77 * @param object PEAR_Error object
79 function displayFatalError($eobj)
81 $this->displayError($eobj);
82 if (class_exists('PEAR_Config')) {
83 $config = &PEAR_Config::singleton();
84 if ($config->get('verbose') > 5) {
85 if (function_exists('debug_print_backtrace')) {
86 debug_print_backtrace();
91 foreach (debug_backtrace() as $i => $frame) {
93 if (isset($frame['class'])
94 && strtolower($frame['class']) == 'pear'
95 && strtolower($frame['function']) == 'raiseerror'
103 $frame['class'] = !isset($frame['class']) ? '' : $frame['class'];
104 $frame['type'] = !isset($frame['type']) ? '' : $frame['type'];
105 $frame['function'] = !isset($frame['function']) ? '' : $frame['function'];
106 $frame['line'] = !isset($frame['line']) ? '' : $frame['line'];
107 $this->_displayLine("#$i: $frame[class]$frame[type]$frame[function] $frame[line]");
116 * Instruct the runInstallScript method to skip a paramgroup that matches the
117 * id value passed in.
119 * This method is useful for dynamically configuring which sections of a post-install script
120 * will be run based on the user's setup, which is very useful for making flexible
121 * post-install scripts without losing the cross-Frontend ability to retrieve user input
124 function skipParamgroup($id)
126 $this->_skipSections[$id] = true;
129 function runPostinstallScripts(&$scripts)
131 foreach ($scripts as $i => $script) {
132 $this->runInstallScript($scripts[$i]->_params, $scripts[$i]->_obj);
137 * @param array $xml contents of postinstallscript tag
138 * @param object $script post-installation script
139 * @param string install|upgrade
141 function runInstallScript($xml, &$script)
143 $this->_skipSections = array();
144 if (!is_array($xml) || !isset($xml['paramgroup'])) {
145 $script->run(array(), '_default');
149 $completedPhases = array();
150 if (!isset($xml['paramgroup'][0])) {
151 $xml['paramgroup'] = array($xml['paramgroup']);
154 foreach ($xml['paramgroup'] as $group) {
155 if (isset($this->_skipSections[$group['id']])) {
156 // the post-install script chose to skip this section dynamically
160 if (isset($group['name'])) {
161 $paramname = explode('::', $group['name']);
162 if ($lastgroup['id'] != $paramname[0]) {
166 $group['name'] = $paramname[1];
167 if (!isset($answers)) {
171 if (isset($answers[$group['name']])) {
172 switch ($group['conditiontype']) {
174 if ($answers[$group['name']] != $group['value']) {
179 if ($answers[$group['name']] == $group['value']) {
184 if (!@preg_match('/' . $group['value'] . '/',
185 $answers[$group['name']])) {
196 if (isset($group['instructions'])) {
197 $this->_display($group['instructions']);
200 if (!isset($group['param'][0])) {
201 $group['param'] = array($group['param']);
204 if (isset($group['param'])) {
205 if (method_exists($script, 'postProcessPrompts')) {
206 $prompts = $script->postProcessPrompts($group['param'], $group['id']);
207 if (!is_array($prompts) || count($prompts) != count($group['param'])) {
208 $this->outputData('postinstall', 'Error: post-install script did not ' .
209 'return proper post-processed prompts');
210 $prompts = $group['param'];
212 foreach ($prompts as $i => $var) {
213 if (!is_array($var) || !isset($var['prompt']) ||
214 !isset($var['name']) ||
215 ($var['name'] != $group['param'][$i]['name']) ||
216 ($var['type'] != $group['param'][$i]['type'])
218 $this->outputData('postinstall', 'Error: post-install script ' .
219 'modified the variables or prompts, severe security risk. ' .
220 'Will instead use the defaults from the package.xml');
221 $prompts = $group['param'];
226 $answers = $this->confirmDialog($prompts);
228 $answers = $this->confirmDialog($group['param']);
232 if ((isset($answers) && $answers) || !isset($group['param'])) {
233 if (!isset($answers)) {
237 array_unshift($completedPhases, $group['id']);
238 if (!$script->run($answers, $group['id'])) {
239 $script->run($completedPhases, '_undoOnError');
243 $script->run($completedPhases, '_undoOnError');
250 * Ask for user input, confirm the answers and continue until the user is satisfied
251 * @param array an array of arrays, format array('name' => 'paramname', 'prompt' =>
252 * 'text to display', 'type' => 'string'[, default => 'default value'])
255 function confirmDialog($params)
257 $answers = $prompts = $types = array();
258 foreach ($params as $param) {
259 $prompts[$param['name']] = $param['prompt'];
260 $types[$param['name']] = $param['type'];
261 $answers[$param['name']] = isset($param['default']) ? $param['default'] : '';
268 foreach ($answers as $var => $value) {
269 if (!strlen($value)) {
270 echo $this->bold("* Enter an answer for #" . $i . ": ({$prompts[$var]})\n");
276 $answers = $this->userDialog('', $prompts, $types, $answers);
278 } while (is_array($answers) && count(array_filter($answers)) != count($prompts));
283 function userDialog($command, $prompts, $types = array(), $defaults = array(), $screensize = 20)
285 if (!is_array($prompts)) {
289 $testprompts = array_keys($prompts);
293 if (count($prompts) === 1) {
294 foreach ($prompts as $key => $prompt) {
295 $type = $types[$key];
296 $default = @$defaults[$key];
303 $line = fgets(STDIN, 2048);
304 $result[$key] = ($default && trim($line) == '') ? $default : trim($line);
312 $descLength = max(array_map('strlen', $prompts));
313 $descFormat = "%-{$descLength}s";
314 $last = count($prompts);
317 foreach ($prompts as $n => $var) {
318 $res = isset($result[$n]) ? $result[$n] : null;
319 printf("%2d. $descFormat : %s\n", ++$i, $prompts[$n], $res);
321 print "\n1-$last, 'all', 'abort', or Enter to continue: ";
323 $tmp = trim(fgets(STDIN, 1024));
328 if ($tmp == 'abort') {
332 if (isset($testprompts[(int)$tmp - 1])) {
333 $var = $testprompts[(int)$tmp - 1];
334 $desc = $prompts[$var];
335 $current = @$result[$var];
336 print "$desc [$current] : ";
337 $tmp = trim(fgets(STDIN, 1024));
339 $result[$var] = $tmp;
341 } elseif ($tmp == 'all') {
342 foreach ($prompts as $var => $desc) {
343 $current = $result[$var];
344 print "$desc [$current] : ";
345 $tmp = trim(fgets(STDIN, 1024));
346 if (trim($tmp) !== '') {
347 $result[$var] = trim($tmp);
358 function userConfirm($prompt, $default = 'yes')
360 trigger_error("PEAR_Frontend_CLI::userConfirm not yet converted", E_USER_ERROR);
361 static $positives = array('y', 'yes', 'on', '1');
362 static $negatives = array('n', 'no', 'off', '0');
363 print "$this->lp$prompt [$default] : ";
364 $fp = fopen("php://stdin", "r");
365 $line = fgets($fp, 2048);
367 $answer = strtolower(trim($line));
368 if (empty($answer)) {
371 if (in_array($answer, $positives)) {
374 if (in_array($answer, $negatives)) {
377 if (in_array($default, $positives)) {
383 function outputData($data, $command = '_default')
387 foreach ($data as $type => $section) {
388 if ($type == 'main') {
389 $section['data'] = array_values($section['data']);
392 $this->outputData($section);
398 if (is_array($data) && isset($data['release_warnings'])) {
399 $this->_displayLine('');
400 $this->_startTable(array(
402 'caption' => 'Release Warnings'
404 $this->_tableRow(array($data['release_warnings']), null, array(1 => array('wrap' => 55)));
406 $this->_displayLine('');
409 $this->_displayLine(is_array($data) ? $data['data'] : $data);
412 $this->_startTable($data);
413 if (isset($data['headline']) && is_array($data['headline'])) {
414 $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55)));
418 foreach($data['data'] as $category) {
419 foreach($category as $name => $pkg) {
420 $packages[$pkg[0]] = $pkg;
424 $p = array_keys($packages);
426 foreach ($p as $name) {
427 $this->_tableRow($packages[$name], null, array(1 => array('wrap' => 55)));
433 if (!isset($data['data'])) {
434 $this->_displayLine('No packages in channel');
438 $this->_startTable($data);
439 if (isset($data['headline']) && is_array($data['headline'])) {
440 $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55)));
444 foreach($data['data'] as $category) {
445 foreach($category as $name => $pkg) {
446 $packages[$pkg[0]] = $pkg;
450 $p = array_keys($packages);
452 foreach ($p as $name) {
453 $pkg = $packages[$name];
454 unset($pkg[4], $pkg[5]);
455 $this->_tableRow($pkg, null, array(1 => array('wrap' => 55)));
461 $data['border'] = false;
463 0 => array('wrap' => 30),
464 1 => array('wrap' => 20),
465 2 => array('wrap' => 35)
468 $this->_startTable($data);
469 if (isset($data['headline']) && is_array($data['headline'])) {
470 $this->_tableRow($data['headline'], array('bold' => true), $opts);
473 foreach ($data['data'] as $group) {
474 foreach ($group as $value) {
475 if ($value[2] == '') {
476 $value[2] = "<not set>";
479 $this->_tableRow($value, null, $opts);
488 'caption' => 'Package details:',
491 array("Latest", $data['stable']),
492 array("Installed", $data['installed']),
493 array("Package", $data['name']),
494 array("License", $data['license']),
495 array("Category", $data['category']),
496 array("Summary", $data['summary']),
497 array("Description", $data['description']),
501 if (isset($d['deprecated']) && $d['deprecated']) {
502 $conf = &PEAR_Config::singleton();
503 $reg = $conf->getRegistry();
504 $name = $reg->parsedPackageNameToString($d['deprecated'], true);
505 $data['data'][] = array('Deprecated! use', $name);
508 if (is_array($data)) {
509 $this->_startTable($data);
510 $count = count($data['data'][0]);
512 $opts = array(0 => array('wrap' => 25),
513 1 => array('wrap' => 48)
515 } elseif ($count == 3) {
516 $opts = array(0 => array('wrap' => 30),
517 1 => array('wrap' => 20),
518 2 => array('wrap' => 35)
523 if (isset($data['headline']) && is_array($data['headline'])) {
524 $this->_tableRow($data['headline'],
525 array('bold' => true),
529 if (is_array($data['data'])) {
530 foreach($data['data'] as $row) {
531 $this->_tableRow($row, null, $opts);
534 $this->_tableRow(array($data['data']), null, $opts);
538 $this->_displayLine($data);
544 function log($text, $append_crlf = true)
547 return $this->_displayLine($text);
550 return $this->_display($text);
555 if (empty($this->term['bold'])) {
556 return strtoupper($text);
559 return $this->term['bold'] . $text . $this->term['normal'];
562 function _displayHeading($title)
564 print $this->lp.$this->bold($title)."\n";
565 print $this->lp.str_repeat("=", strlen($title))."\n";
568 function _startTable($params = array())
570 $params['table_data'] = array();
571 $params['widest'] = array(); // indexed by column
572 $params['highest'] = array(); // indexed by row
573 $params['ncols'] = 0;
574 $this->params = $params;
577 function _tableRow($columns, $rowparams = array(), $colparams = array())
580 for ($i = 0; $i < count($columns); $i++) {
581 $col = &$columns[$i];
582 if (isset($colparams[$i]) && !empty($colparams[$i]['wrap'])) {
583 $col = wordwrap($col, $colparams[$i]['wrap']);
586 if (strpos($col, "\n") !== false) {
587 $multiline = explode("\n", $col);
589 foreach ($multiline as $n => $line) {
590 $len = strlen($line);
595 $lines = count($multiline);
600 if (isset($this->params['widest'][$i])) {
601 if ($w > $this->params['widest'][$i]) {
602 $this->params['widest'][$i] = $w;
605 $this->params['widest'][$i] = $w;
608 $tmp = count_chars($columns[$i], 1);
609 // handle unix, mac and windows formats
610 $lines = (isset($tmp[10]) ? $tmp[10] : (isset($tmp[13]) ? $tmp[13] : 0)) + 1;
611 if ($lines > $highest) {
616 if (count($columns) > $this->params['ncols']) {
617 $this->params['ncols'] = count($columns);
622 'height' => $highest,
623 'rowparams' => $rowparams,
624 'colparams' => $colparams,
626 $this->params['table_data'][] = $new_row;
631 extract($this->params);
632 if (!empty($caption)) {
633 $this->_displayHeading($caption);
636 if (count($table_data) === 0) {
640 if (!isset($width)) {
643 for ($i = 0; $i < $ncols; $i++) {
644 if (!isset($width[$i])) {
645 $width[$i] = $widest[$i];
651 if (empty($border)) {
663 foreach ($width as $w) {
664 $borderline .= str_repeat('-', $w + strlen($cellstart) + strlen($cellend) - 1);
670 $this->_displayLine($borderline);
673 for ($i = 0; $i < count($table_data); $i++) {
674 extract($table_data[$i]);
675 if (!is_array($rowparams)) {
676 $rowparams = array();
679 if (!is_array($colparams)) {
680 $colparams = array();
685 for ($c = 0; $c < count($data); $c++) {
686 $rowlines[$c] = preg_split('/(\r?\n|\r)/', $data[$c]);
687 if (count($rowlines[$c]) < $height) {
688 $rowlines[$c] = array_pad($rowlines[$c], $height, '');
692 for ($c = 0; $c < count($data); $c++) {
693 $rowlines[$c] = array($data[$c]);
697 for ($r = 0; $r < $height; $r++) {
699 for ($c = 0; $c < count($data); $c++) {
700 if (isset($colparams[$c])) {
701 $attribs = array_merge($rowparams, $colparams);
703 $attribs = $rowparams;
706 $w = isset($width[$c]) ? $width[$c] : 0;
708 $cell = $rowlines[$c][$r];
711 $cell = substr($cell, 0, $w);
714 if (isset($attribs['bold'])) {
715 $cell = $this->bold($cell);
719 // not using str_pad here because we may
720 // add bold escape characters to $cell
721 $cell .= str_repeat(' ', $w - $l);
724 $rowtext .= $cellstart . $cell . $cellend;
728 $rowtext = rtrim($rowtext);
732 $this->_displayLine($rowtext);
737 $this->_displayLine($borderline);
741 function _displayLine($text)
743 print "$this->lp$text\n";
746 function _display($text)