diff --git a/.gitignore b/.gitignore index b03f338..f932b87 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ app/storage /release/ php-cs-fixer-v2.phar app/sto +.idea/* diff --git a/example/OpenFileCommand.php b/example/OpenFileCommand.php index 4f88045..4342c46 100644 --- a/example/OpenFileCommand.php +++ b/example/OpenFileCommand.php @@ -1,4 +1,5 @@ getArgValue('path'); - + if ($path === null) { $path = $this->getInput('Give me file path:'); } - + if (!file_exists($path)) { $this->error('File not found: '.$path); + return -1; } $resource = fopen($path, 'r'); $ch = ''; - while($ch !== false) { + + while ($ch !== false) { $ch = fgetc($resource); $this->prints($ch); } - + fclose($resource); + return 1; } - } diff --git a/webfiori/cli/CLICommand.php b/webfiori/cli/CLICommand.php index 86c9796..82fbda9 100644 --- a/webfiori/cli/CLICommand.php +++ b/webfiori/cli/CLICommand.php @@ -156,7 +156,6 @@ public function addArgs(array $arr) { $this->commandArgs = []; foreach ($arr as $optionName => $options) { - if ($options instanceof CommandArgument) { $this->addArgument($options); } else { @@ -761,6 +760,34 @@ public function prints(string $str, ...$_) { public function read(int $bytes = 1) : string { return $this->getInputStream()->read($bytes); } + /** + * Reads and validates class name. + * + * @param string|null $suffix An optional string to append to class name. + * + * @param string $prompt The text that will be shown to the user as prompt for + * class name. + * + * @param string $errMsg A string to show in case provided class name is + * not valid. + * + * @return string A string that represents a valid class name. If suffix is + * not null, the method will return the name with the suffix included. + */ + public function readClassName(string $prompt, string $suffix = null, string $errMsg = 'Invalid class name is given.') { + return $this->getInput($prompt, null, new InputValidator(function (&$className, $suffix) + { + if ($suffix !== null) { + $subSuffix = substr($className, strlen($className) - strlen($suffix)); + + if ($subSuffix != $suffix) { + $className .= $suffix; + } + } + + return InputValidator::isValidClassName($className); + }, $errMsg, [$suffix])); + } /** * Reads a value as float. @@ -780,24 +807,6 @@ public function readFloat(string $prompt, float $default = null) : float { return InputValidator::isFloat($val); }, 'Provided value is not a floating number!')); } - /** - * Reads a value as an integer. - * - * @param string $prompt The string that will be shown to the user. The - * string must be non-empty. - * - * @param int $default An optional default value to use in case the user - * hit "Enter" without entering any value. If null is passed, no default - * value will be set. - * - * @return int - */ - public function readInteger(string $prompt, int $default = null) : int { - return $this->getInput($prompt, $default, new InputValidator(function ($val) - { - return InputValidator::isInt($val); - }, 'Provided value is not an integer!')); - } /** * Reads the namespace of class and return an instance of it. * @@ -810,43 +819,50 @@ public function readInteger(string $prompt, int $default = null) : int { * @return object The method will return an instance of the class. */ public function readInstance(string $prompt, string $errMsg = 'Invalid Class!', $constructorArgs = []) { - $clazzNs = $this->getInput($prompt, null, new InputValidator(function ($input) { - + $clazzNs = $this->getInput($prompt, null, new InputValidator(function ($input) + { if (InputValidator::isClass($input)) { return true; } + return false; }, $errMsg)); $reflection = new \ReflectionClass($clazzNs); + return $reflection->newInstanceArgs($constructorArgs); } /** - * Reads and validates class name. + * Reads a value as an integer. * - * @param string|null $suffix An optional string to append to class name. + * @param string $prompt The string that will be shown to the user. The + * string must be non-empty. * - * @param string $prompt The text that will be shown to the user as prompt for - * class name. + * @param int $default An optional default value to use in case the user + * hit "Enter" without entering any value. If null is passed, no default + * value will be set. * - * @param string $errMsg A string to show in case provided class name is - * not valid. + * @return int + */ + public function readInteger(string $prompt, int $default = null) : int { + return $this->getInput($prompt, $default, new InputValidator(function ($val) + { + return InputValidator::isInt($val); + }, 'Provided value is not an integer!')); + } + /** + * Reads one line from input stream. * - * @return string A string that represents a valid class name. If suffix is - * not null, the method will return the name with the suffix included. + * The method will continue to read from input stream till it finds end of + * line character "\n". + * + * @return string The method will return the string which was taken from + * input stream without the end of line character. + * + * @since 1.0 */ - public function readClassName(string $prompt, string $suffix = null, string $errMsg = 'Invalid class name is given.') { - return $this->getInput($prompt, null, new InputValidator(function (&$className, $suffix) { - if ($suffix !== null) { - $subSuffix = substr($className, strlen($className) - strlen($suffix)); - - if ($subSuffix != $suffix) { - $className .= $suffix; - } - } - - return InputValidator::isValidClassName($className); - }, $errMsg, [$suffix])); + public function readln() : string { + return $this->getInputStream()->readLine(); } /** @@ -870,28 +886,15 @@ public function readNamespace(string $prompt, string $defaultNs = null, string $ if ($defaultNs !== null && !InputValidator::isValidNamespace($defaultNs)) { throw new IOException('Provided default namespace is not valid.'); } - return $this->getInput($prompt, $defaultNs, new InputValidator(function ($input) { - + + return $this->getInput($prompt, $defaultNs, new InputValidator(function ($input) + { if (InputValidator::isValidNamespace($input)) { return true; } + return false; }, $errMsg)); - - } - /** - * Reads one line from input stream. - * - * The method will continue to read from input stream till it finds end of - * line character "\n". - * - * @return string The method will return the string which was taken from - * input stream without the end of line character. - * - * @since 1.0 - */ - public function readln() : string { - return $this->getInputStream()->readLine(); } /** * Removes an argument from the command given its name. @@ -1045,16 +1048,6 @@ public function setName(string $name) : bool { return false; } - /** - * Sets the runner that owns the command. - * - * The runner is the instance that will execute the command. - * - * @param Runner $owner - */ - public function setOwner(Runner $owner = null) { - $this->owner = $owner; - } /** * Sets the stream at which the command will send output to. * @@ -1065,6 +1058,16 @@ public function setOwner(Runner $owner = null) { public function setOutputStream(OutputStream $stream) { $this->outputStream = $stream; } + /** + * Sets the runner that owns the command. + * + * The runner is the instance that will execute the command. + * + * @param Runner $owner + */ + public function setOwner(Runner $owner = null) { + $this->owner = $owner; + } /** * Display a message that represents a success status. * @@ -1136,7 +1139,7 @@ private function _checkSelectedChoice($choices, $defaultIndex, $input) { //Selected option is an index. Search for it and return its value. $retVal = $this->_getChoiceAtIndex($choices, $input); } - + if ($retVal === null) { $this->error('Invalid answer.'); } @@ -1252,6 +1255,7 @@ private function getInputHelper(&$input, InputValidator $validator = null, strin } } $retVal['value'] = $input; + return $retVal; } private function printMsg(string $msg, string $prefix, string $color) { diff --git a/webfiori/cli/CommandArgument.php b/webfiori/cli/CommandArgument.php index 4cb3f9b..b9694c6 100644 --- a/webfiori/cli/CommandArgument.php +++ b/webfiori/cli/CommandArgument.php @@ -114,12 +114,13 @@ public static function create(string $name, array $options) { */ public static function extractValue(string $argName, Runner $runner = null) { $trimmedOptName = trim($argName); - + if ($runner !== null) { $argsV = $runner->getArgsVector(); } else { $argsV = $_SERVER['argv']; } + foreach ($argsV as $option) { $optionClean = filter_var($option, FILTER_DEFAULT); $optExpl = explode('=', $optionClean); diff --git a/webfiori/cli/InputValidator.php b/webfiori/cli/InputValidator.php index 4e82af9..bb63428 100644 --- a/webfiori/cli/InputValidator.php +++ b/webfiori/cli/InputValidator.php @@ -65,74 +65,12 @@ public static function isClass(string $classNs, array $args = []) : bool { if (class_exists($classNs)) { $reflection = new \ReflectionClass($classNs); $clazz = $reflection->newInstanceArgs($args); + return gettype($clazz) == 'object'; } } catch (Throwable $ex) { return false; } - return false; - } - /** - * Checks if provided string represents a valid namespace or not. - * - * @param string $ns A string to be validated. - * - * @return bool If the provided string represents a valid namespace, the - * method will return true. False if it does not represent a valid namespace. - */ - public static function isValidNamespace(string $ns) : bool { - if ($ns == '\\') { - return true; - } - if (strlen($ns) == 0) { - return false; - } - $split = explode('\\', $ns); - - foreach ($split as $subNs) { - $len = strlen($subNs); - - for ($x = 0 ; $x < $len ; $x++) { - $char = $subNs[$x]; - - if ($x == 0 && $char >= '0' && $char <= '9') { - return false; - } - - if (!(($char <= 'Z' && $char >= 'A') || ($char <= 'z' && $char >= 'a') || ($char >= '0' && $char <= '9') || $char == '_')) { - return false; - } - } - } - - return true; - } - /** - * Checks if a given string represents a valid class name or not. - * - * @param string $name A string to check such as 'My_Super_Class'. - * - * @return bool If the given string is a valid class name, the method - * will return true. False otherwise. - */ - public static function isValidClassName(string $name) : bool { - $len = strlen($name); - - if ($len > 0) { - for ($x = 0 ; $x < $len ; $x++) { - $char = $name[$x]; - - if ($x == 0 && $char >= '0' && $char <= '9') { - return false; - } - - if (!(($char <= 'Z' && $char >= 'A') || ($char <= 'z' && $char >= 'a') || ($char >= '0' && $char <= '9') || $char == '_')) { - return false; - } - } - - return true; - } return false; } @@ -208,4 +146,69 @@ public static function isInt(string $val) : bool { public function isValid(string &$input) : bool { return call_user_func_array($this->callback, array_merge([&$input], $this->params)); } + /** + * Checks if a given string represents a valid class name or not. + * + * @param string $name A string to check such as 'My_Super_Class'. + * + * @return bool If the given string is a valid class name, the method + * will return true. False otherwise. + */ + public static function isValidClassName(string $name) : bool { + $len = strlen($name); + + if ($len > 0) { + for ($x = 0 ; $x < $len ; $x++) { + $char = $name[$x]; + + if ($x == 0 && $char >= '0' && $char <= '9') { + return false; + } + + if (!(($char <= 'Z' && $char >= 'A') || ($char <= 'z' && $char >= 'a') || ($char >= '0' && $char <= '9') || $char == '_')) { + return false; + } + } + + return true; + } + + return false; + } + /** + * Checks if provided string represents a valid namespace or not. + * + * @param string $ns A string to be validated. + * + * @return bool If the provided string represents a valid namespace, the + * method will return true. False if it does not represent a valid namespace. + */ + public static function isValidNamespace(string $ns) : bool { + if ($ns == '\\') { + return true; + } + + if (strlen($ns) == 0) { + return false; + } + $split = explode('\\', $ns); + + foreach ($split as $subNs) { + $len = strlen($subNs); + + for ($x = 0 ; $x < $len ; $x++) { + $char = $subNs[$x]; + + if ($x == 0 && $char >= '0' && $char <= '9') { + return false; + } + + if (!(($char <= 'Z' && $char >= 'A') || ($char <= 'z' && $char >= 'a') || ($char >= '0' && $char <= '9') || $char == '_')) { + return false; + } + } + } + + return true; + } } diff --git a/webfiori/cli/KeysMap.php b/webfiori/cli/KeysMap.php index ce7cd66..a7d8f6e 100644 --- a/webfiori/cli/KeysMap.php +++ b/webfiori/cli/KeysMap.php @@ -76,6 +76,7 @@ public static function map(string $ch) : string { public static function read(InputStream $stream, $bytes = 1) : string { $input = ''; $len = strlen($input); + while ($len < $bytes) { $char = self::readAndTranslate($stream); self::appendChar($char, $input); diff --git a/webfiori/cli/Runner.php b/webfiori/cli/Runner.php index f9312df..aa90fb9 100644 --- a/webfiori/cli/Runner.php +++ b/webfiori/cli/Runner.php @@ -1,7 +1,6 @@ true, 'description' => 'Force the use of ANSI output.' ]); - $this->setBeforeStart(function (Runner $r) { + $this->setBeforeStart(function (Runner $r) + { if (count($r->getArgsVector()) == 0) { $r->setArgsVector($_SERVER['argv']); } $r->checkIsIntr(); }); } - /** - * Add a function to execute after every command. - * - * The method can be used to set multiple callbacks. - * - * @param callable $func The function that will be executed after the - * completion of command execution. The first parameter of the method - * will always be an instance of 'Runner' (e.g. function (Runner $runner){}). - * - * @param array $params Any additional parameters that will be passed to the - * callback. - */ - public function setAfterExecution(callable $func, array $params = []) { - $this->afterRunPool[] = [ - 'func' => $func, - 'params' => $params - ]; - } /** * Adds a global command argument. * @@ -239,6 +220,16 @@ public function getDefaultCommand() { public function getInputStream() : InputStream { return $this->inputStream; } + + /** + * Returns exit status code of last executed command. + * + * @return int For success run, the method should return 0. Other than that, + * it means the command was executed with an error. + */ + public function getLastCommandExitStatus() : int { + return $this->commandExitVal; + } /** * Returns an array that contain all generated output by executing a command. * @@ -348,29 +339,6 @@ public function reset() { $this->outputStream = new StdOut(); $this->commands = []; } - - /** - * Returns exit status code of last executed command. - * - * @return int For success run, the method should return 0. Other than that, - * it means the command was executed with an error. - */ - public function getLastCommandExitStatus() : int { - return $this->commandExitVal; - } - private function printMsg(string $msg, string $prefix = null, string $color = null) { - if ($prefix !== null) { - $prefix = Formatter::format($prefix, [ - 'color' => $color, - 'bold' => true, - 'ansi' => $this->isAnsi - ]); - $this->getOutputStream()->prints("$prefix "); - } - if (strlen($msg) != 0) { - $this->getOutputStream()->println($msg); - } - } /** * Executes a command given as object. * @@ -398,7 +366,7 @@ public function runCommand(CLICommand $c = null, array $args = [], bool $ansi = } else { if (isset($args[0])) { $commandName = filter_var($args[0], FILTER_DEFAULT); - + $c = $this->getCommandByName($commandName); } else { $c = $this->getDefaultCommand(); @@ -407,23 +375,24 @@ public function runCommand(CLICommand $c = null, array $args = [], bool $ansi = if ($c === null) { if ($commandName == null) { - $this->printMsg("No command was specified to run.", 'Info:', 'blue'); + return 0; } else { $this->printMsg("The command '".$commandName."' is not supported.", 'Error:', 'red'); $this->commandExitVal = -1; + return -1; } - } } + if ($ansi) { $args[] = '--ansi'; } $this->setArgV($args); $this->setActiveCommand($c); - + try { $this->commandExitVal = $c->excCommand(); } catch (Throwable $ex) { @@ -434,16 +403,12 @@ public function runCommand(CLICommand $c = null, array $args = [], bool $ansi = $this->printMsg($ex->getLine(), 'Line:', 'yellow'); $this->commandExitVal = $ex->getCode() == 0 ? -1 : $ex->getCode(); } - + $this->invokeAfterExc(); $this->setActiveCommand(); + return $this->commandExitVal; } - private function invokeAfterExc() { - foreach ($this->afterRunPool as $funcArr) { - call_user_func_array($funcArr['func'], array_merge([$this], $funcArr['params'])); - } - } /** * Sets the command which is currently in execution stage. @@ -465,6 +430,24 @@ public function setActiveCommand(CLICommand $c = null) { $this->getActiveCommand()->setOwner($this); } } + /** + * Add a function to execute after every command. + * + * The method can be used to set multiple callbacks. + * + * @param callable $func The function that will be executed after the + * completion of command execution. The first parameter of the method + * will always be an instance of 'Runner' (e.g. function (Runner $runner){}). + * + * @param array $params Any additional parameters that will be passed to the + * callback. + */ + public function setAfterExecution(callable $func, array $params = []) { + $this->afterRunPool[] = [ + 'func' => $func, + 'params' => $params + ]; + } /** * Sets arguments vector to have specific value. * @@ -553,6 +536,7 @@ public function start() : int { foreach ($this->beforeStartPool as $func) { call_user_func_array($func, [$this]); } + if ($this->isInteractive()) { $this->isAnsi = in_array('--ansi', $this->getArgsVector()); $this->printMsg('Running in interactive mode.', '>>', 'blue'); @@ -566,10 +550,12 @@ public function start() : int { if ($argsCount == 0) { $this->getOutputStream()->println('No input.'); - } else if ($args[0] == 'exit') { - return 0; } else { - $this->runCommand(null, $args, $this->isAnsi); + if ($args[0] == 'exit') { + return 0; + } else { + $this->runCommand(null, $args, $this->isAnsi); + } } $this->printMsg('', '>>', 'blue'); } @@ -582,14 +568,35 @@ private function checkIsIntr() { $this->isInteractive = $arg == '-i' || $this->isInteractive; } } + private function invokeAfterExc() { + foreach ($this->afterRunPool as $funcArr) { + call_user_func_array($funcArr['func'], array_merge([$this], $funcArr['params'])); + } + } + private function printMsg(string $msg, string $prefix = null, string $color = null) { + if ($prefix !== null) { + $prefix = Formatter::format($prefix, [ + 'color' => $color, + 'bold' => true, + 'ansi' => $this->isAnsi + ]); + $this->getOutputStream()->prints("$prefix "); + } + + if (strlen($msg) != 0) { + $this->getOutputStream()->println($msg); + } + } private function readInteractive() { $input = trim($this->getInputStream()->readLine()); $argsArr = strlen($input) != 0 ? explode(' ', $input) : []; + if (in_array('--ansi', $argsArr)) { return array_diff($argsArr, ['--ansi']); } + return $argsArr; } /** @@ -615,8 +622,10 @@ private function run() { } $argsArr = $tempArgs; } + if (count($argsArr) == 0) { $command = $this->getDefaultCommand(); + return $this->runCommand($command, [], $this->isAnsi); }