From 3bc12ac2423f9e8af5d1481b9e997662d1e248f6 Mon Sep 17 00:00:00 2001 From: Stig Bakken Date: Sun, 12 May 2002 21:09:04 +0000 Subject: [PATCH] * refactored the command/options code: - now each command class should define a "commands" property with documentation, option specs etc. - both long and short options are now supported - after recent changes to Console_Getopt, you may now have options to commands even though the same option is also valid for the pear command itself - less CLI-centric, better suited to Gtk and Web frontends --- pear/PEAR/Command.php | 107 +++++++++++--------- pear/PEAR/Command/Common.php | 59 ++++++++++- pear/PEAR/Command/Install.php | 179 +++++++++++++++++++++++++--------- pear/PEAR/Installer.php | 15 +-- pear/scripts/pear.in | 52 ++++++++-- 5 files changed, 300 insertions(+), 112 deletions(-) diff --git a/pear/PEAR/Command.php b/pear/PEAR/Command.php index 7972f42eb5..6127a97099 100644 --- a/pear/PEAR/Command.php +++ b/pear/PEAR/Command.php @@ -27,6 +27,12 @@ require_once "PEAR.php"; */ $GLOBALS['_PEAR_Command_commandlist'] = array(); +/** + * Array of command objects + * @var array class => object + */ +$GLOBALS['_PEAR_Command_objects'] = array(); + /** * Which user interface class is being used. * @var string class name @@ -39,12 +45,6 @@ $GLOBALS['_PEAR_Command_uiclass'] = 'PEAR_Frontend_CLI'; */ $GLOBALS['_PEAR_Command_uiobject'] = null; -/** -* The options accepted by the commands -* @var string the options -*/ -$GLOBALS['_PEAR_Command_commandopts'] = ''; - /** * PEAR command class, a simple factory class for administrative * commands. @@ -81,8 +81,8 @@ $GLOBALS['_PEAR_Command_commandopts'] = ''; * - DON'T OUTPUT ANYTHING! Return text for output instead. * * - DON'T USE HTML! The text you return will be used from both Gtk, - * web and command-line interfaces, so for now keep everything to - * plain text. + * web and command-line interfaces, so for now, keep everything to + * plain text. There may be a common (XML) markup format later. * * - DON'T USE EXIT OR DIE! Always use pear errors. From static * classes do PEAR::raiseError(), from other classes do @@ -93,8 +93,8 @@ class PEAR_Command /** * Get the right object for executing a command. * - * @param object Instance of PEAR_Config object - * @param string The name of the command + * @param string $command The name of the command + * @param object $config Instance of PEAR_Config object * * @return object the command object or a PEAR error * @@ -105,12 +105,13 @@ class PEAR_Command if (empty($GLOBALS['_PEAR_Command_commandlist'])) { PEAR_Command::registerCommands(); } - if (isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { - $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; - $obj = &new $class(PEAR_Command::getFrontendObject(), $config); - return $obj; + $class = @$GLOBALS['_PEAR_Command_commandlist'][$command]; + if (empty($class)) { + return PEAR::raiseError("unknown command `$command'"); } - return PEAR::raiseError("unknown command `$command'"); + $ui = PEAR_Command::getFrontendObject(); + $obj = &new $class($ui, $config); + return $obj; } /** @@ -120,34 +121,44 @@ class PEAR_Command */ function &getFrontendObject() { - global $_PEAR_Command_uiclass, $_PEAR_Command_uiobject; - if (empty($_PEAR_Command_uiobject)) { - $_PEAR_Command_uiobject = &new $_PEAR_Command_uiclass; + if (empty($GLOBALS['_PEAR_Command_uiobject'])) { + $GLOBALS['_PEAR_Command_uiobject'] = &new $GLOBALS['_PEAR_Command_uiclass']; } - return $_PEAR_Command_uiobject; + return $GLOBALS['_PEAR_Command_uiobject']; } /** * Load current frontend class. * - * @param string Name of the frontend + * @param string $uiclass Name of class implementing the frontend * - * @return boolean TRUE if the frontend exists, otherwise FALSE. + * @return object the frontend object, or a PEAR error */ - function setFrontendClass($uiclass) + function &setFrontendClass($uiclass) { - $GLOBALS['_PEAR_Command_uiclass'] = $uiclass; - $file = str_replace("_", "/", $uiclass) . '.php'; - include_once $file; - return class_exists(strtolower($uiclass)); + $file = str_replace('_', '/', $uiclass) . '.php'; + @include_once $file; + if (class_exists(strtolower($uiclass))) { + $obj = &new $uiclass; + // quick test to see if this class implements a few of the most + // important frontend methods + if (method_exists($obj, 'displayLine') && method_exists($obj, 'userConfirm')) { + $GLOBALS['_PEAR_Command_uiobject'] = &$obj; + $GLOBALS['_PEAR_Command_uiclass'] = $uiclass; + return $obj; + } else { + return PEAR::raiseError("not a frontend class: $uiclass"); + } + } + return PEAR::raiseError("no such class: $uiclass"); } /** * Set current frontend. * - * @param string Name of the frontend type + * @param string $uitype Name of the frontend type (for example "CLI") * - * @return boolean TRUE if the frontend exists, otherwise FALSE. + * @return object the frontend object, or a PEAR error */ function setFrontendType($uitype) { @@ -179,32 +190,28 @@ class PEAR_Command } $dp = @opendir($dir); if (empty($dp)) { - return PEAR::raiseError("PEAR_Command::registerCommands: ". - "opendir($dir) failed"); + return PEAR::raiseError("registerCommands: opendir($dir) failed"); } if (!$merge) { $GLOBALS['_PEAR_Command_commandlist'] = array(); } - $cmdopts = array(); while ($entry = readdir($dp)) { - if ($entry{0} == '.' || substr($entry, -4) != '.php' || - $entry == 'Common.php') - { + if ($entry{0} == '.' || substr($entry, -4) != '.php' || $entry == 'Common.php') { continue; } $class = "PEAR_Command_".substr($entry, 0, -4); $file = "$dir/$entry"; include_once $file; // List of commands - $implements = call_user_func(array($class, "getCommands")); + if (empty($GLOBALS['_PEAR_Command_objects'][$class])) { + $GLOBALS['_PEAR_Command_objects'][$class] = &new $class($ui, $config); + } + $implements = $GLOBALS['_PEAR_Command_objects'][$class]->getCommands(); foreach ($implements as $command => $desc) { $GLOBALS['_PEAR_Command_commandlist'][$command] = $class; $GLOBALS['_PEAR_Command_commanddesc'][$command] = $desc; } - // List of options accepted - $cmdopts = array_merge($cmdopts, call_user_func(array($class, "getOptions"))); } - $GLOBALS['_PEAR_Command_commandopts'] = implode('', $cmdopts); return true; } @@ -225,19 +232,27 @@ class PEAR_Command } /** - * Get the list of currently supported options, and what - * classes implement them. + * Compiles arguments for getopt. * - * @return array array option => implementing class + * @param string $command command to get optstring for + * @param string $short_args (reference) short getopt format + * @param array $long_args (reference) long getopt format + * + * @return void * * @access public */ - function getOptions() + function getGetoptArgs($command, &$short_args, &$long_args) { if (empty($GLOBALS['_PEAR_Command_commandlist'])) { PEAR_Command::registerCommands(); } - return $GLOBALS['_PEAR_Command_commandopts']; + $class = @$GLOBALS['_PEAR_Command_commandlist'][$command]; + if (empty($class)) { + return null; + } + $obj = &$GLOBALS['_PEAR_Command_objects'][$class]; + return $obj->getGetoptArgs($command, $short_args, $long_args); } /** @@ -257,8 +272,7 @@ class PEAR_Command /** * Get help for command. * - * @param string Name of the command for which help should be - * called. + * @param string $command Name of the command to return help for * * @access public */ @@ -266,7 +280,8 @@ class PEAR_Command { $cmds = PEAR_Command::getCommands(); if (isset($cmds[$command])) { - return call_user_func(array($cmds[$command], 'getHelp'), $command); + $class = $cmds[$command]; + return $GLOBALS['_PEAR_Command_objects'][$class]->getHelp($command); } return false; } diff --git a/pear/PEAR/Command/Common.php b/pear/PEAR/Command/Common.php index c31c23680d..471930fa67 100644 --- a/pear/PEAR/Command/Common.php +++ b/pear/PEAR/Command/Common.php @@ -13,7 +13,7 @@ // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ -// | Author: Stig Bakken | +// | Author: Stig Sæther Bakken | // +----------------------------------------------------------------------+ // // $Id$ @@ -22,6 +22,8 @@ require_once "PEAR.php"; class PEAR_Command_Common extends PEAR { + // {{{ properties + /** * PEAR_Config object used to pass user system and configuration * on when executing commands @@ -36,6 +38,9 @@ class PEAR_Command_Common extends PEAR */ var $ui; + // }}} + // {{{ constructor + /** * PEAR_Command_Common constructor. * @@ -48,15 +53,59 @@ class PEAR_Command_Common extends PEAR $this->ui = &$ui; } - function getOptions() - { - return array(); - } + // }}} + + // {{{ getHelp() function getHelp($command) { return array(null, 'No help avaible yet'); } + + // }}} + // {{{ getGetoptArgs() + + function getGetoptArgs($command, &$short_args, &$long_args) + { + $short_args = ""; + $long_args = array(); + if (empty($this->commands[$command])) { + return; + } + reset($this->commands[$command]); + while (list($option, $info) = each($this->commands[$command]['options'])) { + $larg = $sarg = ''; + if (isset($info['arg'])) { + if ($info['arg']{0} == '(') { + $larg = '=='; + $sarg = '::'; + $arg = substr($info['arg'], 1, -1); + } else { + $larg = '='; + $sarg = ':'; + $arg = $info['arg']; + } + } + if (isset($info['shortopt'])) { + $short_args .= $info['shortopt'] . $sarg; + } + $long_args[] = $option . $larg; + } + } + + // }}} + // {{{ run() + + function run($command, $options, $params) + { + $func = @$this->commands[$command]['function']; + if (empty($func)) { + return $this->raiseError("unknown command `$command'"); + } + return $this->$func($command, $options, $params); + } + + // }}} } ?> \ No newline at end of file diff --git a/pear/PEAR/Command/Install.php b/pear/PEAR/Command/Install.php index 6436ab4208..ee6a8fd349 100644 --- a/pear/PEAR/Command/Install.php +++ b/pear/PEAR/Command/Install.php @@ -13,13 +13,14 @@ // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ -// | Author: Stig Bakken | +// | Author: Stig Sæther Bakken | // +----------------------------------------------------------------------+ // // $Id$ require_once "PEAR/Command/Common.php"; require_once "PEAR/Installer.php"; +require_once "Console/Getopt.php"; /** * PEAR commands for installation or deinstallation/upgrading of @@ -28,6 +29,109 @@ require_once "PEAR/Installer.php"; */ class PEAR_Command_Install extends PEAR_Command_Common { + // {{{ command definitions + + var $commands = array( + 'install' => array( + 'summary' => 'Install Package', + 'function' => 'doInstall', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'will overwrite newer installed packages', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, install anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as installed', + ), + 'soft' => array( + 'shortopt' => 's', + 'doc' => 'soft install, fail silently, or upgrade if already installed', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + ), + 'doc' => 'Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package" : queries your configured server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. +'), + 'upgrade' => array( + 'summary' => 'Upgrade Package', + 'function' => 'doInstall', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'overwrite newer installed packages', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'request uncompressed files when downloading', + ), + ), + 'doc' => 'Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. +'), + 'uninstall' => array( + 'summary' => 'Un-install Package', + 'function' => 'doUninstall', + 'options' => array( + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, uninstall anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not remove files, only register the packages as not installed', + ), + ), + 'doc' => 'Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. +'), + + ); + + // }}} // {{{ constructor /** @@ -51,11 +155,16 @@ class PEAR_Command_Install extends PEAR_Command_Common */ function getCommands() { - return array('install' => 'Install Package', - 'uninstall' => 'Uninstall Package', - 'upgrade' => 'Upgrade Package'); + $ret = array(); + foreach (array_keys($this->commands) as $command) { + $ret[$command] = $this->commands[$command]['summary']; + } + return $ret; } + // }}} + // {{{ getHelp() + function getHelp($command) { switch ($command) { @@ -83,61 +192,41 @@ class PEAR_Command_Install extends PEAR_Command_Common return $ret; } - // }}} - // {{{ getOptions() - - function getOptions() - { - return array('f', 'n', 'r', 's', 'Z'); - } - // }}} // {{{ run() function run($command, $options, $params) { - $installer = &new PEAR_Installer($this->ui); - + $this->installer = &new PEAR_Installer($ui); +// return parent::run($command, $options, $params); $failmsg = ''; - $opts = array(); - if (isset($options['f'])) { - $opts['force'] = true; - } - if (isset($options['n'])) { - $opts['nodeps'] = true; - } - if (isset($options['r'])) { - $opts['register_only'] = true; - } - if (isset($options['s'])) { - $opts['soft'] = true; - } - if (isset($options['Z'])) { - $opts['nocompress'] = true; - } switch ($command) { case 'upgrade': - $opts['upgrade'] = true; + $options['upgrade'] = true; // fall through - case 'install': { - if ($installer->install(@$params[0], $opts, $this->config)) { - $this->ui->displayLine("$command ok"); - } else { - $failmsg = "$command failed"; + case 'install': + foreach ($params as $pkg) { + $bn = basename($pkg); + $info = $this->installer->install($pkg, $options, $this->config); + if (is_array($info)) { + $label = "$info[package] $info[version]"; + $this->ui->displayLine("$command ok: $label"); + } else { + $failmsg = "$command failed"; + } } break; - } - case 'uninstall': { - if ($installer->uninstall($params[0], $options)) { - $this->ui->displayLine("uninstall ok"); - } else { - $failmsg = "uninstall failed"; + case 'uninstall': + foreach ($params as $pkg) { + if ($this->installer->uninstall($pkg, $options)) { + $this->ui->displayLine("uninstall ok"); + } else { + $failmsg = "uninstall failed"; + } } break; - } - default: { + default: return false; - } } if ($failmsg) { return $this->raiseError($failmsg); diff --git a/pear/PEAR/Installer.php b/pear/PEAR/Installer.php index 92191ee894..0bbea5365a 100644 --- a/pear/PEAR/Installer.php +++ b/pear/PEAR/Installer.php @@ -271,13 +271,13 @@ class PEAR_Installer extends PEAR_Common * * @param $pkgfile path to the package file * - * @return bool true if successful, false if not + * @return array package info if successful, null if not */ function install($pkgfile, $options = array()) { // recognized options: - // - register_only : update registry but don't install files + // - register-only : update registry but don't install files // - upgrade : upgrade existing install // - soft : fail silently // @@ -379,7 +379,7 @@ class PEAR_Installer extends PEAR_Common if (empty($options['soft'])) { $this->log(0, $error); } - return $this->raiseError('Dependencies failed'); + return $this->raiseError("$pkgname: dependencies failed"); } } @@ -399,7 +399,7 @@ class PEAR_Installer extends PEAR_Common if (empty($options['force']) && !version_compare($v2, $v1, 'gt')) { return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)"); } - if (empty($options['register_only'])) { + if (empty($options['register-only'])) { // when upgrading, remove old release's files first: if (PEAR::isError($err = $this->_deletePackageFiles($pkgname))) { return $this->raiseError($err); @@ -411,7 +411,7 @@ class PEAR_Installer extends PEAR_Common // info from the package it self we want to access from _installFile $this->pkginfo = $pkginfo; - if (empty($options['register_only'])) { + if (empty($options['register-only'])) { if (!is_dir($this->config->get('php_dir'))) { return $this->raiseError("no script destination directory\n", null, PEAR_ERROR_DIE); @@ -446,7 +446,10 @@ class PEAR_Installer extends PEAR_Common } else { $ret = $this->registry->updatePackage($pkgname, $this->pkginfo, false); } - return $ret; + if (!$ret) { + return null; + } + return $pkginfo; } // }}} diff --git a/pear/scripts/pear.in b/pear/scripts/pear.in index f1d7a4e558..c68e79ef1d 100644 --- a/pear/scripts/pear.in +++ b/pear/scripts/pear.in @@ -32,12 +32,10 @@ require_once 'Console/Getopt.php'; PEAR_Command::setFrontendType('CLI'); $all_commands = PEAR_Command::getCommands(); -$cmd_options = PEAR_Command::getOptions(); -$progname = basename(__FILE__); $argv = Console_Getopt::readPHPArgv(); -array_shift($argv); -$options = Console_Getopt::getopt($argv, "c:C:d:D:Gh?sSqu:v" . $cmd_options); +$progname = basename(array_shift($argv)); +$options = Console_Getopt::getopt($argv, "c:C:d:D:Gh?sSqu:v"); if (PEAR::isError($options)) { usage($options); } @@ -45,9 +43,13 @@ if (PEAR::isError($options)) { $opts = $options[0]; $fetype = 'CLI'; -foreach ($opts as $opt) { - if ($opt[0] == 'G') { - $fetype = 'Gtk'; +if ($progname == 'gpear' || $progname == 'pear-gtk') { + $fetype = 'Gtk'; +} else { + foreach ($opts as $opt) { + if ($opt[0] == 'G') { + $fetype = 'Gtk'; + } } } PEAR_Command::setFrontendType($fetype); @@ -116,7 +118,7 @@ if ($store_user_config) { $config->store('user'); } -$command = (isset($options[1][0])) ? $options[1][0] : null; +$command = (isset($options[1][0])) ? array_shift($options[1]) : null; if (empty($command) && ($store_user_config || $store_system_config)) { exit; @@ -133,9 +135,35 @@ if ($fetype == 'Gtk') { if (PEAR::isError($cmd)) { die($cmd->getMessage()); } + + $short_args = $long_args = array(); + PEAR_Command::getGetoptArgs($command, $short_args, $long_args); + if (PEAR::isError($tmp = Console_Getopt::getopt($params, $short_args, $long_args))) { + return $this->raiseError($tmp); + } + list($tmpopt, $params) = $tmp; + $options = array(); + foreach ($tmpopt as $foo => $tmp2) { + list($opt, $value) = $tmp2; + if ($value === null) { + $value = true; + } + if (strlen($opt) == 1) { + foreach ($this->commands[$command]['options'] as $o => $d) { + if (@$d['shortopt'] == $opt) { + $options[$o] = $value; + } + } + } else { + if (substr($opt, 0, 2) == '--') { + $options[substr($opt, 2)] = $value; + } + } + } + - $cmdargs = array_slice($options[1], 1); - $ok = $cmd->run($command, $cmdopts, $cmdargs); + + $ok = $cmd->run($command, $options[1]); if ($ok === false) { PEAR::raiseError("unknown command `$command'"); } @@ -190,6 +218,10 @@ function cmdHelp($command) " -u foo unset `foo' in the user configuration\n". " -h, -? display help/usage (this message)\n"; } elseif ($help = PEAR_Command::getHelp($command)) { + if (is_string($help)) { + $help = preg_replace('/{config\s+([^\}]+)}/e', "\$config->get('\1')", $help); + return "Usage : $help"; + } return "Usage : $progname $command {$help[0]}\n{$help[1]}"; } return "No such command"; -- 2.40.0