summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Cli/Loader.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icinga/Cli/Loader.php')
-rw-r--r--library/Icinga/Cli/Loader.php499
1 files changed, 499 insertions, 0 deletions
diff --git a/library/Icinga/Cli/Loader.php b/library/Icinga/Cli/Loader.php
new file mode 100644
index 0000000..732c3bc
--- /dev/null
+++ b/library/Icinga/Cli/Loader.php
@@ -0,0 +1,499 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Cli;
+
+use Icinga\Application\ApplicationBootstrap as App;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotReadableError;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Cli\Params;
+use Icinga\Cli\Screen;
+use Icinga\Cli\Command;
+use Icinga\Cli\Documentation;
+use Exception;
+
+/**
+ *
+ */
+class Loader
+{
+ protected $app;
+
+ protected $docs;
+
+ protected $commands;
+
+ protected $modules;
+
+ protected $moduleCommands = array();
+
+ protected $coreAppDir;
+
+ protected $screen;
+
+ protected $moduleName;
+
+ protected $commandName;
+
+ protected $actionName; // Should this better be moved to the Command?
+
+ /**
+ * [$command] = $class;
+ */
+ protected $commandClassMap = array();
+
+ /**
+ * [$command] = $file;
+ */
+ protected $commandFileMap = array();
+
+ /**
+ * [$module][$command] = $class;
+ */
+ protected $moduleClassMap = array();
+
+ /**
+ * [$module][$command] = $file;
+ */
+ protected $moduleFileMap = array();
+
+ protected $commandInstances = array();
+
+ protected $moduleInstances = array();
+
+ protected $lastSuggestions = array();
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ $this->coreAppDir = $app->getApplicationDir('clicommands');
+ }
+
+ /**
+ * Screen shortcut
+ *
+ * @return Screen
+ */
+ protected function screen()
+ {
+ if ($this->screen === null) {
+ $this->screen = Screen::instance(STDERR);
+ }
+
+ return $this->screen;
+ }
+
+ /**
+ * Documentation shortcut
+ *
+ * @return Documentation
+ */
+ protected function docs()
+ {
+ if ($this->docs === null) {
+ $this->docs = new Documentation($this->app);
+ }
+ return $this->docs;
+ }
+
+ /**
+ * Show given message and exit
+ *
+ * @param string $msg message to show
+ */
+ public function fail($msg)
+ {
+ fprintf(STDERR, "%s: %s\n", $this->screen()->colorize('ERROR', 'red'), $msg);
+ exit(1);
+ }
+
+ public function getModuleName()
+ {
+ return $this->moduleName;
+ }
+
+ public function setModuleName($name)
+ {
+ $this->moduleName = $name;
+ return $this;
+ }
+
+ public function getCommandName()
+ {
+ return $this->commandName;
+ }
+
+ public function getActionName()
+ {
+ return $this->actionName;
+ }
+
+ public function getCommandInstance($command)
+ {
+ if (! array_key_exists($command, $this->commandInstances)) {
+ $this->assertCommandExists($command);
+ require_once $this->commandFileMap[$command];
+ $className = $this->commandClassMap[$command];
+ $this->commandInstances[$command] = new $className(
+ $this->app,
+ null,
+ $command,
+ null,
+ false
+ );
+ }
+ return $this->commandInstances[$command];
+ }
+
+ public function getModuleCommandInstance($module, $command)
+ {
+ if (! array_key_exists($command, $this->moduleInstances[$module])) {
+ $this->assertModuleCommandExists($module, $command);
+ require_once $this->moduleFileMap[$module][$command];
+ $className = $this->moduleClassMap[$module][$command];
+ $this->moduleInstances[$module][$command] = new $className(
+ $this->app,
+ $module,
+ $command,
+ null,
+ false
+ );
+ }
+ return $this->moduleInstances[$module][$command];
+ }
+
+ public function getLastSuggestions()
+ {
+ return $this->lastSuggestions;
+ }
+
+ public function showLastSuggestions()
+ {
+ if (! empty($this->lastSuggestions)) {
+ foreach ($this->lastSuggestions as & $s) {
+ $s = $this->screen()->colorize($s, 'lightblue');
+ }
+ fprintf(
+ STDERR,
+ "Did you mean %s?\n",
+ implode(" or ", $this->lastSuggestions)
+ );
+ }
+ }
+
+ public function parseParams(Params $params = null)
+ {
+ if ($params === null) {
+ $params = $this->app->getParams();
+ }
+
+ if ($this->moduleName === null) {
+ $first = $params->shift();
+ if (! $first) {
+ return;
+ }
+ $found = $this->resolveName($first);
+ } else {
+ $found = $this->moduleName;
+ }
+ if (! $found) {
+ $msg = "There is no such module or command: '$first'";
+ fprintf(STDERR, "%s: %s\n", $this->screen()->colorize('ERROR', 'red'), $msg);
+ $this->showLastSuggestions();
+ fwrite(STDERR, "\n");
+ }
+
+ $obj = null;
+ if ($this->hasCommand($found)) {
+ $this->commandName = $found;
+ $obj = $this->getCommandInstance($this->commandName);
+ } elseif ($this->hasModule($found)) {
+ $this->moduleName = $found;
+ $command = $this->resolveModuleCommandName($found, $params->shift());
+ if ($command) {
+ $this->commandName = $command;
+ $obj = $this->getModuleCommandInstance(
+ $this->moduleName,
+ $this->commandName
+ );
+ }
+ }
+ if ($obj !== null) {
+ $action = $this->resolveObjectActionName(
+ $obj,
+ $params->getStandalone()
+ );
+ if ($obj->hasActionName($action)) {
+ $this->actionName = $action;
+ $params->shift();
+ } elseif ($obj->hasDefaultActionName()) {
+ $this->actionName = $obj->getDefaultActionName();
+ }
+ }
+ return $this;
+ }
+
+ public function handleParams(Params $params = null)
+ {
+ $this->parseParams($params);
+ $this->dispatch();
+ }
+
+ public function dispatch(Params $overrideParams = null)
+ {
+ if ($this->commandName === null) {
+ fwrite(STDERR, $this->docs()->usage($this->moduleName));
+ return false;
+ } elseif ($this->actionName === null) {
+ fwrite(STDERR, $this->docs()->usage($this->moduleName, $this->commandName));
+ return false;
+ }
+
+ try {
+ if ($this->moduleName) {
+ $this->app->getModuleManager()->loadModule($this->moduleName);
+ $obj = $this->getModuleCommandInstance(
+ $this->moduleName,
+ $this->commandName
+ );
+ } else {
+ $obj = $this->getCommandInstance($this->commandName);
+ }
+ if ($overrideParams !== null) {
+ $obj->setParams($overrideParams);
+ }
+ $obj->init();
+ return $obj->{$this->actionName . 'Action'}();
+ } catch (Exception $e) {
+ if ($obj && $obj instanceof Command && $obj->showTrace()) {
+ fwrite(STDERR, $this->formatTrace($e->getTrace()));
+ }
+
+ $this->fail(IcingaException::describe($e));
+ }
+ }
+
+ protected function searchMatch($needle, $haystack)
+ {
+ if ($needle === null) {
+ $needle = '';
+ }
+
+ $this->lastSuggestions = preg_grep(sprintf('/^%s.*$/', preg_quote($needle, '/')), $haystack);
+ $match = array_search($needle, $haystack, true);
+ if (false !== $match) {
+ return $haystack[$match];
+ }
+ if (count($this->lastSuggestions) === 1) {
+ $lastSuggestions = array_values($this->lastSuggestions);
+ return $lastSuggestions[0];
+ }
+ return false;
+ }
+
+ public function resolveName($name)
+ {
+ return $this->searchMatch(
+ $name,
+ array_merge($this->listCommands(), $this->listModules())
+ );
+ }
+
+ public function resolveCommandName($name)
+ {
+ return $this->searchMatch($name, $this->listCommands());
+ }
+
+ public function resolveModuleName($name)
+ {
+ return $this->searchMatch($name, $this->listModules());
+ }
+
+ public function resolveModuleCommandName($module, $name)
+ {
+ return $this->searchMatch($name, $this->listModuleCommands($module));
+ }
+
+ public function resolveObjectActionName($obj, $name)
+ {
+ return $this->searchMatch($name, $obj->listActions());
+ }
+
+ protected function assertModuleExists($module)
+ {
+ if (! $this->hasModule($module)) {
+ throw new ProgrammingError(
+ 'There is no such module: %s',
+ $module
+ );
+ }
+ }
+
+ protected function assertCommandExists($command)
+ {
+ if (! $this->hasCommand($command)) {
+ throw new ProgrammingError(
+ 'There is no such command: %s',
+ $command
+ );
+ }
+ }
+
+ protected function assertModuleCommandExists($module, $command)
+ {
+ $this->assertModuleExists($module);
+ if (! $this->hasModuleCommand($module, $command)) {
+ throw new ProgrammingError(
+ 'The module \'%s\' has no such command: %s',
+ $module,
+ $command
+ );
+ }
+ }
+
+ protected function formatTrace($trace)
+ {
+ $output = array();
+ foreach ($trace as $i => $step) {
+ $object = '';
+ if (isset($step['object']) && is_object($step['object'])) {
+ $object = sprintf('[%s]', get_class($step['object'])) . $step['type'];
+ } elseif (! empty($step['object'])) {
+ $object = (string) $step['object'] . $step['type'];
+ }
+ if (isset($step['args']) && is_array($step['args'])) {
+ foreach ($step['args'] as & $arg) {
+ if (is_object($arg)) {
+ $arg = sprintf('[%s]', get_class($arg));
+ }
+ if (is_string($arg)) {
+ $arg = preg_replace('~\n~', '\n', $arg);
+ if (strlen($arg) > 50) {
+ $arg = substr($arg, 0, 47) . '...';
+ }
+ $arg = "'" . $arg . "'";
+ }
+ if ($arg === null) {
+ $arg = 'NULL';
+ }
+ if (is_bool($arg)) {
+ $arg = $arg ? 'TRUE' : 'FALSE';
+ }
+ }
+ } else {
+ $step['args'] = array();
+ }
+ $args = $step['args'];
+ foreach ($args as & $v) {
+ if (is_array($v)) {
+ $v = var_export($v, 1);
+ } else {
+ $v = (string) $v;
+ }
+ }
+ $output[$i] = sprintf(
+ '#%d %s:%d %s%s(%s)',
+ $i,
+ isset($step['file']) ? preg_replace(
+ '~.+/library/~',
+ 'library/',
+ $step['file']
+ ) : '[unknown file]',
+ isset($step['line']) ? $step['line'] : '0',
+ $object,
+ $step['function'],
+ implode(', ', $args)
+ );
+ }
+ return implode(PHP_EOL, $output) . PHP_EOL;
+ }
+
+ public function hasCommand($name)
+ {
+ return in_array($name, $this->listCommands());
+ }
+
+ public function hasModule($name)
+ {
+ return in_array($name, $this->listModules());
+ }
+
+ public function hasModuleCommand($module, $name)
+ {
+ return in_array($name, $this->listModuleCommands($module));
+ }
+
+ public function listModules()
+ {
+ if ($this->modules === null) {
+ $this->modules = array();
+ try {
+ $this->modules = array_unique(array_merge(
+ $this->app->getModuleManager()->listEnabledModules(),
+ $this->app->getModuleManager()->listLoadedModules()
+ ));
+ } catch (NotReadableError $e) {
+ $this->fail($e->getMessage());
+ }
+ }
+ return $this->modules;
+ }
+
+ protected function retrieveCommandsFromDir($dirname)
+ {
+ $commands = array();
+ if (! @file_exists($dirname) || ! is_readable($dirname)) {
+ return $commands;
+ }
+
+ $base = opendir($dirname);
+ if ($base === false) {
+ return $commands;
+ }
+ while (false !== ($dir = readdir($base))) {
+ if ($dir[0] === '.') {
+ continue;
+ }
+ if (preg_match('~^([A-Za-z0-9]+)Command\.php$~', $dir, $m)) {
+ $cmd = strtolower($m[1]);
+ $commands[] = $cmd;
+ }
+ }
+ closedir($base);
+ sort($commands);
+ return $commands;
+ }
+
+ public function listCommands()
+ {
+ if ($this->commands === null) {
+ $this->commands = array();
+ $ns = 'Icinga\\Clicommands\\';
+ $this->commands = $this->retrieveCommandsFromDir($this->coreAppDir);
+ foreach ($this->commands as $cmd) {
+ $this->commandClassMap[$cmd] = $ns . ucfirst($cmd) . 'Command';
+ $this->commandFileMap[$cmd] = $this->coreAppDir . '/' . ucfirst($cmd) . 'Command.php';
+ }
+ }
+ return $this->commands;
+ }
+
+ public function listModuleCommands($module)
+ {
+ if (! array_key_exists($module, $this->moduleCommands)) {
+ $ns = 'Icinga\\Module\\' . ucfirst($module) . '\\Clicommands\\';
+ $this->assertModuleExists($module);
+ $manager = $this->app->getModuleManager();
+ $manager->loadModule($module);
+ $dir = $manager->getModuleDir($module) . '/application/clicommands';
+ $this->moduleCommands[$module] = $this->retrieveCommandsFromDir($dir);
+ $this->moduleInstances[$module] = array();
+ foreach ($this->moduleCommands[$module] as $cmd) {
+ $this->moduleClassMap[$module][$cmd] = $ns . ucfirst($cmd) . 'Command';
+ $this->moduleFileMap[$module][$cmd] = $dir . '/' . ucfirst($cmd) . 'Command.php';
+ }
+ }
+ return $this->moduleCommands[$module];
+ }
+}