From cbeaa4a0479ce7023201d4032978899ffaceb187 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 10 Nov 2017 14:18:18 +0100 Subject: [PATCH] replace Doku_CLI with splitbrain\phpcli\CLI It has few more features (like turning down verbosity) and looks nicer --- .gitignore | 4 + bin/dwpage.php | 54 +- bin/gittool.php | 75 +-- bin/indexer.php | 24 +- bin/render.php | 31 +- bin/striplangs.php | 45 +- bin/wantedpages.php | 20 +- composer.json | 3 +- composer.lock | 58 ++- inc/cli.php | 3 + vendor/composer/autoload_psr4.php | 1 + vendor/composer/autoload_static.php | 5 + vendor/composer/installed.json | 53 ++ vendor/splitbrain/php-cli/.gitignore | 8 + vendor/splitbrain/php-cli/LICENSE | 21 + vendor/splitbrain/php-cli/README.md | 158 ++++++ vendor/splitbrain/php-cli/composer.json | 34 ++ vendor/splitbrain/php-cli/src/CLI.php | 312 ++++++++++++ vendor/splitbrain/php-cli/src/Colors.php | 166 +++++++ vendor/splitbrain/php-cli/src/Exception.php | 35 ++ vendor/splitbrain/php-cli/src/Options.php | 468 ++++++++++++++++++ vendor/splitbrain/php-cli/src/PSR3CLI.php | 13 + .../splitbrain/php-cli/src/TableFormatter.php | 307 ++++++++++++ 23 files changed, 1782 insertions(+), 116 deletions(-) create mode 100644 vendor/splitbrain/php-cli/.gitignore create mode 100644 vendor/splitbrain/php-cli/LICENSE create mode 100644 vendor/splitbrain/php-cli/README.md create mode 100644 vendor/splitbrain/php-cli/composer.json create mode 100644 vendor/splitbrain/php-cli/src/CLI.php create mode 100644 vendor/splitbrain/php-cli/src/Colors.php create mode 100644 vendor/splitbrain/php-cli/src/Exception.php create mode 100644 vendor/splitbrain/php-cli/src/Options.php create mode 100644 vendor/splitbrain/php-cli/src/PSR3CLI.php create mode 100644 vendor/splitbrain/php-cli/src/TableFormatter.php diff --git a/.gitignore b/.gitignore index 18a21abff..a2e2107b1 100644 --- a/.gitignore +++ b/.gitignore @@ -85,4 +85,8 @@ vendor/marcusschwarz/lesserphp/package.sh vendor/marcusschwarz/lesserphp/lessify* vendor/marcusschwarz/lesserphp/Makefile vendor/marcusschwarz/lesserphp/plessc +vendor/splitbrain/php-cli/examples/* +vendor/splitbrain/php-cli/screenshot* +vendor/splitbrain/php-cli/generate-api.sh +vendor/splitbrain/php-cli/apigen.neon diff --git a/bin/dwpage.php b/bin/dwpage.php index 1c1a1c10e..3124563fc 100755 --- a/bin/dwpage.php +++ b/bin/dwpage.php @@ -1,13 +1,17 @@ #!/usr/bin/php registerOption( 'force', @@ -32,17 +36,17 @@ class PageCLI extends DokuCLI { 'username' ); $options->setHelp( - 'Utility to help command line Dokuwiki page editing, allow '. + 'Utility to help command line Dokuwiki page editing, allow ' . 'pages to be checked out for editing then committed after changes' ); /* checkout command */ $options->registerCommand( 'checkout', - 'Checks out a file from the repository, using the wiki id and obtaining '. - 'a lock for the page. '."\n". - 'If a working_file is specified, this is where the page is copied to. '. - 'Otherwise defaults to the same as the wiki page in the current '. + 'Checks out a file from the repository, using the wiki id and obtaining ' . + 'a lock for the page. ' . "\n" . + 'If a working_file is specified, this is where the page is copied to. ' . + 'Otherwise defaults to the same as the wiki page in the current ' . 'working directory.' ); $options->registerArgument( @@ -61,7 +65,7 @@ class PageCLI extends DokuCLI { /* commit command */ $options->registerCommand( 'commit', - 'Checks in the working_file into the repository using the specified '. + 'Checks in the working_file into the repository using the specified ' . 'wiki id, archiving the previous version.' ); $options->registerArgument( @@ -121,23 +125,24 @@ class PageCLI extends DokuCLI { * * Arguments and options have been parsed when this is run * - * @param DokuCLI_Options $options + * @param Options $options * @return void */ - protected function main(DokuCLI_Options $options) { - $this->force = $options->getOpt('force', false); + protected function main(Options $options) { + $this->force = $options->getOpt('force', false); $this->username = $options->getOpt('user', $this->getUser()); $command = $options->getCmd(); + $args = $options->getArgs(); switch($command) { case 'checkout': - $wiki_id = array_shift($options->args); - $localfile = array_shift($options->args); + $wiki_id = array_shift($args); + $localfile = array_shift($args); $this->commandCheckout($wiki_id, $localfile); break; case 'commit': - $localfile = array_shift($options->args); - $wiki_id = array_shift($options->args); + $localfile = array_shift($args); + $wiki_id = array_shift($args); $this->commandCommit( $localfile, $wiki_id, @@ -146,12 +151,12 @@ class PageCLI extends DokuCLI { ); break; case 'lock': - $wiki_id = array_shift($options->args); + $wiki_id = array_shift($args); $this->obtainLock($wiki_id); $this->success("$wiki_id locked"); break; case 'unlock': - $wiki_id = array_shift($options->args); + $wiki_id = array_shift($args); $this->clearLock($wiki_id); $this->success("$wiki_id unlocked"); break; @@ -177,11 +182,11 @@ class PageCLI extends DokuCLI { } if(empty($localfile)) { - $localfile = getcwd().'/'.utf8_basename($wiki_fn); + $localfile = getcwd() . '/' . utf8_basename($wiki_fn); } if(!file_exists(dirname($localfile))) { - $this->fatal("Directory ".dirname($localfile)." does not exist"); + $this->fatal("Directory " . dirname($localfile) . " does not exist"); } if(stristr(realpath(dirname($localfile)), realpath($conf['datadir'])) !== false) { @@ -204,7 +209,7 @@ class PageCLI extends DokuCLI { * @param string $localfile * @param string $wiki_id * @param string $message - * @param bool $minor + * @param bool $minor */ protected function commandCommit($localfile, $wiki_id, $message, $minor) { $wiki_id = cleanID($wiki_id); @@ -312,7 +317,6 @@ class PageCLI extends DokuCLI { } } - // Main $cli = new PageCLI(); -$cli->run(); \ No newline at end of file +$cli->run(); diff --git a/bin/gittool.php b/bin/gittool.php index cbadb5bfa..f75ac1d9d 100755 --- a/bin/gittool.php +++ b/bin/gittool.php @@ -1,27 +1,31 @@ #!/usr/bin/php */ -class GitToolCLI extends DokuCLI { +class GitToolCLI extends CLI { /** * Register options and arguments on the given $options object * - * @param DokuCLI_Options $options + * @param Options $options * @return void */ - protected function setup(DokuCLI_Options $options) { + protected function setup(Options $options) { $options->setHelp( - "Manage git repositories for DokuWiki and its plugins and templates.\n\n". - "$> ./bin/gittool.php clone gallery template:ach\n". - "$> ./bin/gittool.php repos\n". + "Manage git repositories for DokuWiki and its plugins and templates.\n\n" . + "$> ./bin/gittool.php clone gallery template:ach\n" . + "$> ./bin/gittool.php repos\n" . "$> ./bin/gittool.php origin -v" ); @@ -33,7 +37,7 @@ class GitToolCLI extends DokuCLI { $options->registerCommand( 'clone', - 'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org '. + 'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org ' . 'plugin repository to find the proper git repository. Multiple extensions can be given as parameters' ); $options->registerArgument( @@ -45,7 +49,7 @@ class GitToolCLI extends DokuCLI { $options->registerCommand( 'install', - 'The same as clone, but when no git source repository can be found, the extension is installed via '. + 'The same as clone, but when no git source repository can be found, the extension is installed via ' . 'download' ); $options->registerArgument( @@ -62,7 +66,7 @@ class GitToolCLI extends DokuCLI { $options->registerCommand( '*', - 'Any unknown commands are assumed to be arguments to git and will be executed in all repositories '. + 'Any unknown commands are assumed to be arguments to git and will be executed in all repositories ' . 'found within this DokuWiki installation' ); } @@ -72,39 +76,40 @@ class GitToolCLI extends DokuCLI { * * Arguments and options have been parsed when this is run * - * @param DokuCLI_Options $options + * @param Options $options * @return void */ - protected function main(DokuCLI_Options $options) { + protected function main(Options $options) { $command = $options->getCmd(); - if(!$command) $command = array_shift($options->args); + $args = $options->getArgs(); + if(!$command) $command = array_shift($args); switch($command) { case '': echo $options->help(); break; case 'clone': - $this->cmd_clone($options->args); + $this->cmd_clone($args); break; case 'install': - $this->cmd_install($options->args); + $this->cmd_install($args); break; case 'repo': case 'repos': $this->cmd_repos(); break; default: - $this->cmd_git($command, $options->args); + $this->cmd_git($command, $args); } } /** * Tries to install the given extensions using git clone * - * @param array $extensions + * @param array $extensions */ public function cmd_clone($extensions) { - $errors = array(); + $errors = array(); $succeeded = array(); foreach($extensions as $ext) { @@ -123,17 +128,17 @@ class GitToolCLI extends DokuCLI { } echo "\n"; - if($succeeded) $this->success('successfully cloned the following extensions: '.join(', ', $succeeded)); - if($errors) $this->error('failed to clone the following extensions: '.join(', ', $errors)); + if($succeeded) $this->success('successfully cloned the following extensions: ' . join(', ', $succeeded)); + if($errors) $this->error('failed to clone the following extensions: ' . join(', ', $errors)); } /** * Tries to install the given extensions using git clone with fallback to install * - * @param array $extensions + * @param array $extensions */ public function cmd_install($extensions) { - $errors = array(); + $errors = array(); $succeeded = array(); foreach($extensions as $ext) { @@ -156,8 +161,8 @@ class GitToolCLI extends DokuCLI { } echo "\n"; - if($succeeded) $this->success('successfully installed the following extensions: '.join(', ', $succeeded)); - if($errors) $this->error('failed to install the following extensions: '.join(', ', $errors)); + if($succeeded) $this->success('successfully installed the following extensions: ' . join(', ', $succeeded)); + if($errors) $this->error('failed to install the following extensions: ' . join(', ', $errors)); } /** @@ -247,9 +252,9 @@ class GitToolCLI extends DokuCLI { */ private function cloneExtension($ext, $repo) { if(substr($ext, 0, 9) == 'template:') { - $target = fullpath(tpl_incdir().'../'.substr($ext, 9)); + $target = fullpath(tpl_incdir() . '../' . substr($ext, 9)); } else { - $target = DOKU_PLUGIN.$ext; + $target = DOKU_PLUGIN . $ext; } $this->info("cloning $ext from $repo to $target"); @@ -274,15 +279,15 @@ class GitToolCLI extends DokuCLI { private function findRepos() { $this->info('Looking for .git directories'); $data = array_merge( - glob(DOKU_INC.'.git', GLOB_ONLYDIR), - glob(DOKU_PLUGIN.'*/.git', GLOB_ONLYDIR), - glob(fullpath(tpl_incdir().'../').'/*/.git', GLOB_ONLYDIR) + glob(DOKU_INC . '.git', GLOB_ONLYDIR), + glob(DOKU_PLUGIN . '*/.git', GLOB_ONLYDIR), + glob(fullpath(tpl_incdir() . '../') . '/*/.git', GLOB_ONLYDIR) ); if(!$data) { $this->error('Found no .git directories'); } else { - $this->success('Found '.count($data).' .git directories'); + $this->success('Found ' . count($data) . ' .git directories'); } $data = array_map('fullpath', array_map('dirname', $data)); return $data; @@ -308,7 +313,7 @@ class GitToolCLI extends DokuCLI { if(preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) { $user = $m[1]; $repo = $m[2]; - return 'https://github.com/'.$user.'/'.$repo.'.git'; + return 'https://github.com/' . $user . '/' . $repo . '.git'; } // match gitorious repos @@ -317,14 +322,14 @@ class GitToolCLI extends DokuCLI { $repo = $m[2]; if(!$repo) $repo = $user; - return 'https://git.gitorious.org/'.$user.'/'.$repo.'.git'; + return 'https://git.gitorious.org/' . $user . '/' . $repo . '.git'; } // match bitbucket repos - most people seem to use mercurial there though if(preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) { $user = $m[1]; $repo = $m[2]; - return 'https://bitbucket.org/'.$user.'/'.$repo.'.git'; + return 'https://bitbucket.org/' . $user . '/' . $repo . '.git'; } return false; @@ -333,4 +338,4 @@ class GitToolCLI extends DokuCLI { // Main $cli = new GitToolCLI(); -$cli->run(); \ No newline at end of file +$cli->run(); diff --git a/bin/indexer.php b/bin/indexer.php index 13895c36a..4d19a9515 100755 --- a/bin/indexer.php +++ b/bin/indexer.php @@ -1,13 +1,17 @@ #!/usr/bin/php setHelp( - 'Updates the searchindex by indexing all new or changed pages. When the -c option is '. + 'Updates the searchindex by indexing all new or changed pages. When the -c option is ' . 'given the index is cleared first.' ); @@ -41,10 +45,10 @@ class IndexerCLI extends DokuCLI { * * Arguments and options have been parsed when this is run * - * @param DokuCLI_Options $options + * @param Options $options * @return void */ - protected function main(DokuCLI_Options $options) { + protected function main(Options $options) { $this->clear = $options->getOpt('clear'); $this->quiet = $options->getOpt('quiet'); @@ -61,7 +65,7 @@ class IndexerCLI extends DokuCLI { $data = array(); $this->quietecho("Searching pages... "); search($data, $conf['datadir'], 'search_allpages', array('skipacl' => true)); - $this->quietecho(count($data)." pages found.\n"); + $this->quietecho(count($data) . " pages found.\n"); foreach($data as $val) { $this->index($val['id']); @@ -100,4 +104,4 @@ class IndexerCLI extends DokuCLI { // Main $cli = new IndexerCLI(); -$cli->run(); \ No newline at end of file +$cli->run(); diff --git a/bin/render.php b/bin/render.php index 672993223..cc4a00314 100755 --- a/bin/render.php +++ b/bin/render.php @@ -1,9 +1,12 @@ #!/usr/bin/php */ -class RenderCLI extends DokuCLI { +class RenderCLI extends CLI { /** * Register options and arguments on the given $options object * - * @param DokuCLI_Options $options + * @param Options $options * @return void */ - protected function setup(DokuCLI_Options $options) { + protected function setup(Options $options) { $options->setHelp( - 'A simple commandline tool to render some DokuWiki syntax with a given renderer.'. - "\n\n". - 'This may not work for plugins that expect a certain environment to be '. - 'set up before rendering, but should work for most or even all standard '. + 'A simple commandline tool to render some DokuWiki syntax with a given renderer.' . + "\n\n" . + 'This may not work for plugins that expect a certain environment to be ' . + 'set up before rendering, but should work for most or even all standard ' . 'DokuWiki markup' ); $options->registerOption('renderer', 'The renderer mode to use. Defaults to xhtml', 'r', 'mode'); @@ -40,16 +43,16 @@ class RenderCLI extends DokuCLI { * * Arguments and options have been parsed when this is run * - * @param DokuCLI_Options $options + * @param Options $options * @throws DokuCLI_Exception * @return void */ - protected function main(DokuCLI_Options $options) { + protected function main(Options $options) { $renderer = $options->getOpt('renderer', 'xhtml'); // do the action $source = stream_get_contents(STDIN); - $info = array(); + $info = array(); $result = p_render($renderer, p_get_instructions($source), $info); if(is_null($result)) throw new DokuCLI_Exception("No such renderer $renderer"); echo $result; @@ -58,4 +61,4 @@ class RenderCLI extends DokuCLI { // Main $cli = new RenderCLI(); -$cli->run(); \ No newline at end of file +$cli->run(); diff --git a/bin/striplangs.php b/bin/striplangs.php index 82d27d462..1800971e5 100755 --- a/bin/striplangs.php +++ b/bin/striplangs.php @@ -1,25 +1,28 @@ #!/usr/bin/php setHelp( - 'Remove all languages from the installation, besides the ones specified. English language '. + 'Remove all languages from the installation, besides the ones specified. English language ' . 'is never removed!' ); @@ -41,10 +44,10 @@ class StripLangsCLI extends DokuCLI { * * Arguments and options have been parsed when this is run * - * @param DokuCLI_Options $options + * @param Options $options * @return void */ - protected function main(DokuCLI_Options $options) { + protected function main(Options $options) { if($options->getOpt('keep')) { $keep = explode(',', $options->getOpt('keep')); if(!in_array('en', $keep)) $keep[] = 'en'; @@ -56,16 +59,16 @@ class StripLangsCLI extends DokuCLI { } // Kill all language directories in /inc/lang and /lib/plugins besides those in $langs array - $this->stripDirLangs(realpath(dirname(__FILE__).'/../inc/lang'), $keep); - $this->processExtensions(realpath(dirname(__FILE__).'/../lib/plugins'), $keep); - $this->processExtensions(realpath(dirname(__FILE__).'/../lib/tpl'), $keep); + $this->stripDirLangs(realpath(dirname(__FILE__) . '/../inc/lang'), $keep); + $this->processExtensions(realpath(dirname(__FILE__) . '/../lib/plugins'), $keep); + $this->processExtensions(realpath(dirname(__FILE__) . '/../lib/tpl'), $keep); } /** * Strip languages from extensions * - * @param string $path path to plugin or template dir - * @param array $keep_langs languages to keep + * @param string $path path to plugin or template dir + * @param array $keep_langs languages to keep */ protected function processExtensions($path, $keep_langs) { if(is_dir($path)) { @@ -73,9 +76,9 @@ class StripLangsCLI extends DokuCLI { foreach($entries as $entry) { if($entry != "." && $entry != "..") { - if(is_dir($path.'/'.$entry)) { + if(is_dir($path . '/' . $entry)) { - $plugin_langs = $path.'/'.$entry.'/lang'; + $plugin_langs = $path . '/' . $entry . '/lang'; if(is_dir($plugin_langs)) { $this->stripDirLangs($plugin_langs, $keep_langs); @@ -89,17 +92,17 @@ class StripLangsCLI extends DokuCLI { /** * Strip languages from path * - * @param string $path path to lang dir - * @param array $keep_langs languages to keep + * @param string $path path to lang dir + * @param array $keep_langs languages to keep */ protected function stripDirLangs($path, $keep_langs) { $dir = dir($path); while(($cur_dir = $dir->read()) !== false) { - if($cur_dir != '.' and $cur_dir != '..' and is_dir($path.'/'.$cur_dir)) { + if($cur_dir != '.' and $cur_dir != '..' and is_dir($path . '/' . $cur_dir)) { if(!in_array($cur_dir, $keep_langs, true)) { - io_rmdir($path.'/'.$cur_dir, true); + io_rmdir($path . '/' . $cur_dir, true); } } } @@ -108,4 +111,4 @@ class StripLangsCLI extends DokuCLI { } $cli = new StripLangsCLI(); -$cli->run(); \ No newline at end of file +$cli->run(); diff --git a/bin/wantedpages.php b/bin/wantedpages.php index 6887ac18e..0240eb958 100755 --- a/bin/wantedpages.php +++ b/bin/wantedpages.php @@ -1,5 +1,9 @@ #!/usr/bin/php setHelp( 'Outputs a list of wanted pages (pages that do not exist yet) and their origin pages ' . ' (the pages that are linkin to these missing pages).' @@ -54,13 +58,13 @@ class WantedPagesCLI extends DokuCLI { * * Arguments and options have been parsed when this is run * - * @param DokuCLI_Options $options + * @param Options $options * @return void */ - protected function main(DokuCLI_Options $options) { - - if($options->args) { - $startdir = dirname(wikiFN($options->args[0] . ':xxx')); + protected function main(Options $options) { + $args = $options->getArgs(); + if($args) { + $startdir = dirname(wikiFN($args[0] . ':xxx')); } else { $startdir = dirname(wikiFN('xxx')); } diff --git a/composer.json b/composer.json index 95d401e24..948b97a62 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "geshi/geshi": "^1.0", "openpsa/universalfeedcreator": "^1.8", "aziraphale/email-address-validator": "^2", - "marcusschwarz/lesserphp": "^0.5.1" + "marcusschwarz/lesserphp": "^0.5.1", + "splitbrain/php-cli": "^1.1" }, "config": { "platform": { diff --git a/composer.lock b/composer.lock index 306facf68..9a2ceb8a0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "149ef96a4cadb6765aac9e7c6a2b5b17", + "content-hash": "d5c15248668d2dd749de47b106049b77", "packages": [ { "name": "aziraphale/email-address-validator", @@ -437,6 +437,57 @@ "zip" ], "time": "2017-06-11T06:11:38+00:00" + }, + { + "name": "splitbrain/php-cli", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/splitbrain/php-cli.git", + "reference": "4795af10ff1c3b1ac614ef380d7d810af495e6f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/splitbrain/php-cli/zipball/4795af10ff1c3b1ac614ef380d7d810af495e6f1", + "reference": "4795af10ff1c3b1ac614ef380d7d810af495e6f1", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.5.*" + }, + "suggest": { + "psr/log": "Allows you to make the CLI available as PSR-3 logger" + }, + "type": "library", + "autoload": { + "psr-4": { + "splitbrain\\phpcli\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Gohr", + "email": "andi@splitbrain.org" + } + ], + "description": "Easy command line scripts for PHP with opt parsing and color output. No dependencies", + "keywords": [ + "argparse", + "cli", + "command line", + "console", + "getopt", + "optparse", + "terminal" + ], + "time": "2017-10-27T16:18:07+00:00" } ], "packages-dev": [], @@ -448,5 +499,8 @@ "platform": { "php": ">=5.6" }, - "platform-dev": [] + "platform-dev": [], + "platform-overrides": { + "php": "5.6" + } } diff --git a/inc/cli.php b/inc/cli.php index 4c4403a39..cb4fc587d 100644 --- a/inc/cli.php +++ b/inc/cli.php @@ -25,6 +25,9 @@ abstract class DokuCLI { $this->options = new DokuCLI_Options(); $this->colors = new DokuCLI_Colors(); + + dbg_deprecated('use \splitbrain\phpcli\CLI instead'); + $this->error('DokuCLI is deprecated, use \splitbrain\phpcli\CLI instead.'); } /** diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index 3488312df..3a072f739 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'splitbrain\\phpcli\\' => array($vendorDir . '/splitbrain/php-cli/src'), 'splitbrain\\PHPArchive\\' => array($vendorDir . '/splitbrain/php-archive/src'), 'phpseclib\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'), ); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index d22d6d13c..3f7b01e01 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -14,6 +14,7 @@ class ComposerStaticInita19a915ee98347a0c787119619d2ff9b public static $prefixLengthsPsr4 = array ( 's' => array ( + 'splitbrain\\phpcli\\' => 18, 'splitbrain\\PHPArchive\\' => 22, ), 'p' => @@ -23,6 +24,10 @@ class ComposerStaticInita19a915ee98347a0c787119619d2ff9b ); public static $prefixDirsPsr4 = array ( + 'splitbrain\\phpcli\\' => + array ( + 0 => __DIR__ . '/..' . '/splitbrain/php-cli/src', + ), 'splitbrain\\PHPArchive\\' => array ( 0 => __DIR__ . '/..' . '/splitbrain/php-archive/src', diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 391262918..381e18292 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -446,5 +446,58 @@ "feeds", "rss" ] + }, + { + "name": "splitbrain/php-cli", + "version": "1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/splitbrain/php-cli.git", + "reference": "4795af10ff1c3b1ac614ef380d7d810af495e6f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/splitbrain/php-cli/zipball/4795af10ff1c3b1ac614ef380d7d810af495e6f1", + "reference": "4795af10ff1c3b1ac614ef380d7d810af495e6f1", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.5.*" + }, + "suggest": { + "psr/log": "Allows you to make the CLI available as PSR-3 logger" + }, + "time": "2017-10-27T16:18:07+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "splitbrain\\phpcli\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Gohr", + "email": "andi@splitbrain.org" + } + ], + "description": "Easy command line scripts for PHP with opt parsing and color output. No dependencies", + "keywords": [ + "argparse", + "cli", + "command line", + "console", + "getopt", + "optparse", + "terminal" + ] } ] diff --git a/vendor/splitbrain/php-cli/.gitignore b/vendor/splitbrain/php-cli/.gitignore new file mode 100644 index 000000000..c6277c187 --- /dev/null +++ b/vendor/splitbrain/php-cli/.gitignore @@ -0,0 +1,8 @@ +*.iml +.idea/ +composer.phar +vendor/ +composer.lock +apigen.phar +docs/ + diff --git a/vendor/splitbrain/php-cli/LICENSE b/vendor/splitbrain/php-cli/LICENSE new file mode 100644 index 000000000..4a8abc3eb --- /dev/null +++ b/vendor/splitbrain/php-cli/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Andreas Gohr + +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. diff --git a/vendor/splitbrain/php-cli/README.md b/vendor/splitbrain/php-cli/README.md new file mode 100644 index 000000000..3359be722 --- /dev/null +++ b/vendor/splitbrain/php-cli/README.md @@ -0,0 +1,158 @@ +# PHP-CLI + +PHP-CLI is a simple library that helps with creating nice looking command line scripts. + +It takes care of + +- **option parsing** +- **help page generation** +- **automatic width adjustment** +- **colored output** +- **optional PSR3 compatibility** + +It is lightweight and has **no 3rd party dependencies**. + +[![Build Status](https://travis-ci.org/splitbrain/php-cli.svg)](https://travis-ci.org/splitbrain/php-cli) + +## Installation + +Use composer: + +```php composer.phar require splitbrain/php-cli``` + +## Usage and Examples + +Minimal example: + +```php +#!/usr/bin/php +setHelp('A very minimal example that does nothing but print a version'); + $options->registerOption('version', 'print version', 'v'); + } + + // implement your code + protected function main(Options $options) + { + if ($options->getOpt('version')) { + $this->info('1.0.0'); + } else { + echo $options->help(); + } + } +} +// execute it +$cli = new Minimal(); +$cli->run(); +``` + +![Screenshot](screenshot.png) + + +The basic usage is simple: + +- create a class and ``extend splitbrain\phpcli\CLI`` +- implement the ```setup($options)``` method and register options, arguments, commands and set help texts + - ``$options->setHelp()`` adds a general description + - ``$options->registerOption()`` adds an option + - ``$options->registerArgument()`` adds an argument + - ``$options->registerComman()`` adds a sub command +- implement the ```main($options)``` method and do your business logic there + - ``$options->getOpts`` lets you access set options + - ``$options->getArgs()`` returns the remaining arguments after removing the options + - ``$options->getCmd()`` returns the sub command the user used +- instantiate your class and call ```run()``` on it + +More examples can be found in the examples directory. Please refer to the [API docs](https://splitbrain.github.io/php-cli/) +for further info. + +## Exceptions + +By default the CLI class registers an exception handler and will print the exception's message to the end user and +exit the programm with a non-zero exit code. You can disable this behaviour and catch all exceptions yourself by +passing false to the constructor. + +You can use the provided ``splitbrain\phpcli\Exception`` to signal any problems within your main code yourself. The +exceptions's code will be used as the exit code then. + +Stacktraces will be printed on log level `debug`. + +## Colored output + +Colored output is handled through the ``Colors`` class. It tries to detect if a color terminal is available and only +then uses terminal colors. You can always suppress colored output by passing ``--no-colors`` to your scripts. +Disabling colors will also disable the emoticon prefixes. + +Simple colored log messages can be printed by you using the convinence methods ``success()`` (green), ``info()`` (cyan), +``error()`` (red) or ``fatal()`` (red). The latter will also exit the programm with a non-zero exit code. + +For more complex coloring you can access the color class through ``$this->colors`` in your script. The ``wrap()`` method +is probably what you want to use. + +The table formatter allows coloring full columns. To use that mechanism pass an array of colors as third parameter to +its ``format()`` method. Please note that you can not pass colored texts in the second parameters (text length calculation +and wrapping will fail, breaking your texts). + +## Table Formatter + +The ``TableFormatter`` class allows you to align texts in multiple columns. It tries to figure out the available +terminal width on its own. It can be overwritten by setting a ``COLUMNS`` environment variable. + +The formatter is used through the ``format()`` method which expects at least two arrays: The first defines the column +widths, the second contains the texts to fill into the columns. Between each column a border is printed (a single space +by default). + +See the ``example/table.php`` for sample usage. + +Columns width can be given in three forms: + +- fixed width in characters by providing an integer (eg. ``15``) +- precentages by provifing an integer and a percent sign (eg. ``25%``) +- a single fluid "rest" column marked with an asterisk (eg. ``*``) + +When mixing fixed and percentage widths, percentages refer to the remaining space after all fixed columns have been +assigned. + +Space for borders is automatically calculated. It is recommended to always have some relative (percentage) or a fluid +column to adjust for different terminal widths. + +The table formatter is used for the automatic help screen accessible when calling your script with ``-h`` or ``--help``. + +## PSR-3 Logging + +The CLI class is a fully PSR-3 compatible logger (printing colored log data to STDOUT and STDERR). This is useful when +you call backend code from your CLI that expects a Logger instance to produce any sensible status output while running. + +To use this ability simply inherit from `splitbrain\phpcli\PSR3CLI` instead of `splitbrain\phpcli\CLI`, then pass `$this` +as the logger instance. Be sure you have the suggested `psr/log` composer package installed. + +![Screenshot](screenshot2.png) + +You can adjust the verbosity of your CLI tool using the `--loglevel` parameter. Supported loglevels are the PSR-3 +loglevels and our own `success` level: + +* debug +* info +* notice +* success +* warning +* error +* critical +* alert +* emergency + +Convenience methods for all log levels are available. Placeholder interpolation as described in PSR-3 is available, too. +Messages from `warning` level onwards are printed to `STDERR` all below are printed to `STDOUT`. + +The default log level of your script can be set by overwriting the `$logdefault` member. + +See `example/logging.php` for an example. \ No newline at end of file diff --git a/vendor/splitbrain/php-cli/composer.json b/vendor/splitbrain/php-cli/composer.json new file mode 100644 index 000000000..79e2502c4 --- /dev/null +++ b/vendor/splitbrain/php-cli/composer.json @@ -0,0 +1,34 @@ +{ + "name": "splitbrain/php-cli", + "description": "Easy command line scripts for PHP with opt parsing and color output. No dependencies", + "keywords": [ + "cli", + "console", + "terminal", + "command line", + "getopt", + "optparse", + "argparse" + ], + "license": "MIT", + "authors": [ + { + "name": "Andreas Gohr", + "email": "andi@splitbrain.org" + } + ], + "require": { + "php": ">=5.3.0" + }, + "suggest": { + "psr/log": "Allows you to make the CLI available as PSR-3 logger" + }, + "require-dev": { + "phpunit/phpunit": "4.5.*" + }, + "autoload": { + "psr-4": { + "splitbrain\\phpcli\\": "src" + } + } +} diff --git a/vendor/splitbrain/php-cli/src/CLI.php b/vendor/splitbrain/php-cli/src/CLI.php new file mode 100644 index 000000000..c48207af6 --- /dev/null +++ b/vendor/splitbrain/php-cli/src/CLI.php @@ -0,0 +1,312 @@ + + * @license MIT + */ +abstract class CLI +{ + /** @var string the executed script itself */ + protected $bin; + /** @var Options the option parser */ + protected $options; + /** @var Colors */ + public $colors; + + /** @var array PSR-3 compatible loglevels and their prefix, color, output channel */ + protected $loglevel = array( + 'debug' => array('', Colors::C_LIGHTGRAY, STDOUT), + 'info' => array('ℹ ', Colors::C_CYAN, STDOUT), + 'notice' => array('☛ ', Colors::C_CYAN, STDOUT), + 'success' => array('✓ ', Colors::C_GREEN, STDOUT), + 'warning' => array('⚠ ', Colors::C_BROWN, STDERR), + 'error' => array('✗ ', Colors::C_RED, STDERR), + 'critical' => array('☠ ', Colors::C_LIGHTRED, STDERR), + 'alert' => array('✖ ', Colors::C_LIGHTRED, STDERR), + 'emergency' => array('✘ ', Colors::C_LIGHTRED, STDERR), + ); + + protected $logdefault = 'info'; + + /** + * constructor + * + * Initialize the arguments, set up helper classes and set up the CLI environment + * + * @param bool $autocatch should exceptions be catched and handled automatically? + */ + public function __construct($autocatch = true) + { + if ($autocatch) { + set_exception_handler(array($this, 'fatal')); + } + + $this->colors = new Colors(); + $this->options = new Options($this->colors); + } + + /** + * Register options and arguments on the given $options object + * + * @param Options $options + * @return void + */ + abstract protected function setup(Options $options); + + /** + * Your main program + * + * Arguments and options have been parsed when this is run + * + * @param Options $options + * @return void + */ + abstract protected function main(Options $options); + + /** + * Execute the CLI program + * + * Executes the setup() routine, adds default options, initiate the options parsing and argument checking + * and finally executes main() + */ + public function run() + { + if ('cli' != php_sapi_name()) { + throw new Exception('This has to be run from the command line'); + } + + // setup + $this->setup($this->options); + $this->options->registerOption( + 'help', + 'Display this help screen and exit immeadiately.', + 'h' + ); + $this->options->registerOption( + 'no-colors', + 'Do not use any colors in output. Useful when piping output to other tools or files.' + ); + $this->options->registerOption( + 'loglevel', + 'Minimum level of messages to display. Default is ' . $this->colors->wrap($this->logdefault, Colors::C_CYAN) . '. ' . + 'Valid levels are: debug, info, notice, success, warning, error, critical, alert, emergency.', + null, + 'level' + ); + + // parse + $this->options->parseOptions(); + + // handle defaults + if ($this->options->getOpt('no-colors')) { + $this->colors->disable(); + } + if ($this->options->getOpt('help')) { + echo $this->options->help(); + exit(0); + } + $level = $this->options->getOpt('loglevel', $this->logdefault); + if (!isset($this->loglevel[$level])) $this->fatal('Unknown log level'); + foreach (array_keys($this->loglevel) as $l) { + if ($l == $level) break; + unset($this->loglevel[$l]); + } + + // check arguments + $this->options->checkArguments(); + + // execute + $this->main($this->options); + + exit(0); + } + + // region logging + + /** + * Exits the program on a fatal error + * + * @param \Exception|string $error either an exception or an error message + * @param array $context + */ + public function fatal($error, array $context = array()) + { + $code = 0; + if (is_object($error) && is_a($error, 'Exception')) { + /** @var Exception $error */ + $this->debug(get_class($error) . ' caught in ' . $error->getFile() . ':' . $error->getLine()); + $this->debug($error->getTraceAsString()); + $code = $error->getCode(); + $error = $error->getMessage(); + + } + if (!$code) { + $code = Exception::E_ANY; + } + + $this->critical($error, $context); + exit($code); + } + + /** + * System is unusable. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function emergency($message, array $context = array()) + { + $this->log('emergency', $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + */ + public function alert($message, array $context = array()) + { + $this->log('alert', $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + */ + public function critical($message, array $context = array()) + { + $this->log('critical', $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + */ + public function error($message, array $context = array()) + { + $this->log('error', $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + */ + public function warning($message, array $context = array()) + { + $this->log('warning', $message, $context); + } + + /** + * Normal, positive outcome + * + * @param string $string + * @param array $context + */ + public function success($string, array $context = array()) + { + $this->log('success', $string, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + */ + public function notice($message, array $context = array()) + { + $this->log('notice', $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + */ + public function info($message, array $context = array()) + { + $this->log('info', $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + */ + public function debug($message, array $context = array()) + { + $this->log('debug', $message, $context); + } + + /** + * @param string $level + * @param string $message + * @param array $context + */ + public function log($level, $message, array $context = array()) + { + // is this log level wanted? + if (!isset($this->loglevel[$level])) return; + + /** @var string $prefix */ + /** @var string $color */ + /** @var resource $channel */ + list($prefix, $color, $channel) = $this->loglevel[$level]; + if(!$this->colors->isEnabled()) $prefix = ''; + + $message = $this->interpolate($message, $context); + $this->colors->ptln($prefix . $message, $color, $channel); + } + + /** + * Interpolates context values into the message placeholders. + * + * @param $message + * @param array $context + * @return string + */ + function interpolate($message, array $context = array()) + { + // build a replacement array with braces around the context keys + $replace = array(); + foreach ($context as $key => $val) { + // check that the value can be casted to string + if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { + $replace['{' . $key . '}'] = $val; + } + } + + // interpolate replacement values into the message and return + return strtr($message, $replace); + } + + // endregion +} diff --git a/vendor/splitbrain/php-cli/src/Colors.php b/vendor/splitbrain/php-cli/src/Colors.php new file mode 100644 index 000000000..65cfe9521 --- /dev/null +++ b/vendor/splitbrain/php-cli/src/Colors.php @@ -0,0 +1,166 @@ + + * @license MIT + */ +class Colors +{ + // these constants make IDE autocompletion easier, but color names can also be passed as strings + const C_RESET = 'reset'; + const C_BLACK = 'black'; + const C_DARKGRAY = 'darkgray'; + const C_BLUE = 'blue'; + const C_LIGHTBLUE = 'lightblue'; + const C_GREEN = 'green'; + const C_LIGHTGREEN = 'lightgreen'; + const C_CYAN = 'cyan'; + const C_LIGHTCYAN = 'lightcyan'; + const C_RED = 'red'; + const C_LIGHTRED = 'lightred'; + const C_PURPLE = 'purple'; + const C_LIGHTPURPLE = 'lightpurple'; + const C_BROWN = 'brown'; + const C_YELLOW = 'yellow'; + const C_LIGHTGRAY = 'lightgray'; + const C_WHITE = 'white'; + + /** @var array known color names */ + protected $colors = array( + self::C_RESET => "\33[0m", + self::C_BLACK => "\33[0;30m", + self::C_DARKGRAY => "\33[1;30m", + self::C_BLUE => "\33[0;34m", + self::C_LIGHTBLUE => "\33[1;34m", + self::C_GREEN => "\33[0;32m", + self::C_LIGHTGREEN => "\33[1;32m", + self::C_CYAN => "\33[0;36m", + self::C_LIGHTCYAN => "\33[1;36m", + self::C_RED => "\33[0;31m", + self::C_LIGHTRED => "\33[1;31m", + self::C_PURPLE => "\33[0;35m", + self::C_LIGHTPURPLE => "\33[1;35m", + self::C_BROWN => "\33[0;33m", + self::C_YELLOW => "\33[1;33m", + self::C_LIGHTGRAY => "\33[0;37m", + self::C_WHITE => "\33[1;37m", + ); + + /** @var bool should colors be used? */ + protected $enabled = true; + + /** + * Constructor + * + * Tries to disable colors for non-terminals + */ + public function __construct() + { + if (function_exists('posix_isatty') && !posix_isatty(STDOUT)) { + $this->enabled = false; + return; + } + if (!getenv('TERM')) { + $this->enabled = false; + return; + } + } + + /** + * enable color output + */ + public function enable() + { + $this->enabled = true; + } + + /** + * disable color output + */ + public function disable() + { + $this->enabled = false; + } + + /** + * @return bool is color support enabled? + */ + public function isEnabled() + { + return $this->enabled; + } + + /** + * Convenience function to print a line in a given color + * + * @param string $line the line to print, a new line is added automatically + * @param string $color one of the available color names + * @param resource $channel file descriptor to write to + */ + public function ptln($line, $color, $channel = STDOUT) + { + $this->set($color); + fwrite($channel, rtrim($line) . "\n"); + $this->reset(); + } + + /** + * Returns the given text wrapped in the appropriate color and reset code + * + * @param string $text string to wrap + * @param string $color one of the available color names + * @return string the wrapped string + * @throws Exception + */ + public function wrap($text, $color) + { + return $this->getColorCode($color) . $text . $this->getColorCode('reset'); + } + + /** + * Gets the appropriate terminal code for the given color + * + * @param string $color one of the available color names + * @return string color code + * @throws Exception + */ + public function getColorCode($color) + { + if (!$this->enabled) { + return ''; + } + if (!isset($this->colors[$color])) { + throw new Exception("No such color $color"); + } + + return $this->colors[$color]; + } + + /** + * Set the given color for consecutive output + * + * @param string $color one of the supported color names + * @param resource $channel file descriptor to write to + * @throws Exception + */ + public function set($color, $channel = STDOUT) + { + fwrite($channel, $this->getColorCode($color)); + } + + /** + * reset the terminal color + * + * @param resource $channel file descriptor to write to + */ + public function reset($channel = STDOUT) + { + $this->set('reset', $channel); + } +} diff --git a/vendor/splitbrain/php-cli/src/Exception.php b/vendor/splitbrain/php-cli/src/Exception.php new file mode 100644 index 000000000..b2aa98115 --- /dev/null +++ b/vendor/splitbrain/php-cli/src/Exception.php @@ -0,0 +1,35 @@ + + * @license MIT + */ +class Exception extends \Exception +{ + const E_ANY = -1; // no error code specified + const E_UNKNOWN_OPT = 1; //Unrecognized option + const E_OPT_ARG_REQUIRED = 2; //Option requires argument + const E_OPT_ARG_DENIED = 3; //Option not allowed argument + const E_OPT_ABIGUOUS = 4; //Option abiguous + const E_ARG_READ = 5; //Could not read argv + + /** + * @param string $message The Exception message to throw. + * @param int $code The Exception code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = "", $code = 0, \Exception $previous = null) + { + if (!$code) { + $code = self::E_ANY; + } + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/splitbrain/php-cli/src/Options.php b/vendor/splitbrain/php-cli/src/Options.php new file mode 100644 index 000000000..919dd9310 --- /dev/null +++ b/vendor/splitbrain/php-cli/src/Options.php @@ -0,0 +1,468 @@ + + * @license MIT + */ +class Options +{ + /** @var array keeps the list of options to parse */ + protected $setup; + + /** @var array store parsed options */ + protected $options = array(); + + /** @var string current parsed command if any */ + protected $command = ''; + + /** @var array passed non-option arguments */ + protected $args = array(); + + /** @var string the executed script */ + protected $bin; + + /** @var Colors for colored help output */ + protected $colors; + + /** + * Constructor + * + * @param Colors $colors optional configured color object + * @throws Exception when arguments can't be read + */ + public function __construct(Colors $colors = null) + { + if (!is_null($colors)) { + $this->colors = $colors; + } else { + $this->colors = new Colors(); + } + + $this->setup = array( + '' => array( + 'opts' => array(), + 'args' => array(), + 'help' => '' + ) + ); // default command + + $this->args = $this->readPHPArgv(); + $this->bin = basename(array_shift($this->args)); + + $this->options = array(); + } + + /** + * Sets the help text for the tool itself + * + * @param string $help + */ + public function setHelp($help) + { + $this->setup['']['help'] = $help; + } + + /** + * Register the names of arguments for help generation and number checking + * + * This has to be called in the order arguments are expected + * + * @param string $arg argument name (just for help) + * @param string $help help text + * @param bool $required is this a required argument + * @param string $command if theses apply to a sub command only + * @throws Exception + */ + public function registerArgument($arg, $help, $required = true, $command = '') + { + if (!isset($this->setup[$command])) { + throw new Exception("Command $command not registered"); + } + + $this->setup[$command]['args'][] = array( + 'name' => $arg, + 'help' => $help, + 'required' => $required + ); + } + + /** + * This registers a sub command + * + * Sub commands have their own options and use their own function (not main()). + * + * @param string $command + * @param string $help + * @throws Exception + */ + public function registerCommand($command, $help) + { + if (isset($this->setup[$command])) { + throw new Exception("Command $command already registered"); + } + + $this->setup[$command] = array( + 'opts' => array(), + 'args' => array(), + 'help' => $help + ); + + } + + /** + * Register an option for option parsing and help generation + * + * @param string $long multi character option (specified with --) + * @param string $help help text for this option + * @param string|null $short one character option (specified with -) + * @param bool|string $needsarg does this option require an argument? give it a name here + * @param string $command what command does this option apply to + * @throws Exception + */ + public function registerOption($long, $help, $short = null, $needsarg = false, $command = '') + { + if (!isset($this->setup[$command])) { + throw new Exception("Command $command not registered"); + } + + $this->setup[$command]['opts'][$long] = array( + 'needsarg' => $needsarg, + 'help' => $help, + 'short' => $short + ); + + if ($short) { + if (strlen($short) > 1) { + throw new Exception("Short options should be exactly one ASCII character"); + } + + $this->setup[$command]['short'][$short] = $long; + } + } + + /** + * Checks the actual number of arguments against the required number + * + * Throws an exception if arguments are missing. + * + * This is run from CLI automatically and usually does not need to be called directly + * + * @throws Exception + */ + public function checkArguments() + { + $argc = count($this->args); + + $req = 0; + foreach ($this->setup[$this->command]['args'] as $arg) { + if (!$arg['required']) { + break; + } // last required arguments seen + $req++; + } + + if ($req > $argc) { + throw new Exception("Not enough arguments", Exception::E_OPT_ARG_REQUIRED); + } + } + + /** + * Parses the given arguments for known options and command + * + * The given $args array should NOT contain the executed file as first item anymore! The $args + * array is stripped from any options and possible command. All found otions can be accessed via the + * getOpt() function + * + * Note that command options will overwrite any global options with the same name + * + * This is run from CLI automatically and usually does not need to be called directly + * + * @throws Exception + */ + public function parseOptions() + { + $non_opts = array(); + + $argc = count($this->args); + for ($i = 0; $i < $argc; $i++) { + $arg = $this->args[$i]; + + // The special element '--' means explicit end of options. Treat the rest of the arguments as non-options + // and end the loop. + if ($arg == '--') { + $non_opts = array_merge($non_opts, array_slice($this->args, $i + 1)); + break; + } + + // '-' is stdin - a normal argument + if ($arg == '-') { + $non_opts = array_merge($non_opts, array_slice($this->args, $i)); + break; + } + + // first non-option + if ($arg{0} != '-') { + $non_opts = array_merge($non_opts, array_slice($this->args, $i)); + break; + } + + // long option + if (strlen($arg) > 1 && $arg{1} == '-') { + $arg = explode('=', substr($arg, 2), 2); + $opt = array_shift($arg); + $val = array_shift($arg); + + if (!isset($this->setup[$this->command]['opts'][$opt])) { + throw new Exception("No such option '$opt'", Exception::E_UNKNOWN_OPT); + } + + // argument required? + if ($this->setup[$this->command]['opts'][$opt]['needsarg']) { + if (is_null($val) && $i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) { + $val = $this->args[++$i]; + } + if (is_null($val)) { + throw new Exception("Option $opt requires an argument", + Exception::E_OPT_ARG_REQUIRED); + } + $this->options[$opt] = $val; + } else { + $this->options[$opt] = true; + } + + continue; + } + + // short option + $opt = substr($arg, 1); + if (!isset($this->setup[$this->command]['short'][$opt])) { + throw new Exception("No such option $arg", Exception::E_UNKNOWN_OPT); + } else { + $opt = $this->setup[$this->command]['short'][$opt]; // store it under long name + } + + // argument required? + if ($this->setup[$this->command]['opts'][$opt]['needsarg']) { + $val = null; + if ($i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) { + $val = $this->args[++$i]; + } + if (is_null($val)) { + throw new Exception("Option $arg requires an argument", + Exception::E_OPT_ARG_REQUIRED); + } + $this->options[$opt] = $val; + } else { + $this->options[$opt] = true; + } + } + + // parsing is now done, update args array + $this->args = $non_opts; + + // if not done yet, check if first argument is a command and reexecute argument parsing if it is + if (!$this->command && $this->args && isset($this->setup[$this->args[0]])) { + // it is a command! + $this->command = array_shift($this->args); + $this->parseOptions(); // second pass + } + } + + /** + * Get the value of the given option + * + * Please note that all options are accessed by their long option names regardless of how they were + * specified on commandline. + * + * Can only be used after parseOptions() has been run + * + * @param mixed $option + * @param bool|string $default what to return if the option was not set + * @return bool|string + */ + public function getOpt($option = null, $default = false) + { + if ($option === null) { + return $this->options; + } + + if (isset($this->options[$option])) { + return $this->options[$option]; + } + return $default; + } + + /** + * Return the found command if any + * + * @return string + */ + public function getCmd() + { + return $this->command; + } + + /** + * Get all the arguments passed to the script + * + * This will not contain any recognized options or the script name itself + * + * @return array + */ + public function getArgs() + { + return $this->args; + } + + /** + * Builds a help screen from the available options. You may want to call it from -h or on error + * + * @return string + */ + public function help() + { + $tf = new TableFormatter($this->colors); + $text = ''; + + $hascommands = (count($this->setup) > 1); + foreach ($this->setup as $command => $config) { + $hasopts = (bool)$this->setup[$command]['opts']; + $hasargs = (bool)$this->setup[$command]['args']; + + // usage or command syntax line + if (!$command) { + $text .= $this->colors->wrap('USAGE:', Colors::C_BROWN); + $text .= "\n"; + $text .= ' ' . $this->bin; + $mv = 2; + } else { + $text .= "\n"; + $text .= $this->colors->wrap(' ' . $command, Colors::C_PURPLE); + $mv = 4; + } + + if ($hasopts) { + $text .= ' ' . $this->colors->wrap('', Colors::C_GREEN); + } + + if (!$command && $hascommands) { + $text .= ' ' . $this->colors->wrap(' ...', Colors::C_PURPLE); + } + + foreach ($this->setup[$command]['args'] as $arg) { + $out = $this->colors->wrap('<' . $arg['name'] . '>', Colors::C_CYAN); + + if (!$arg['required']) { + $out = '[' . $out . ']'; + } + $text .= ' ' . $out; + } + $text .= "\n"; + + // usage or command intro + if ($this->setup[$command]['help']) { + $text .= "\n"; + $text .= $tf->format( + array($mv, '*'), + array('', $this->setup[$command]['help'] . "\n") + ); + } + + // option description + if ($hasopts) { + if (!$command) { + $text .= "\n"; + $text .= $this->colors->wrap('OPTIONS:', Colors::C_BROWN); + } + $text .= "\n"; + foreach ($this->setup[$command]['opts'] as $long => $opt) { + + $name = ''; + if ($opt['short']) { + $name .= '-' . $opt['short']; + if ($opt['needsarg']) { + $name .= ' <' . $opt['needsarg'] . '>'; + } + $name .= ', '; + } + $name .= "--$long"; + if ($opt['needsarg']) { + $name .= ' <' . $opt['needsarg'] . '>'; + } + + $text .= $tf->format( + array($mv, '30%', '*'), + array('', $name, $opt['help']), + array('', 'green', '') + ); + $text .= "\n"; + } + } + + // argument description + if ($hasargs) { + if (!$command) { + $text .= "\n"; + $text .= $this->colors->wrap('ARGUMENTS:', Colors::C_BROWN); + } + $text .= "\n"; + foreach ($this->setup[$command]['args'] as $arg) { + $name = '<' . $arg['name'] . '>'; + + $text .= $tf->format( + array($mv, '30%', '*'), + array('', $name, $arg['help']), + array('', 'cyan', '') + ); + } + } + + // head line and intro for following command documentation + if (!$command && $hascommands) { + $text .= "\n"; + $text .= $this->colors->wrap('COMMANDS:', Colors::C_BROWN); + $text .= "\n"; + $text .= $tf->format( + array($mv, '*'), + array('', 'This tool accepts a command as first parameter as outlined below:') + ); + $text .= "\n"; + } + } + + return $text; + } + + /** + * Safely read the $argv PHP array across different PHP configurations. + * Will take care on register_globals and register_argc_argv ini directives + * + * @throws Exception + * @return array the $argv PHP array or PEAR error if not registered + */ + private function readPHPArgv() + { + global $argv; + if (!is_array($argv)) { + if (!@is_array($_SERVER['argv'])) { + if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { + throw new Exception( + "Could not read cmd args (register_argc_argv=Off?)", + Exception::E_ARG_READ + ); + } + return $GLOBALS['HTTP_SERVER_VARS']['argv']; + } + return $_SERVER['argv']; + } + return $argv; + } +} + diff --git a/vendor/splitbrain/php-cli/src/PSR3CLI.php b/vendor/splitbrain/php-cli/src/PSR3CLI.php new file mode 100644 index 000000000..ef744f342 --- /dev/null +++ b/vendor/splitbrain/php-cli/src/PSR3CLI.php @@ -0,0 +1,13 @@ + + * @license MIT + */ +class TableFormatter +{ + /** @var string border between columns */ + protected $border = ' '; + + /** @var int the terminal width */ + protected $max = 74; + + /** @var Colors for coloring output */ + protected $colors; + + /** + * TableFormatter constructor. + * + * @param Colors|null $colors + */ + public function __construct(Colors $colors = null) + { + // try to get terminal width + $width = 0; + if (isset($_SERVER['COLUMNS'])) { + // from environment + $width = (int)$_SERVER['COLUMNS']; + } + if (!$width) { + // via tput command + $width = @exec('tput cols'); + } + if ($width) { + $this->max = $width; + } + + if ($colors) { + $this->colors = $colors; + } else { + $this->colors = new Colors(); + } + } + + /** + * The currently set border (defaults to ' ') + * + * @return string + */ + public function getBorder() + { + return $this->border; + } + + /** + * Set the border. The border is set between each column. Its width is + * added to the column widths. + * + * @param string $border + */ + public function setBorder($border) + { + $this->border = $border; + } + + /** + * Width of the terminal in characters + * + * initially autodetected + * + * @return int + */ + public function getMaxWidth() + { + return $this->max; + } + + /** + * Set the width of the terminal to assume (in characters) + * + * @param int $max + */ + public function setMaxWidth($max) + { + $this->max = $max; + } + + /** + * Takes an array with dynamic column width and calculates the correct width + * + * Column width can be given as fixed char widths, percentages and a single * width can be given + * for taking the remaining available space. When mixing percentages and fixed widths, percentages + * refer to the remaining space after allocating the fixed width + * + * @param array $columns + * @return int[] + * @throws Exception + */ + protected function calculateColLengths($columns) + { + $idx = 0; + $border = $this->strlen($this->border); + $fixed = (count($columns) - 1) * $border; // borders are used already + $fluid = -1; + + // first pass for format check and fixed columns + foreach ($columns as $idx => $col) { + // handle fixed columns + if ((string)intval($col) === (string)$col) { + $fixed += $col; + continue; + } + // check if other colums are using proper units + if (substr($col, -1) == '%') { + continue; + } + if ($col == '*') { + // only one fluid + if ($fluid < 0) { + $fluid = $idx; + continue; + } else { + throw new Exception('Only one fluid column allowed!'); + } + } + throw new Exception("unknown column format $col"); + } + + $alloc = $fixed; + $remain = $this->max - $alloc; + + // second pass to handle percentages + foreach ($columns as $idx => $col) { + if (substr($col, -1) != '%') { + continue; + } + $perc = floatval($col); + + $real = (int)floor(($perc * $remain) / 100); + + $columns[$idx] = $real; + $alloc += $real; + } + + $remain = $this->max - $alloc; + if ($remain < 0) { + throw new Exception("Wanted column widths exceed available space"); + } + + // assign remaining space + if ($fluid < 0) { + $columns[$idx] += ($remain); // add to last column + } else { + $columns[$fluid] = $remain; + } + + return $columns; + } + + /** + * Displays text in multiple word wrapped columns + * + * @param int[] $columns list of column widths (in characters, percent or '*') + * @param string[] $texts list of texts for each column + * @param array $colors A list of color names to use for each column. use empty string for default + * @return string + * @throws Exception + */ + public function format($columns, $texts, $colors = array()) + { + $columns = $this->calculateColLengths($columns); + + $wrapped = array(); + $maxlen = 0; + + foreach ($columns as $col => $width) { + $wrapped[$col] = explode("\n", $this->wordwrap($texts[$col], $width, "\n", true)); + $len = count($wrapped[$col]); + if ($len > $maxlen) { + $maxlen = $len; + } + + } + + $last = count($columns) - 1; + $out = ''; + for ($i = 0; $i < $maxlen; $i++) { + foreach ($columns as $col => $width) { + if (isset($wrapped[$col][$i])) { + $val = $wrapped[$col][$i]; + } else { + $val = ''; + } + $chunk = $this->pad($val, $width); + if (isset($colors[$col]) && $colors[$col]) { + $chunk = $this->colors->wrap($chunk, $colors[$col]); + } + $out .= $chunk; + + // border + if ($col != $last) { + $out .= $this->border; + } + } + $out .= "\n"; + } + return $out; + + } + + /** + * Pad the given string to the correct length + * + * @param string $string + * @param int $len + * @return string + */ + protected function pad($string, $len) + { + $strlen = $this->strlen($string); + if ($strlen > $len) return $string; + + $pad = $len - $strlen; + return $string . str_pad('', $pad, ' '); + } + + /** + * Measures char length in UTF-8 when possible + * + * @param $string + * @return int + */ + protected function strlen($string) + { + // don't count color codes + $string = preg_replace("/\33\\[\\d+(;\\d+)?m/", '', $string); + + if (function_exists('mb_strlen')) { + return mb_strlen($string, 'utf-8'); + } + + return strlen($string); + } + + /** + * @param string $string + * @param int $start + * @param int|null $length + * @return string + */ + protected function substr($string, $start = 0, $length = null) + { + if (function_exists('mb_substr')) { + return mb_substr($string, $start, $length); + } else { + return substr($string, $start, $length); + } + } + + /** + * @param string $str + * @param int $width + * @param string $break + * @param bool $cut + * @return string + * @link http://stackoverflow.com/a/4988494 + */ + protected function wordwrap($str, $width = 75, $break = "\n", $cut = false) + { + $lines = explode($break, $str); + foreach ($lines as &$line) { + $line = rtrim($line); + if ($this->strlen($line) <= $width) { + continue; + } + $words = explode(' ', $line); + $line = ''; + $actual = ''; + foreach ($words as $word) { + if ($this->strlen($actual . $word) <= $width) { + $actual .= $word . ' '; + } else { + if ($actual != '') { + $line .= rtrim($actual) . $break; + } + $actual = $word; + if ($cut) { + while ($this->strlen($actual) > $width) { + $line .= $this->substr($actual, 0, $width) . $break; + $actual = $this->substr($actual, $width); + } + } + $actual .= ' '; + } + } + $line .= trim($actual); + } + return implode($break, $lines); + } +} \ No newline at end of file