From a6b66b9b010aa88a3f8d6beddaffd09f5c84962e Mon Sep 17 00:00:00 2001 From: Abel Hoogeveen Date: Mon, 25 Jan 2021 11:55:17 +0100 Subject: [PATCH] Initial commit --- .gitattributes | 3 + .gitignore | 4 + LICENSE | 21 ++ README.md | 60 ++++++ bin/fuzeworks | 94 ++++++++ composer.json | 30 +++ src/FuzeWorks/CLI/CLIComponent.php | 156 ++++++++++++++ src/FuzeWorks/CLI/CLIView.php | 57 +++++ src/FuzeWorks/CLI/CommandProcessor.php | 212 +++++++++++++++++++ src/FuzeWorks/CLI/Exception/CLIException.php | 41 ++++ src/FuzeWorks/CLI/Input.php | 155 ++++++++++++++ src/FuzeWorks/CLI/Output.php | 92 ++++++++ 12 files changed, 925 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bin/fuzeworks create mode 100644 composer.json create mode 100644 src/FuzeWorks/CLI/CLIComponent.php create mode 100644 src/FuzeWorks/CLI/CLIView.php create mode 100644 src/FuzeWorks/CLI/CommandProcessor.php create mode 100644 src/FuzeWorks/CLI/Exception/CLIException.php create mode 100644 src/FuzeWorks/CLI/Input.php create mode 100644 src/FuzeWorks/CLI/Output.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..18db5ee --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +.gitattributes export-ignore +.gitignore export-ignore +test/ export-ignore \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e4edb72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +composer.lock +.idea/ +log/ +vendor/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..82fedfb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-2019 TechFuze + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7ad29a5 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +FuzeWorks CLI Component - Readme +=================== + +Version 1.3.0 + +A versatile PHP Framework built to perform. + +https://i15.nl/fuzeworks + +Summary +------- + +FuzeWorks is a flexible PHP Framework made for the requirements of today's web. +For a summary of features, list of requirements, and installation instructions, +please see the documentation in the ./doc/ folder or at http://i15.nl/fuzeworks + +Copyright +--------- + +Copyright © 2013 onwards -- i15 + +Certain libraries are copyrighted by their respective authors; +see the full copyright list for details. + +For full copyright information, please see ./doc/copyright.html + +License +------- + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Licensing of current contributions +---------------------------------- + +Beginning on 2018-04-17, new contributions to this codebase are all licensed +under terms compatible with the MIT license. FuzeWorks is currently +transitioning older code to the MIT License, but work is not yet complete. + +Enjoy! +------ + +i15 \ No newline at end of file diff --git a/bin/fuzeworks b/bin/fuzeworks new file mode 100644 index 0000000..1015564 --- /dev/null +++ b/bin/fuzeworks @@ -0,0 +1,94 @@ +#!/usr/bin/env php +')) { + fwrite( + STDERR, + sprintf( + 'FuzeWorks Async requires PHP 7.4 or higher.' . PHP_EOL . + 'You are using PHP %s (%s).' . PHP_EOL, + PHP_VERSION, + PHP_BINARY + ) + ); + + die(1); +} + +// First load composer +$autoloaders = [ + __DIR__ . '/../../autoload.php', + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/vendor/autoload.php' +]; +foreach ($autoloaders as $file) + if (file_exists($file)) + require_once($file); + +// If a bootstrap is provided, use that one +$arguments = getopt('', ['bootstrap:']); +if (!isset($arguments['bootstrap']) || empty($arguments['bootstrap'])) +{ + fwrite(STDERR, "Could not load supervisor. No bootstrap provided.\n"); + die(1); +} + +// Load the file. If it doesn't exist, fail. +$bootstrap = $arguments['bootstrap']; +if (!file_exists($bootstrap)) +{ + fwrite(STDERR, "Could not load supervisor. Provided bootstrap doesn't exist."); + die(1); +} + +// Load the bootstrap +/** @var Factory $container */ +$container = require($bootstrap); + +// Check if container is a Factory +if (!$container instanceof Factory) +{ + fwrite(STDERR, "Could not load supervisor. Provided bootstrap is not a valid bootstrap."); + die(1); +} + +/** @var CLIComponent $cli */ +$cli = $container->cli; +$cli->routeCliRequest(); \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..367ed89 --- /dev/null +++ b/composer.json @@ -0,0 +1,30 @@ +{ + "name": "fuzeworks/clicomponent", + "description": "FuzeWorks CLI Component", + "license": ["MIT"], + "authors": [ + { + "name": "TechFuze", + "homepage": "https://techfuze.net" + }, + { + "name": "FuzeWorks Community", + "homepage": "https://techfuze.net/fuzeworks/contributors" + } + ], + "require": { + "php": ">=7.4.0", + "fuzeworks/mvcr": "~1.3.1", + "fuzeworks/core": "~1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9", + "fuzeworks/tracycomponent": "~1.2.0" + }, + "autoload": { + "psr-4": { + "FuzeWorks\\": "src/FuzeWorks/" + } + }, + "bin": ["bin/fuzeworks"] +} \ No newline at end of file diff --git a/src/FuzeWorks/CLI/CLIComponent.php b/src/FuzeWorks/CLI/CLIComponent.php new file mode 100644 index 0000000..f979d6c --- /dev/null +++ b/src/FuzeWorks/CLI/CLIComponent.php @@ -0,0 +1,156 @@ + $this, + 'cliInput' => '\FuzeWorks\CLI\Input', + 'cliOutput' => '\FuzeWorks\CLI\Output' + ]; + } + + public function onAddComponent(Configurator $configurator) + { + // Add dependencies + $configurator->addComponent(new MVCRComponent()); + + // Add fallback configuration directory + $configurator->addDirectory( + dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Config', + 'config', + Priority::LOWEST + ); + + // If the component will handle a request, set the logger to output for CLI + if (self::$willHandleRequest) + $configurator->call('logger', 'setLoggerTemplate', null, 'logger_cli'); + + } + + public function onCreateContainer(Factory $container) + { + } + + public function enableComponent() + { + self::$willHandleRequest = true; + } + + public function disableComponent() + { + self::$willHandleRequest = false; + } + + protected int $logCount; + + /** + * @return bool + * @throws CLIException + * @throws FactoryException + */ + public function routeCliRequest(): bool + { + if (!self::$willHandleRequest) + throw new CLIException("Could not route CLI request. CLIComponent is not configured to handle requests."); + + // Load dependencies + /** @var Router $router */ + /** @var Input $input */ + /** @var Models $models */ + /** @var Views $views */ + /** @var Controllers $controllers */ + $input = Factory::getInstance('cliInput'); + $models = Factory::getInstance('models'); + $views = Factory::getInstance('views'); + $controllers = Factory::getInstance('controllers'); + + // Add the directories for Models, Views, Controllers from this Component + $srcPath = dirname(__DIR__, 2); + $models->addComponentPath($srcPath . DS . 'Models'. Priority::LOW); + $views->addComponentPath($srcPath . DS . 'Views', Priority::LOW); + $controllers->addComponentPath($srcPath . DS . 'Controllers', Priority::LOW); + + // Load the commandProcessor + $processor = new CommandProcessor(); + Events::addListener([$processor, 'routerCallViewEventListener'], 'routerCallViewEvent', Priority::HIGHEST); + + // Start logging the request + Logger::newLevel("Routing CLI request..."); + + // Fetch input options + $arguments = $input->arguments(); + + // Prepare commandString and execute + array_shift($arguments); + $commandString = implode(' ', $arguments); + $processor->executeCommand($commandString); + + // And stop logging the request + Logger::stopLevel(); + + return true; + } +} \ No newline at end of file diff --git a/src/FuzeWorks/CLI/CLIView.php b/src/FuzeWorks/CLI/CLIView.php new file mode 100644 index 0000000..f62ddf3 --- /dev/null +++ b/src/FuzeWorks/CLI/CLIView.php @@ -0,0 +1,57 @@ +input = Factory::getInstance('cliInput'); + $this->output = Factory::getInstance('cliOutput'); + } + +} \ No newline at end of file diff --git a/src/FuzeWorks/CLI/CommandProcessor.php b/src/FuzeWorks/CLI/CommandProcessor.php new file mode 100644 index 0000000..bb93066 --- /dev/null +++ b/src/FuzeWorks/CLI/CommandProcessor.php @@ -0,0 +1,212 @@ +view instanceof CLIView) + $event->addParameter($this->options); + } + + /** + * Executes a command in the CLI environment. + * + * Returns true on successful processing. Returns false on failure + * + * @param string $commandString + */ + public function executeCommand(string $commandString): void + { + /** @var Router $router */ + /** @var Output $output */ + $this->router = Factory::getInstance('router'); + $this->output = Factory::getInstance('cliOutput'); + + // Pre-process the command + $commandString = $this->preProcess($commandString); + + // First test if the command is empty. If it is, cancel execution + if (empty($commandString)) { + $this->output->warningOut("No command provided!"); + return; + } + + // Distill different parts of the command + $parts = explode(' ', $commandString); + + // Prepare the logCount for a consolidation of logs later + // @todo Find a better way to determine to first log. LogCount is not effective and contains many logs from router + // @todo Perhaps using routerCallViewEvent would be useful. Much closer than this early logCount + $logCount = count(Logger::$logs); + + try { + $this->router->route( + $commandString, [3 => + [ + '(?P.*?)(| (?P.*?)(| (?P.*?)))' => ['viewType' => 'cli'] + ] + ] + ); + } catch (NotFoundException $e) { + $this->output->warningOut("Requested command '$parts[0]' was not found."); + } catch (RouterException $e) { + $this->output->errorOut($e->getMessage()); + } catch (HaltException $e) { + $this->output->errorOut("Requested command '$parts[0]' was denied."); + } + + // Consolidate logs + /** @var array $logs */ + $logs = array_slice(Logger::$logs, $logCount); + + // And parse them + $verbose = isset($this->options['v']) || isset($this->options['verbose']); + $this->parseLogs($logs, $verbose); + } + + /** + * The callable that is used to pick the correct view and controller + * + * @internal + * @param array $matches + * @param array $routeData + * @param string $route + */ + public function cliCallable(array $matches, array $routeData, string $route) + { + dump($this->options); + } + + /** + * Processes a commandString and extracts options from it + * + * @todo Proper compatiblity with string like '--option "Hello World"' + * + * @param string $commandString + * @return string + */ + protected function preProcess(string $commandString): string + { + // Split up the command into parts + $parts = explode(' ', $commandString); + + // Pass over each part and look for special recognized parts, such as options + $options = []; + $keepParts = []; + for ($i=0;$ioptions = $options; + return implode(' ', $keepParts); + } + + /** + * Parse all logs from FuzeWorks::logger and output all that have been thrown for this command + * + * @param array $logs + * @param bool $verbose + */ + protected function parseLogs(array $logs, bool $verbose = false) + { + foreach ($logs as $log) { + switch ($log['type']) { + case 'WARNING': + $this->output->warningOut($log['message']); + break; + case 'ERROR': + case 'EXCEPTION': + $this->output->errorOut($log['message']); + break; + case 'LEVEL_START': + case 'INFO': + if ($verbose) + $this->output->lineOut('&f[&rINFO&f]&r ' . $log['message']); + break; + } + } + } + + +} \ No newline at end of file diff --git a/src/FuzeWorks/CLI/Exception/CLIException.php b/src/FuzeWorks/CLI/Exception/CLIException.php new file mode 100644 index 0000000..fee5bb8 --- /dev/null +++ b/src/FuzeWorks/CLI/Exception/CLIException.php @@ -0,0 +1,41 @@ +arguments = $argv; + } + + /** + * @param array $shortOpt + * @param array $longOpt + * @return false|false[]|string[] + */ + public function option(array $shortOpt = [], array $longOpt = []) + { + $shortOpt = implode('', $shortOpt); + return getopt($shortOpt, $longOpt); + } + + public function arguments(): array + { + return $this->arguments; + } + + public function line(): string + { + return trim(fgets(STDIN)); + } + + /** + * @return string + * @todo Implement multiline input from pipe ('|') + */ + public function multiLine(): string + { + $input = ''; + + // While an input is coming, keep going until a break is passed + while ($line = $this->line()) + { + // If the line does not end with '\', save the input and break the multiline + if (substr(rtrim($line), -2, 2) != ' \\') + { + $input .= $line; + break; + } + // If the line ends with '\', save the line and ask for the next one; + else + { + $input .= substr(rtrim($line), 0 , -1); + echo "\r" . ' > '; + } + } + + // And finally output the input + return $input; + } + + /** + * Asks the user a question to which the user responds. Input will be of type multiline + * + * @param string $question + * @param string $response_type + * @param bool $optional + * @return string + */ + public function dialog(string $question, string $response_type, bool $optional = false): string + { + // First ask the dialog question + echo $question . " > "; + + // Retrieve the input using a multiline + $input = $this->multiLine(); + + // If the input is empty and not optional, ask again + if (empty($input) && !$optional) + { + echo "Input may is not optional!\n"; + $input = $this->dialog($question, $response_type, $optional); + } + + // Verify whether the input matches the requested type + // @todo + + return $input; + } + + /** + * Can be used for things like pipe. Might need some rework though... + * @todo Rework, implement safe_feof() + * @see https://www.php.net/manual/en/function.feof.php + * + * @return string + */ + public function stdin(): string + { + $lines = $this->stdinArray(); + return implode("\n", $lines); + } + + public function stdinArray(): array + { + $lines = []; + while(!feof(STDIN)) + $lines[] = fgets(STDIN); + + return $lines; + } + + + + +} \ No newline at end of file diff --git a/src/FuzeWorks/CLI/Output.php b/src/FuzeWorks/CLI/Output.php new file mode 100644 index 0000000..9e51b8c --- /dev/null +++ b/src/FuzeWorks/CLI/Output.php @@ -0,0 +1,92 @@ + '0;30', + '&1' => '0;34', + '&2' => '0;32', + '&3' => '0;36', + '&4' => '0;31', + '&5' => '0;35', + '&6' => '0;33', + '&7' => '0;37', + '&8' => '1;30', + '&9' => '1;34', + '&a' => '1;32', + '&b' => '1;36', + '&c' => '1;31', + '&d' => '1;35', + '&e' => '1;33', + '&f' => '1;37', + '&r' => '0' + ]; + + public function charsOut(string $output): void + { + echo $this->parseOutput($output); + } + + public function lineOut(string $output): void + { + echo $this->parseOutput($output) . "\n"; + } + + public function warningOut(string $output): void + { + $this->lineOut('&4[&eWARNING&4] &e' . $output . '&r'); + } + + public function errorOut(string $output): void + { + $this->lineOut('&4[&eERROR&4] &c' . $output . '&r'); + } + + protected function parseOutput(string $output): string + { + // First parse for colors + foreach ($this->colors as $colorKey => $colorVal) { + $output = str_replace($colorKey, "\033[" . $colorVal . 'm', $output); + } + + return $output; + } + +} \ No newline at end of file