123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587 |
- <?php
- /*
- * This file is part of the symfony package.
- * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- /**
- * sfCommandApplication manages the lifecycle of a CLI application.
- *
- * @package symfony
- * @subpackage command
- * @author Fabien Potencier <fabien.potencier@symfony-project.com>
- * @version SVN: $Id: sfCommandApplication.class.php 11505 2008-09-13 09:22:23Z fabien $
- */
- abstract class sfCommandApplication
- {
- protected
- $commandManager = null,
- $trace = false,
- $verbose = true,
- $dryrun = false,
- $nowrite = false,
- $name = 'UNKNOWN',
- $version = 'UNKNOWN',
- $tasks = array(),
- $currentTask = null,
- $dispatcher = null,
- $options = array(),
- $formatter = null;
- /**
- * Constructor.
- *
- * @param sfEventDispatcher $dispatcher A sfEventDispatcher instance
- * @param sfFormatter $formatter A sfFormatter instance
- * @param array $options An array of options
- */
- public function __construct(sfEventDispatcher $dispatcher, sfFormatter $formatter, $options = array())
- {
- $this->dispatcher = $dispatcher;
- $this->formatter = $formatter;
- $this->options = $options;
- $this->fixCgi();
- $this->configure();
- $this->registerTasks();
- }
- /**
- * Configures the current command application.
- */
- abstract public function configure();
- /**
- * Returns the value of a given option.
- *
- * @param string $name The option name
- *
- * @return mixed The option value
- */
- public function getOption($name)
- {
- return isset($this->options[$name]) ? $this->options[$name] : null;
- }
- /**
- * Returns the formatter instance.
- *
- * @return object The formatter instance
- */
- public function getFormatter()
- {
- return $this->formatter;
- }
- /**
- * Registers an array of task objects.
- *
- * If you pass null, this method will register all available tasks.
- *
- * @param array $tasks An array of tasks
- */
- public function registerTasks($tasks = null)
- {
- if (is_null($tasks))
- {
- $tasks = array();
- foreach (get_declared_classes() as $class)
- {
- $r = new Reflectionclass($class);
- if ($r->isSubclassOf('sfTask') && !$r->isAbstract())
- {
- $tasks[] = new $class($this->dispatcher, $this->formatter);
- }
- }
- }
- foreach ($tasks as $task)
- {
- $this->registerTask($task);
- }
- }
- /**
- * Registers a task object.
- *
- * @param sfTask $task An sfTask object
- */
- public function registerTask(sfTask $task)
- {
- if (isset($this->tasks[$task->getFullName()]))
- {
- throw new sfCommandException(sprintf('The task named "%s" in "%s" task is already registered by the "%s" task.', $task->getFullName(), get_class($task), get_class($this->tasks[$task->getFullName()])));
- }
- $this->tasks[$task->getFullName()] = $task;
- foreach ($task->getAliases() as $alias)
- {
- if (isset($this->tasks[$alias]))
- {
- throw new sfCommandException(sprintf('A task named "%s" is already registered.', $alias));
- }
- $this->tasks[$alias] = $task;
- }
- }
- /**
- * Returns all registered tasks.
- *
- * @return array An array of sfTask objects
- */
- public function getTasks()
- {
- return $this->tasks;
- }
- /**
- * Returns a registered task by name or alias.
- *
- * @param string $name The task name or alias
- *
- * @return sfTask An sfTask object
- */
- public function getTask($name)
- {
- if (!isset($this->tasks[$name]))
- {
- throw new sfCommandException(sprintf('The task "%s" does not exist.', $name));
- }
- return $this->tasks[$name];
- }
- /**
- * Runs the current application.
- *
- * @param mixed $options The command line options
- */
- public function run($options = null)
- {
- $this->handleOptions($options);
- $arguments = $this->commandManager->getArgumentValues();
- $this->currentTask = $this->getTaskToExecute($arguments['task']);
- $ret = $this->currentTask->runFromCLI($this->commandManager, $this->commandOptions);
- $this->currentTask = null;
- return $ret;
- }
- /**
- * Gets the name of the application.
- *
- * @return string The application name
- */
- public function getName()
- {
- return $this->name;
- }
- /**
- * Sets the application name.
- *
- * @param string $name The application name
- */
- public function setName($name)
- {
- $this->name = $name;
- }
- /**
- * Gets the application version.
- *
- * @return string The application version
- */
- public function getVersion()
- {
- return $this->version;
- }
- /**
- * Sets the application version.
- *
- * @param string $version The application version
- */
- public function setVersion($version)
- {
- $this->version = $version;
- }
- /**
- * Returns the long version of the application.
- *
- * @return string The long application version
- */
- public function getLongVersion()
- {
- return sprintf('%s version %s', $this->getName(), $this->formatter->format($this->getVersion(), 'INFO'))."\n";
- }
- /**
- * Returns whether the application must be verbose.
- *
- * @return Boolean true if the application must be verbose, false otherwise
- */
- public function isVerbose()
- {
- return $this->verbose;
- }
- /**
- * Returns whether the application must activate the trace.
- *
- * @return Boolean true if the application must activate the trace, false otherwise
- */
- public function withTrace()
- {
- return $this->trace;
- }
- /*
- * Returns whether the application must run in dry mode.
- *
- * @return Boolean true if the application must run in dry mode, false otherwise
- */
- public function isDryrun()
- {
- return $this->dryrun;
- }
- /**
- * Outputs a help message for the current application.
- */
- public function help()
- {
- $messages = array(
- $this->formatter->format('Usage:', 'COMMENT'),
- sprintf(" %s [options] task_name [arguments]\n", $this->getName()),
- $this->formatter->format('Options:', 'COMMENT'),
- );
- foreach ($this->commandManager->getOptionSet()->getOptions() as $option)
- {
- $messages[] = sprintf(' %-24s %s %s', $this->formatter->format('--'.$option->getName(), 'INFO'), $this->formatter->format('-'.$option->getShortcut(), 'INFO'), $option->getHelp());
- }
- $this->dispatcher->notify(new sfEvent($this, 'command.log', $messages));
- }
- /**
- * Parses and handles command line options.
- *
- * @param mixed $options The command line options
- */
- protected function handleOptions($options = null)
- {
- $argumentSet = new sfCommandArgumentSet(array(
- new sfCommandArgument('task', sfCommandArgument::REQUIRED, 'The task to execute'),
- ));
- $optionSet = new sfCommandOptionSet(array(
- new sfCommandOption('--dry-run', '-n', sfCommandOption::PARAMETER_NONE, 'Do a dry run without executing actions.'),
- new sfCommandOption('--help', '-H', sfCommandOption::PARAMETER_NONE, 'Display this help message.'),
- new sfCommandOption('--quiet', '-q', sfCommandOption::PARAMETER_NONE, 'Do not log messages to standard output.'),
- new sfCommandOption('--trace', '-t', sfCommandOption::PARAMETER_NONE, 'Turn on invoke/execute tracing, enable full backtrace.'),
- new sfCommandOption('--version', '-V', sfCommandOption::PARAMETER_NONE, 'Display the program version.'),
- ));
- $this->commandManager = new sfCommandManager($argumentSet, $optionSet);
- $this->commandManager->process($options);
- foreach ($this->commandManager->getOptionValues() as $opt => $value)
- {
- if (false === $value)
- {
- continue;
- }
- switch ($opt)
- {
- case 'dry-run':
- $this->verbose = true;
- $this->nowrite = true;
- $this->dryrun = true;
- $this->trace = true;
- break;
- case 'help':
- $this->help();
- exit();
- case 'quiet':
- $this->verbose = false;
- break;
- case 'trace':
- $this->trace = true;
- $this->verbose = true;
- break;
- case 'version':
- echo $this->getLongVersion();
- exit(0);
- }
- }
- $this->commandOptions = $options;
- }
- /**
- * Renders an exception.
- *
- * @param Exception $e An exception object
- */
- public function renderException($e)
- {
- $title = sprintf(' [%s] ', get_class($e));
- $len = $this->strlen($title);
- $lines = array();
- foreach (explode("\n", $e->getMessage()) as $line)
- {
- $lines[] = sprintf(' %s ', $line);
- $len = max($this->strlen($line) + 4, $len);
- }
- $messages = array(str_repeat(' ', $len));
- if ($this->trace)
- {
- $messages[] = $title.str_repeat(' ', $len - $this->strlen($title));
- }
- foreach ($lines as $line)
- {
- $messages[] = $line.str_repeat(' ', $len - $this->strlen($line));
- }
- $messages[] = str_repeat(' ', $len);
- fwrite(STDERR, "\n");
- foreach ($messages as $message)
- {
- fwrite(STDERR, $this->formatter->format($message, 'ERROR', STDERR)."\n");
- }
- fwrite(STDERR, "\n");
- if (!is_null($this->currentTask) && $e instanceof sfCommandArgumentsException)
- {
- fwrite(STDERR, $this->formatter->format(sprintf($this->currentTask->getSynopsis(), $this->getName()), 'INFO', STDERR)."\n");
- fwrite(STDERR, "\n");
- }
- if ($this->trace)
- {
- fwrite(STDERR, $this->formatter->format("Exception trace:\n", 'COMMENT'));
- // exception related properties
- $trace = $e->getTrace();
- array_unshift($trace, array(
- 'function' => '',
- 'file' => $e->getFile() != null ? $e->getFile() : 'n/a',
- 'line' => $e->getLine() != null ? $e->getLine() : 'n/a',
- 'args' => array(),
- ));
- for ($i = 0, $count = count($trace); $i < $count; $i++)
- {
- $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
- $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
- $function = $trace[$i]['function'];
- $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
- $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
- fwrite(STDERR, sprintf(" %s%s%s at %s:%s\n", $class, $type, $function, $this->formatter->format($file, 'INFO', STDERR), $this->formatter->format($line, 'INFO', STDERR)));
- }
- fwrite(STDERR, "\n");
- }
- }
- /**
- * Gets a task from a task name or a shortcut.
- *
- * @param string $name The task name or a task shortcut
- *
- * @return sfTask A sfTask object
- */
- protected function getTaskToExecute($name)
- {
- // namespace
- if (false !== $pos = strpos($name, ':'))
- {
- $namespace = substr($name, 0, $pos);
- $name = substr($name, $pos + 1);
- $namespaces = array();
- foreach ($this->tasks as $task)
- {
- if ($task->getNamespace() && !in_array($task->getNamespace(), $namespaces))
- {
- $namespaces[] = $task->getNamespace();
- }
- }
- $abbrev = $this->getAbbreviations($namespaces);
- if (!isset($abbrev[$namespace]))
- {
- throw new sfCommandException(sprintf('There are no tasks defined in the "%s" namespace.', $namespace));
- }
- else if (count($abbrev[$namespace]) > 1)
- {
- throw new sfCommandException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, implode(', ', $abbrev[$namespace])));
- }
- else
- {
- $namespace = $abbrev[$namespace][0];
- }
- }
- else
- {
- $namespace = '';
- }
- // name
- $tasks = array();
- foreach ($this->tasks as $taskName => $task)
- {
- if ($taskName == $task->getFullName() && $task->getNamespace() == $namespace)
- {
- $tasks[] = $task->getName();
- }
- }
- $abbrev = $this->getAbbreviations($tasks);
- if (isset($abbrev[$name]) && count($abbrev[$name]) == 1)
- {
- return $this->getTask($namespace ? $namespace.':'.$abbrev[$name][0] : $abbrev[$name][0]);
- }
- // aliases
- $aliases = array();
- foreach ($this->tasks as $taskName => $task)
- {
- if ($taskName == $task->getFullName())
- {
- foreach ($task->getAliases() as $alias)
- {
- $aliases[] = $alias;
- }
- }
- }
- $abbrev = $this->getAbbreviations($aliases);
- $fullName = $namespace ? $namespace.':'.$name : $name;
- if (!isset($abbrev[$fullName]))
- {
- throw new sfCommandException(sprintf('Task "%s" is not defined.', $fullName));
- }
- else if (count($abbrev[$fullName]) > 1)
- {
- throw new sfCommandException(sprintf('Task "%s" is ambiguous (%s).', $fullName, implode(', ', $abbrev[$fullName])));
- }
- else
- {
- return $this->getTask($abbrev[$fullName][0]);
- }
- }
- protected function strlen($string)
- {
- return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
- }
- /**
- * Fixes php behavior if using cgi php.
- *
- * @see http://www.sitepoint.com/article/php-command-line-1/3
- */
- protected function fixCgi()
- {
- // handle output buffering
- @ob_end_flush();
- ob_implicit_flush(true);
- // PHP ini settings
- set_time_limit(0);
- ini_set('track_errors', true);
- ini_set('html_errors', false);
- ini_set('magic_quotes_runtime', false);
- if (false === strpos(PHP_SAPI, 'cgi'))
- {
- return;
- }
- // define stream constants
- define('STDIN', fopen('php://stdin', 'r'));
- define('STDOUT', fopen('php://stdout', 'w'));
- define('STDERR', fopen('php://stderr', 'w'));
- // change directory
- if (isset($_SERVER['PWD']))
- {
- chdir($_SERVER['PWD']);
- }
- // close the streams on script termination
- register_shutdown_function(create_function('', 'fclose(STDIN); fclose(STDOUT); fclose(STDERR); return true;'));
- }
- /**
- * Returns an array of possible abbreviations given a set of names.
- *
- * @see Text::Abbrev perl module for the algorithm
- */
- protected function getAbbreviations($names)
- {
- $abbrevs = array();
- $table = array();
- foreach ($names as $name)
- {
- for ($len = strlen($name) - 1; $len > 0; --$len)
- {
- $abbrev = substr($name, 0, $len);
- if (!array_key_exists($abbrev, $table))
- {
- $table[$abbrev] = 1;
- }
- else
- {
- ++$table[$abbrev];
- }
- $seen = $table[$abbrev];
- if ($seen == 1)
- {
- // We're the first word so far to have this abbreviation.
- $abbrevs[$abbrev] = array($name);
- }
- else if ($seen == 2)
- {
- // We're the second word to have this abbreviation, so we can't use it.
- // unset($abbrevs[$abbrev]);
- $abbrevs[$abbrev][] = $name;
- }
- else
- {
- // We're the third word to have this abbreviation, so skip to the next word.
- continue;
- }
- }
- }
- // Non-abbreviations always get entered, even if they aren't unique
- foreach ($names as $name)
- {
- $abbrevs[$name] = array($name);
- }
- return $abbrevs;
- }
- }
|