replace Doku_CLI with splitbrain\phpcli\CLI

It has few more features (like turning down verbosity) and looks nicer
This commit is contained in:
Andreas Gohr 2017-11-10 14:18:18 +01:00
parent 0f8cc87bd3
commit cbeaa4a047
23 changed files with 1782 additions and 116 deletions

4
.gitignore vendored
View File

@ -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

View File

@ -1,13 +1,17 @@
#!/usr/bin/php
<?php
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__).'/../').'/');
use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../') . '/');
define('NOSESSION', 1);
require_once(DOKU_INC.'inc/init.php');
require_once(DOKU_INC . 'inc/init.php');
/**
* Checkout and commit pages from the command line while maintaining the history
*/
class PageCLI extends DokuCLI {
class PageCLI extends CLI {
protected $force = false;
protected $username = '';
@ -15,10 +19,10 @@ class PageCLI extends DokuCLI {
/**
* 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) {
/* global */
$options->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();
$cli->run();

View File

@ -1,27 +1,31 @@
#!/usr/bin/php
<?php
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__).'/../').'/');
use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../') . '/');
define('NOSESSION', 1);
require_once(DOKU_INC.'inc/init.php');
require_once(DOKU_INC . 'inc/init.php');
/**
* Easily manage DokuWiki git repositories
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
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();
$cli->run();

View File

@ -1,13 +1,17 @@
#!/usr/bin/php
<?php
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__).'/../').'/');
use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../') . '/');
define('NOSESSION', 1);
require_once(DOKU_INC.'inc/init.php');
require_once(DOKU_INC . 'inc/init.php');
/**
* Update the Search Index from command line
*/
class IndexerCLI extends DokuCLI {
class IndexerCLI extends CLI {
private $quiet = false;
private $clear = false;
@ -15,12 +19,12 @@ class IndexerCLI extends DokuCLI {
/**
* 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(
'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();
$cli->run();

View File

@ -1,9 +1,12 @@
#!/usr/bin/php
<?php
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__).'/../').'/');
define('NOSESSION', 1);
require_once(DOKU_INC.'inc/init.php');
use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../') . '/');
define('NOSESSION', 1);
require_once(DOKU_INC . 'inc/init.php');
/**
* A simple commandline tool to render some DokuWiki syntax with a given
@ -16,20 +19,20 @@ require_once(DOKU_INC.'inc/init.php');
* @license GPL2
* @author Andreas Gohr <andi@splitbrain.org>
*/
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();
$cli->run();

View File

@ -1,25 +1,28 @@
#!/usr/bin/php
<?php
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__).'/../').'/');
define('NOSESSION', 1);
require_once(DOKU_INC.'inc/init.php');
use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../') . '/');
define('NOSESSION', 1);
require_once(DOKU_INC . 'inc/init.php');
/**
* Remove unwanted languages from a DokuWiki install
*/
class StripLangsCLI extends DokuCLI {
class StripLangsCLI 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(
'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();
$cli->run();

View File

@ -1,5 +1,9 @@
#!/usr/bin/php
<?php
use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../') . '/');
define('NOSESSION', 1);
require_once(DOKU_INC . 'inc/init.php');
@ -7,7 +11,7 @@ require_once(DOKU_INC . 'inc/init.php');
/**
* Find wanted pages
*/
class WantedPagesCLI extends DokuCLI {
class WantedPagesCLI extends CLI {
const DIR_CONTINUE = 1;
const DIR_NS = 2;
@ -21,10 +25,10 @@ class WantedPagesCLI extends DokuCLI {
/**
* 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(
'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'));
}

View File

@ -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": {

58
composer.lock generated
View File

@ -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"
}
}

View File

@ -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.');
}
/**

View File

@ -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'),
);

View File

@ -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',

View File

@ -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"
]
}
]

8
vendor/splitbrain/php-cli/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
*.iml
.idea/
composer.phar
vendor/
composer.lock
apigen.phar
docs/

21
vendor/splitbrain/php-cli/LICENSE vendored Normal file
View File

@ -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.

158
vendor/splitbrain/php-cli/README.md vendored Normal file
View File

@ -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
<?php
require __DIR__ . '/../vendor/autoload.php';
use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;
class Minimal extends CLI
{
// register options and arguments
protected function setup(Options $options)
{
$options->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.

34
vendor/splitbrain/php-cli/composer.json vendored Normal file
View File

@ -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"
}
}
}

312
vendor/splitbrain/php-cli/src/CLI.php vendored Normal file
View File

@ -0,0 +1,312 @@
<?php
namespace splitbrain\phpcli;
/**
* Class CLI
*
* Your commandline script should inherit from this class and implement the abstract methods.
*
* @author Andreas Gohr <andi@splitbrain.org>
* @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
}

166
vendor/splitbrain/php-cli/src/Colors.php vendored Normal file
View File

@ -0,0 +1,166 @@
<?php
namespace splitbrain\phpcli;
/**
* Class Colors
*
* Handles color output on (Linux) terminals
*
* @author Andreas Gohr <andi@splitbrain.org>
* @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);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace splitbrain\phpcli;
/**
* Class Exception
*
* The code is used as exit code for the CLI tool. This should probably be extended. Many cases just fall back to the
* E_ANY code.
*
* @author Andreas Gohr <andi@splitbrain.org>
* @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);
}
}

View File

@ -0,0 +1,468 @@
<?php
namespace splitbrain\phpcli;
/**
* Class Options
*
* Parses command line options passed to the CLI script. Allows CLI scripts to easily register all accepted options and
* commands and even generates a help text from this setup.
*
* @author Andreas Gohr <andi@splitbrain.org>
* @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('<OPTIONS>', Colors::C_GREEN);
}
if (!$command && $hascommands) {
$text .= ' ' . $this->colors->wrap('<COMMAND> ...', 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;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace splitbrain\phpcli;
use Psr\Log\LoggerInterface;
/**
* Class PSR3CLI
*
* The same as CLI, but implements the PSR-3 logger interface
*/
abstract class PSR3CLI extends CLI implements LoggerInterface {
}

View File

@ -0,0 +1,307 @@
<?php
namespace splitbrain\phpcli;
/**
* Class TableFormatter
*
* Output text in multiple columns
*
* @author Andreas Gohr <andi@splitbrain.org>
* @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);
}
}