From fa35199bfaf7b6d4652f3a6f146c0297aa5e752e Mon Sep 17 00:00:00 2001 From: sobolevna Date: Sun, 4 Dec 2016 11:13:02 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BD=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D1=81=D1=80=D0=B5=D0=B4?= =?UTF-8?q?=D1=8B=20=D1=80=D0=B0=D0=B7=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 + composer.json | 32 ++++ phpunit.xml | 16 ++ src/PHPixie/CLI.php | 40 +++++ src/PHPixie/CLI/Builder.php | 79 +++++++++ src/PHPixie/CLI/Context.php | 14 ++ src/PHPixie/CLI/Context/Container.php | 8 + .../CLI/Context/Container/Implementation.php | 35 ++++ .../CLI/Context/Container/Settable.php | 8 + src/PHPixie/CLI/Context/SAPI.php | 160 ++++++++++++++++++ src/PHPixie/CLI/Exception.php | 8 + src/PHPixie/CLI/Input.php | 65 +++++++ src/PHPixie/CLI/Parameters.php | 25 +++ src/PHPixie/CLI/Prompt.php | 95 +++++++++++ src/PHPixie/CLI/Stream/Input.php | 39 +++++ src/PHPixie/CLI/Stream/Output.php | 23 +++ src/PHPixie/CLI/Streams.php | 6 + tests/PHPixie/Tests/CLI/Context/SAPITest.php | 96 +++++++++++ tests/PHPixie/Tests/CLI/PromptTest.php | 32 ++++ tests/phpunit.php | 2 + 20 files changed, 787 insertions(+) create mode 100755 .gitignore create mode 100644 composer.json create mode 100644 phpunit.xml create mode 100644 src/PHPixie/CLI.php create mode 100644 src/PHPixie/CLI/Builder.php create mode 100644 src/PHPixie/CLI/Context.php create mode 100644 src/PHPixie/CLI/Context/Container.php create mode 100644 src/PHPixie/CLI/Context/Container/Implementation.php create mode 100644 src/PHPixie/CLI/Context/Container/Settable.php create mode 100644 src/PHPixie/CLI/Context/SAPI.php create mode 100644 src/PHPixie/CLI/Exception.php create mode 100644 src/PHPixie/CLI/Input.php create mode 100644 src/PHPixie/CLI/Parameters.php create mode 100644 src/PHPixie/CLI/Prompt.php create mode 100644 src/PHPixie/CLI/Stream/Input.php create mode 100644 src/PHPixie/CLI/Stream/Output.php create mode 100644 src/PHPixie/CLI/Streams.php create mode 100644 tests/PHPixie/Tests/CLI/Context/SAPITest.php create mode 100644 tests/PHPixie/Tests/CLI/PromptTest.php create mode 100644 tests/phpunit.php diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..71ae68f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +vendor +composer.lock +.idea + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ef96370 --- /dev/null +++ b/composer.json @@ -0,0 +1,32 @@ +{ + "name": "phpixie/cli", + "description": "CLI library for PHPixie", + "keywords": ["console", "cli", "command line"], + "homepage": "http://phpixie.com", + "type": "library", + "license": "BSD", + "authors": [ + { + "name": "Roman Tsiupa", + "email": "draconyster@gmail.com", + "homepage": "http://dracony.org" + } + ], + "require": { + "phpixie/slice": "~3.0" + }, + "require-dev": { + "phpixie/test": "~3.0" + }, + "autoload": { + "psr-4": { + "PHPixie\\": "src/PHPixie", + "PHPixie\\Tests\\": "tests/PHPixie/Tests" + } + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..b6a599c --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,16 @@ + + + + ./tests + ./tests/PHPixie/Tests/ORM/Functional + + + ./tests/PHPixie/Tests/ORM/Functional + + + + + ./src + + + \ No newline at end of file diff --git a/src/PHPixie/CLI.php b/src/PHPixie/CLI.php new file mode 100644 index 0000000..8377b72 --- /dev/null +++ b/src/PHPixie/CLI.php @@ -0,0 +1,40 @@ +builder = $this->buildBuilder($contextContainer); + } + + /** + * + * @return \PHPixie\CLI\Context\SAPI + */ + public function context() + { + return $this->builder->context(); + } + + public function buildSapiContext() + { + return $this->builder->buildSapiContext(); + } + + /** + * + * @param type $contextContainer + * @return \PHPixie\CLI\Builder + */ + protected function buildBuilder($contextContainer) + { + return new CLI\Builder($contextContainer); + } +} diff --git a/src/PHPixie/CLI/Builder.php b/src/PHPixie/CLI/Builder.php new file mode 100644 index 0000000..40f61c8 --- /dev/null +++ b/src/PHPixie/CLI/Builder.php @@ -0,0 +1,79 @@ +contextContainer = $contextContainer; + } + + /** + * + * @param string $resource + * @return \PHPixie\CLI\Stream\Input + */ + public function inputStream($resource) + { + return new Stream\Input($resource); + } + + /** + * + * @param string $resource + * @return \PHPixie\CLI\Stream\Output + */ + public function outputStream($resource) + { + return new Stream\Output($resource); + } + + /** + * + * @return \PHPixie\CLI\Context\SAPI + */ + public function context() + { + return $this->contextContainer()->cliContext(); + } + + /** + * + * @return \PHPixie\CLI\Context\Container\Implementation + */ + public function contextContainer() + { + if($this->contextContainer === null) { + $context = $this->buildSapiContext(); + $this->contextContainer = $this->buildContextContainer($context); + } + + return $this->contextContainer; + } + + /** + * + * @param \PHPixie\CLI\Context\SAPI $context + * @return \PHPixie\CLI\Context\Container\Implementation + */ + public function buildContextContainer($context) + { + return new \PHPixie\CLI\Context\Container\Implementation($context); + } + + /** + * + * @return \PHPixie\CLI\Context\SAPI + */ + public function buildSapiContext() + { + return new Context\SAPI($this); + } +} diff --git a/src/PHPixie/CLI/Context.php b/src/PHPixie/CLI/Context.php new file mode 100644 index 0000000..33f67b9 --- /dev/null +++ b/src/PHPixie/CLI/Context.php @@ -0,0 +1,14 @@ +cliContext = $cliContext; + } + + /** + * + * @return \PHPixie\CLI\Context\SAPI + */ + public function cliContext() + { + return $this->cliContext; + } + + public function setCliContext($cliContext) + { + return $this->cliContext = $cliContext; + } +} diff --git a/src/PHPixie/CLI/Context/Container/Settable.php b/src/PHPixie/CLI/Context/Container/Settable.php new file mode 100644 index 0000000..9845d9a --- /dev/null +++ b/src/PHPixie/CLI/Context/Container/Settable.php @@ -0,0 +1,8 @@ +builder = $builder; + } + + /** + * + * @return \PHPixie\CLI\Stream\Input + */ + public function inputStream() + { + if($this->inputStream === null) { + $this->inputStream = $this->builder->inputStream(STDIN); + } + + return $this->inputStream; + } + + /** + * + * @return \PHPixie\CLI\Stream\Output + */ + public function outputStream() + { + if($this->outputStream === null) { + $this->outputStream = $this->builder->outputStream(STDOUT); + } + + return $this->outputStream; + } + + /** + * + * @return \PHPixie\CLI\Stream\Output + */ + public function errorStream() + { + if($this->errorStream === null) { + $this->errorStream = $this->builder->outputStream(STDERR); + } + + return $this->errorStream; + } + + public function currentDirectory() + { + if($this->currentDirectory === null) { + $this->currentDirectory = getcwd(); + } + + return $this->currentDirectory; + } + + public function arguments() + { + $this->requireParsedArguments(); + return $this->arguments; + } + + public function options() + { + $this->requireParsedArguments(); + return $this->options; + } + + public function rawArguments() + { + global $argv; + return $argv; + } + + public function requireParsedArguments() + { + if($this->arguments !== null) { + return; + } + + global $argv; + $parameters = $this->rawArguments(); + + $arguments = array(); + $options = array(); + + array_shift($parameters); + foreach($parameters as $parameter) { + + if($parameter{0} !== '-') { + $arguments[]= $parameter; + continue; + } + + if(preg_match('#^-([a-z0-9])=(.*)$#i', $parameter, $matches)) { + foreach(str_split($matches[1]) as $option) { + $options[$matches[1]] = $matches[2]; + } + continue; + } + + if(preg_match('#^-([a-z0-9]*)$#i', $parameter, $matches)) { + foreach(str_split($matches[1]) as $option) { + $options[$option] = true; + } + continue; + } + + if(preg_match('#^--([a-z0-9\-]+)(?:=(.*))?$#i', $parameter, $matches)) { + $option = $matches[1]; + + if(!empty($matches[2])) { + $options[$option] = $matches[2]; + }else{ + $options[$option] = true; + } + continue; + } + + throw new \PHPixie\CLI\Exception("Invalid option '$parameter'"); + } + + $this->options = $options; + $this->arguments = $arguments; + } + + public function setExitCode($exitCode) + { + $this->exitCode = $exitCode; + } + + public function exitCode() + { + return $this->exitCode; + } +} diff --git a/src/PHPixie/CLI/Exception.php b/src/PHPixie/CLI/Exception.php new file mode 100644 index 0000000..743068d --- /dev/null +++ b/src/PHPixie/CLI/Exception.php @@ -0,0 +1,8 @@ + $name) { + $opt = isset($flags[$name]) ? $alias : $alias.'::'; + $shortOpts[$opt] = true; + } + + $shortOpts = implode('', array_keys($shortOpts)); + $data = getopt($shortOpts, $longOpts); + + $result = array(); + foreach($data as $name => $value) { + if(isset($shortAliases[$name])) { + $name = $shortAliases[$name]; + } + + if(isset($flags[$name])) { + $value = true; + } + + $result[$name] = $value; + } + + return $result; + } + + public function getCurrentDirectory() + { + return getcwd(); + } + + public function getArguments() + { + global $argv; + $args = $argv; + array_shift($args); + return $args; + } +} diff --git a/src/PHPixie/CLI/Parameters.php b/src/PHPixie/CLI/Parameters.php new file mode 100644 index 0000000..ed84f69 --- /dev/null +++ b/src/PHPixie/CLI/Parameters.php @@ -0,0 +1,25 @@ +context = $context; + } + + public function options() + { + $this->requireParsedArguments(); + return $this->options; + } + + public function arguments() + { + $this->requireParsedArguments(); + return $this->arguments; + } +} diff --git a/src/PHPixie/CLI/Prompt.php b/src/PHPixie/CLI/Prompt.php new file mode 100644 index 0000000..059a700 --- /dev/null +++ b/src/PHPixie/CLI/Prompt.php @@ -0,0 +1,95 @@ +contextContainer = $contextContainer; + } + + public function prompt($string, $newLine = false) + { + $context = $this->context(); + $string.= $newLine ? "\n" : ' '; + + $context->outputStream()->write($string); + return $context->inputStream()->readLine(); + } + + public function charPrompt($string) + { + $context->outputStream()->write($string.' '); + return $context->inputStream()->read(1); + } + + public function table($rows) + { + $sizes = array(); + foreach($rows as $i => $row) { + $rows[$i] = (array) $row; + } + + $keys = array_keys($rows[0]); + + foreach($keys as $key) { + $sizes[$key] = strlen($key); + } + + foreach($rows as $row) { + foreach($keys as $key) { + $length = strlen($row[$key]); + if($sizes[$key] < $length) { + $sizes[$key] = $length; + } + } + } + + foreach($sizes as $key => $size) { + $sizes[$key]+= 3; + } + + $separator = ''; + foreach($sizes as $size) { + $separator.= str_pad('+', $size, '-'); + } + $separator.="+\n"; + + $result = $separator; + + $header = array_combine($keys, $keys); + array_unshift($header, $rows); + + foreach($sizes as $key => $size) { + $result.= str_pad('| '.$key, $size); + } + + $result.="|\n"; + + $result.= $separator; + + foreach($rows as $i => $row) { + foreach($sizes as $key => $size) { + $result.= str_pad('| '.$row[$key], $size); + } + + $result.="|\n"; + } + + $result .= $separator; + return $result; + } + + protected function tableSeparator($sizes) + { + + } + + protected function context() + { + return $this->contextContainer->cliContext(); + } +} diff --git a/src/PHPixie/CLI/Stream/Input.php b/src/PHPixie/CLI/Stream/Input.php new file mode 100644 index 0000000..1349bf1 --- /dev/null +++ b/src/PHPixie/CLI/Stream/Input.php @@ -0,0 +1,39 @@ +resource = $resource; + } + + public function readUntilEnd() + { + return stream_get_contents($this->resource, -1); + } + + public function finished() + { + return feof($this->resource); + } + + /** + * @inheritdoc + */ + public function read($length) + { + return fread($this->resource, $length); + } + + /** + * @inheritdoc + */ + public function readLine() + { + return fgets($this->resource, $length); + } +} diff --git a/src/PHPixie/CLI/Stream/Output.php b/src/PHPixie/CLI/Stream/Output.php new file mode 100644 index 0000000..115e1b2 --- /dev/null +++ b/src/PHPixie/CLI/Stream/Output.php @@ -0,0 +1,23 @@ +resource = $resource; + } + + public function write($string) + { + fwrite($this->resource, $string); + } + + public function writeLine($string) + { + $this->write($string."\n"); + } +} diff --git a/src/PHPixie/CLI/Streams.php b/src/PHPixie/CLI/Streams.php new file mode 100644 index 0000000..43f8409 --- /dev/null +++ b/src/PHPixie/CLI/Streams.php @@ -0,0 +1,6 @@ +builder = $this->quickMock('PHPixie\CLI\Builder'); + $this->context = new \PHPixie\CLI\Context\SAPI($this->builder); + } + + public function testInputStream() + { + $this->streamTest('inputStream', 'input', STDIN); + } + + public function testOutputStream() + { + $this->streamTest('outputStream', 'output', STDOUT); + } + + public function testErrorStream() + { + $this->streamTest('errorStream', 'output', STDERR); + } + + public function testCurrentDirectory() + { + for($i=0; $i<2; $i++) { + $this->assertSame(getcwd(), $this->context->currentDirectory()); + } + } + + public function testRawArguments() + { + global $argv; + for($i=0; $i<2; $i++) { + $this->assertSame($argv, $this->context->rawArguments()); + } + } + + public function testArguments() + { + $context = $this->contextMock(array('rawArguments')); + + $this->method($context, 'rawArguments', array( + '', + 'test', + '-abc', + '-d=4', + 'test2', + '--debug', + '--value=5' + ), array(), 0); + + $options = $this->quickMock('\PHPixie\Slice\Data'); + $arguments = $this->quickMock('\PHPixie\Slice\Data'); + + $this->assertSame(array( + 'a' => true, + 'b' => true, + 'c' => true, + 'd' => '4', + 'debug' => true, + 'value' => '5' + ), $context->options()); + + $this->assertSame(array( + 'test', + 'test2' + ), $context->arguments()); + } + + protected function streamTest($method, $type, $resource) + { + $stream = $this->quickMock('PHPixie\CLI\Streams\\'.ucfirst($type)); + $this->method($this->builder, $type.'Stream', $stream, array($resource), 0); + + for($i=0; $i<2; $i++) { + $this->assertSame($stream, call_user_func(array($this->context, $method))); + } + } + + protected function contextMock($methods) + { + return $this->getMock( + '\PHPixie\CLI\Context\SAPI', + $methods, + array($this->builder) + ); + } +} diff --git a/tests/PHPixie/Tests/CLI/PromptTest.php b/tests/PHPixie/Tests/CLI/PromptTest.php new file mode 100644 index 0000000..c216368 --- /dev/null +++ b/tests/PHPixie/Tests/CLI/PromptTest.php @@ -0,0 +1,32 @@ +contextContainer = $this->quickMock('\PHPixie\CLI\Context\Container'); + $this->prompt = new \PHPixie\CLI\Prompt($this->contextContainer); + } + + public function testTable() + { + $this->assertSame( + "+-----+----+\n". + "| abc | b |\n". + "+-----+----+\n". + "| 1 | 1 |\n". + "| 1 | 15 |\n". + "+-----+----+\n", + $this->prompt->table([ + ['abc' =>1, 'b' => 1], + ['abc' =>1, 'b' => 15] + ]) + ); + } +} diff --git a/tests/phpunit.php b/tests/phpunit.php new file mode 100644 index 0000000..0a149f6 --- /dev/null +++ b/tests/phpunit.php @@ -0,0 +1,2 @@ +