基础代码
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
.idea
|
||||
vendor
|
||||
.*.swp
|
||||
composer.lock
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
sudo: false
|
||||
dist: trusty
|
||||
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- dist: precise
|
||||
php: 5.3
|
||||
|
||||
before_script:
|
||||
- php -m
|
||||
- php --info | grep -i 'intl\|icu\|pcre'
|
||||
|
||||
script: phpunit --debug
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2010 James Logsdon
|
||||
|
||||
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.
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
PHP Command Line Tools
|
||||
======================
|
||||
|
||||
[](https://travis-ci.org/wp-cli/php-cli-tools)
|
||||
|
||||
A collection of functions and classes to assist with command line development.
|
||||
|
||||
Requirements
|
||||
|
||||
* PHP >= 5.3
|
||||
|
||||
Suggested PHP extensions
|
||||
|
||||
* mbstring - Used for calculating string widths.
|
||||
|
||||
Function List
|
||||
-------------
|
||||
|
||||
* `cli\out($msg, ...)`
|
||||
* `cli\out_padded($msg, ...)`
|
||||
* `cli\err($msg, ...)`
|
||||
* `cli\line($msg = '', ...)`
|
||||
* `cli\input()`
|
||||
* `cli\prompt($question, $default = false, $marker = ':')`
|
||||
* `cli\choose($question, $choices = 'yn', $default = 'n')`
|
||||
* `cli\menu($items, $default = false, $title = 'Choose an Item')`
|
||||
|
||||
Progress Indicators
|
||||
-------------------
|
||||
|
||||
* `cli\notify\Dots($msg, $dots = 3, $interval = 100)`
|
||||
* `cli\notify\Spinner($msg, $interval = 100)`
|
||||
* `cli\progress\Bar($msg, $total, $interval = 100)`
|
||||
|
||||
Tabular Display
|
||||
---------------
|
||||
|
||||
* `cli\Table::__construct(array $headers = null, array $rows = null)`
|
||||
* `cli\Table::setHeaders(array $headers)`
|
||||
* `cli\Table::setRows(array $rows)`
|
||||
* `cli\Table::setRenderer(cli\table\Renderer $renderer)`
|
||||
* `cli\Table::addRow(array $row)`
|
||||
* `cli\Table::sort($column)`
|
||||
* `cli\Table::display()`
|
||||
|
||||
The display function will detect if output is piped and, if it is, render a tab delimited table instead of the ASCII
|
||||
table rendered for visual display.
|
||||
|
||||
You can also explicitly set the renderer used by calling `cli\Table::setRenderer()` and giving it an instance of one
|
||||
of the concrete `cli\table\Renderer` classes.
|
||||
|
||||
Tree Display
|
||||
------------
|
||||
|
||||
* `cli\Tree::__construct()`
|
||||
* `cli\Tree::setData(array $data)`
|
||||
* `cli\Tree::setRenderer(cli\tree\Renderer $renderer)`
|
||||
* `cli\Tree::render()`
|
||||
* `cli\Tree::display()`
|
||||
|
||||
Argument Parser
|
||||
---------------
|
||||
|
||||
Argument parsing uses a simple framework for taking a list of command line arguments,
|
||||
usually straight from `$_SERVER['argv']`, and parses the input against a set of
|
||||
defined rules.
|
||||
|
||||
Check `examples/arguments.php` for an example.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
See `examples/` directory for examples.
|
||||
|
||||
|
||||
Todo
|
||||
----
|
||||
|
||||
* Expand this README
|
||||
* Add doc blocks to rest of code
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "wp-cli/php-cli-tools",
|
||||
"type": "library",
|
||||
"description": "Console utilities for PHP",
|
||||
"keywords": ["console", "cli"],
|
||||
"homepage": "http://github.com/wp-cli/php-cli-tools",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniel Bachhuber",
|
||||
"email": "daniel@handbuilt.co",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "James Logsdon",
|
||||
"email": "jlogsdon@php.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">= 5.3.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"cli": "lib/"
|
||||
},
|
||||
"files": [
|
||||
"lib/cli/cli.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Sample invocations:
|
||||
*
|
||||
* # php example_args.php -vC ./ --version
|
||||
* {"verbose":true,"cache":".\/","version":true}
|
||||
* # php example_args.php -vC --version
|
||||
* PHP Warning: [cli\Arguments] no value given for -C
|
||||
* # php example_args.php -vC multi word --version
|
||||
* {"verbose":true,"cache":"multi word","version":true}
|
||||
*
|
||||
*/
|
||||
|
||||
require 'common.php';
|
||||
|
||||
$strict = in_array('--strict', $_SERVER['argv']);
|
||||
$arguments = new \cli\Arguments(compact('strict'));
|
||||
|
||||
$arguments->addFlag(array('verbose', 'v'), 'Turn on verbose output');
|
||||
$arguments->addFlag('version', 'Display the version');
|
||||
$arguments->addFlag(array('quiet', 'q'), 'Disable all output');
|
||||
$arguments->addFlag(array('help', 'h'), 'Show this help screen');
|
||||
|
||||
$arguments->addOption(array('cache', 'C'), array(
|
||||
'default' => getcwd(),
|
||||
'description' => 'Set the cache directory'));
|
||||
$arguments->addOption(array('name', 'n'), array(
|
||||
'default' => 'James',
|
||||
'description' => 'Set a name with a really long description and a default so we can see what line wrapping looks like which is probably a goo idea'));
|
||||
|
||||
$arguments->parse();
|
||||
if ($arguments['help']) {
|
||||
echo $arguments->getHelpScreen();
|
||||
echo "\n\n";
|
||||
}
|
||||
|
||||
echo $arguments->asJSON() . "\n";
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// Samples. Lines marked with * should be colored in output
|
||||
// php examples/colors.php
|
||||
// * All output is run through \cli\Colors::colorize before display
|
||||
// * All output is run through \cli\Colors::colorize before display
|
||||
// * All output is run through \cli\Colors::colorize before display
|
||||
// * All output is run through \cli\Colors::colorize before display
|
||||
// All output is run through \cli\Colors::colorize before display
|
||||
// * All output is run through \cli\Colors::colorize before display
|
||||
// php examples/colors.php | cat
|
||||
// All output is run through \cli\Colors::colorize before display
|
||||
// * All output is run through \cli\Colors::colorize before display
|
||||
// All output is run through \cli\Colors::colorize before display
|
||||
// * All output is run through \cli\Colors::colorize before display
|
||||
// All output is run through \cli\Colors::colorize before display
|
||||
// All output is run through \cli\Colors::colorize before display
|
||||
|
||||
require_once 'common.php';
|
||||
|
||||
\cli\line(' %C%5All output is run through %Y%6\cli\Colors::colorize%C%5 before display%n');
|
||||
|
||||
echo \cli\Colors::colorize(' %C%5All output is run through %Y%6\cli\Colors::colorize%C%5 before display%n', true) . "\n";
|
||||
echo \cli\Colors::colorize(' %C%5All output is run through %Y%6\cli\Colors::colorize%C%5 before display%n') . "\n";
|
||||
|
||||
\cli\Colors::enable(); // Forcefully enable
|
||||
\cli\line(' %C%5All output is run through %Y%6\cli\Colors::colorize%C%5 before display%n');
|
||||
|
||||
\cli\Colors::disable(); // Disable forcefully!
|
||||
\cli\line(' %C%5All output is run through %Y%6\cli\Colors::colorize%C%5 before display%n', true);
|
||||
\cli\Colors::enable(false); // Enable, but not forcefully
|
||||
\cli\line(' %C%5All output is run through %Y%6\cli\Colors::colorize%C%5 before display%n');
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
if (php_sapi_name() != 'cli') {
|
||||
die('Must run from command line');
|
||||
}
|
||||
|
||||
error_reporting(E_ALL | E_STRICT);
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('log_errors', 0);
|
||||
ini_set('html_errors', 0);
|
||||
|
||||
foreach(array(__DIR__ . '/../vendor', __DIR__ . '/../../../../vendor') as $vendorDir) {
|
||||
if(is_dir($vendorDir)) {
|
||||
require_once $vendorDir . '/autoload.php';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function test_notify(cli\Notify $notify, $cycle = 1000000, $sleep = null) {
|
||||
for ($i = 0; $i < $cycle; $i++) {
|
||||
$notify->tick();
|
||||
if ($sleep) usleep($sleep);
|
||||
}
|
||||
$notify->finish();
|
||||
}
|
||||
|
||||
function test_notify_msg(cli\Notify $notify, $cycle = 1000000, $sleep = null) {
|
||||
$notify->display();
|
||||
for ($i = 0; $i < $cycle; $i++) {
|
||||
// Sleep before tick to simulate time-intensive work and give time
|
||||
// for the initial message to display before it is changed
|
||||
if ($sleep) usleep($sleep);
|
||||
$msg = sprintf(' Finished step %d', $i + 1);
|
||||
$notify->tick(1, $msg);
|
||||
}
|
||||
$notify->finish();
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
require_once 'common.php';
|
||||
|
||||
$menu = array(
|
||||
'output' => 'Output Examples',
|
||||
'notify' => 'cli\Notify Examples',
|
||||
'progress' => 'cli\Progress Examples',
|
||||
'table' => 'cli\Table Example',
|
||||
'colors' => 'cli\Colors example',
|
||||
'quit' => 'Quit',
|
||||
);
|
||||
|
||||
while (true) {
|
||||
$choice = \cli\menu($menu, null, 'Choose an example');
|
||||
\cli\line();
|
||||
|
||||
if ($choice == 'quit') {
|
||||
break;
|
||||
}
|
||||
|
||||
include "${choice}.php";
|
||||
\cli\line();
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
require_once 'common.php';
|
||||
|
||||
\cli\line("========\nDots\n");
|
||||
|
||||
test_notify(new \cli\notify\Dots(' \cli\notify\Dots cycles through a set number of dots'));
|
||||
test_notify(new \cli\notify\Dots(' You can disable the delay if ticks take longer than a few milliseconds', 5, 0), 10, 100000);
|
||||
|
||||
\cli\line("\n========\nSpinner\n");
|
||||
|
||||
test_notify(new \cli\notify\Spinner(' \cli\notify\Spinner cycles through a set of characters to emulate a spinner'));
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
require_once 'common.php';
|
||||
|
||||
\cli\out(" \\cli\\out sends output to STDOUT\n");
|
||||
\cli\out(" It does not automatically append a new line\n");
|
||||
\cli\out(" It does accept any number of %s which are then %s to %s for formatting\n", 'arguments', 'passed', 'sprintf');
|
||||
\cli\out(" Alternatively, {:a} can use an {:b} as the second argument.\n\n", array('a' => 'you', 'b' => 'array'));
|
||||
|
||||
\cli\err(' \cli\err sends output to STDERR');
|
||||
\cli\err(' It does automatically append a new line');
|
||||
\cli\err(' It does accept any number of %s which are then %s to %s for formatting', 'arguments', 'passed', 'sprintf');
|
||||
\cli\err(" Alternatively, {:a} can use an {:b} as the second argument.\n", array('a' => 'you', 'b' => 'array'));
|
||||
|
||||
\cli\line(' \cli\line forwards to \cli\out for output');
|
||||
\cli\line(' It does automatically append a new line');
|
||||
\cli\line(' It does accept any number of %s which are then %s to %s for formatting', 'arguments', 'passed', 'sprintf');
|
||||
\cli\line(" Alternatively, {:a} can use an {:b} as the second argument.\n", array('a' => 'you', 'b' => 'array'));
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
require_once 'common.php';
|
||||
|
||||
test_notify(new \cli\progress\Bar(' \cli\progress\Bar displays a progress bar', 1000000));
|
||||
test_notify(new \cli\progress\Bar(' It sizes itself dynamically', 1000000));
|
||||
test_notify_msg(new \cli\progress\Bar(' It can even change its message', 5), 5, 1000000);
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
require_once 'common.php';
|
||||
/*
|
||||
* Please notice that the data has to be an 0-based array,
|
||||
* not an associative one if you intend to work with custom
|
||||
* column widths.
|
||||
*/
|
||||
$headers = array('First Name', 'Last Name', 'City', 'State');
|
||||
$data = array(
|
||||
array('Maryam', 'Elliott', 'Elizabeth City', 'SD'),
|
||||
array('Jerry', 'Washington', 'Bessemer', 'ME'),
|
||||
array('Allegra', 'Hopkins', 'Altoona', 'ME'),
|
||||
array('Audrey', 'Oneil', 'Dalton', 'SK'),
|
||||
array('Ruth', 'Mcpherson', 'San Francisco', 'ID'),
|
||||
array('Odessa', 'Tate', 'Chattanooga', 'FL'),
|
||||
array('Violet', 'Nielsen', 'Valdosta', 'AB'),
|
||||
array('Summer', 'Rollins', 'Revere', 'SK'),
|
||||
array('Mufutau', 'Bowers', 'Scottsbluff', 'WI'),
|
||||
array('Grace', 'Rosario', 'Garden Grove', 'KY'),
|
||||
array('Amanda', 'Berry', 'La Habra', 'AZ'),
|
||||
array('Cassady', 'York', 'Fulton', 'BC'),
|
||||
array('Heather', 'Terrell', 'Statesboro', 'SC'),
|
||||
array('Dominic', 'Jimenez', 'West Valley City', 'ME'),
|
||||
array('Rhonda', 'Potter', 'Racine', 'BC'),
|
||||
array('Nathan', 'Velazquez', 'Cedarburg', 'BC'),
|
||||
array('Richard', 'Fletcher', 'Corpus Christi', 'BC'),
|
||||
array('Cheyenne', 'Rios', 'Broken Arrow', 'VA'),
|
||||
array('Velma', 'Clemons', 'Helena', 'IL'),
|
||||
array('Samuel', 'Berry', 'Lawrenceville', 'NU'),
|
||||
array('Marcia', 'Swanson', 'Fontana', 'QC'),
|
||||
array('Zachary', 'Silva', 'Port Washington', 'MB'),
|
||||
array('Hilary', 'Chambers', 'Suffolk', 'HI'),
|
||||
array('Idola', 'Carroll', 'West Sacramento', 'QC'),
|
||||
array('Kirestin', 'Stephens', 'Fitchburg', 'AB'),
|
||||
);
|
||||
|
||||
$table = new \cli\Table();
|
||||
$table->setHeaders($headers);
|
||||
$table->setRows($data);
|
||||
$table->setRenderer(new \cli\table\Ascii([10, 10, 20, 5]));
|
||||
$table->display();
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
require_once 'common.php';
|
||||
|
||||
$data = array(
|
||||
'Test' => array(
|
||||
'Something Cool' => array(
|
||||
'This is a 3rd layer',
|
||||
),
|
||||
'This is a 2nd layer',
|
||||
),
|
||||
'Other test' => array(
|
||||
'This is awesome' => array(
|
||||
'This is also cool',
|
||||
'This is even cooler',
|
||||
'Wow like what is this' => array(
|
||||
'Awesome eh?',
|
||||
'Totally' => array(
|
||||
'Yep!'
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
printf("ASCII:\n");
|
||||
|
||||
/**
|
||||
* ASCII should look something like this:
|
||||
*
|
||||
* -Test
|
||||
* |\-Something Cool
|
||||
* ||\-This is a 3rd layer
|
||||
* |\-This is a 2nd layer
|
||||
* \-Other test
|
||||
* \-This is awesome
|
||||
* \-This is also cool
|
||||
* \-This is even cooler
|
||||
* \-Wow like what is this
|
||||
* \-Awesome eh?
|
||||
* \-Totally
|
||||
* \-Yep!
|
||||
*/
|
||||
|
||||
$tree = new \cli\Tree;
|
||||
$tree->setData($data);
|
||||
$tree->setRenderer(new \cli\tree\Ascii);
|
||||
$tree->display();
|
||||
|
||||
printf("\nMarkdown:\n");
|
||||
|
||||
/**
|
||||
* Markdown looks like this:
|
||||
*
|
||||
* - Test
|
||||
* - Something Cool
|
||||
* - This is a 3rd layer
|
||||
* - This is a 2nd layer
|
||||
* - Other test
|
||||
* - This is awesome
|
||||
* - This is also cool
|
||||
* - This is even cooler
|
||||
* - Wow like what is this
|
||||
* - Awesome eh?
|
||||
* - Totally
|
||||
* - Yep!
|
||||
*/
|
||||
|
||||
$tree = new \cli\Tree;
|
||||
$tree->setData($data);
|
||||
$tree->setRenderer(new \cli\tree\Markdown(4));
|
||||
$tree->display();
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* An example application using php-cli-tools and Buzz
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
define('BUZZ_PATH', realpath('../Buzz'));
|
||||
define('SCRIPT_NAME', array_shift($argv));
|
||||
|
||||
require_once BUZZ_PATH . '/lib/Buzz/ClassLoader.php';
|
||||
Buzz\ClassLoader::register();
|
||||
|
||||
class HttpConsole {
|
||||
protected $_host;
|
||||
protected $_prompt;
|
||||
|
||||
public function __construct($host) {
|
||||
$this->_host = 'http://' . $host;
|
||||
$this->_prompt = '%K' . $this->_host . '%n/%K>%n ';
|
||||
}
|
||||
|
||||
public function handleRequest($type, $path) {
|
||||
$request = new Buzz\Message\Request($type, $path, $this->_host);
|
||||
$response = new Buzz\Message\Response;
|
||||
|
||||
$client = new Buzz\Client\FileGetContents();
|
||||
$client->send($request, $response);
|
||||
|
||||
// Display headers
|
||||
foreach ($response->getHeaders() as $i => $header) {
|
||||
if ($i == 0) {
|
||||
\cli\line('%G{:header}%n', compact('header'));
|
||||
continue;
|
||||
}
|
||||
|
||||
list($key, $value) = explode(': ', $header, 2);
|
||||
\cli\line('%W{:key}%n: {:value}', compact('key', 'value'));
|
||||
}
|
||||
\cli\line("\n");
|
||||
print $response->getContent() . "\n";
|
||||
|
||||
switch ($type) {
|
||||
}
|
||||
}
|
||||
|
||||
public function run() {
|
||||
while (true) {
|
||||
$cmd = \cli\prompt($this->_prompt, false, null);
|
||||
|
||||
if (preg_match('/^(HEAD|GET|POST|PUT|DELETE) (\S+)$/', $cmd, $matches)) {
|
||||
$this->handleRequest($matches[1], $matches[2]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($cmd == '\q') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$console = new HttpConsole(array_shift($argv) ?: '127.0.0.1:80');
|
||||
$console->run();
|
||||
} catch (\Exception $e) {
|
||||
\cli\err("\n\n%R" . $e->getMessage() . "%n\n");
|
||||
}
|
||||
|
||||
?>
|
||||
+488
@@ -0,0 +1,488 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli;
|
||||
|
||||
use cli\arguments\Argument;
|
||||
use cli\arguments\HelpScreen;
|
||||
use cli\arguments\InvalidArguments;
|
||||
use cli\arguments\Lexer;
|
||||
|
||||
/**
|
||||
* Parses command line arguments.
|
||||
*/
|
||||
class Arguments implements \ArrayAccess {
|
||||
protected $_flags = array();
|
||||
protected $_options = array();
|
||||
protected $_strict = false;
|
||||
protected $_input = array();
|
||||
protected $_invalid = array();
|
||||
protected $_parsed;
|
||||
protected $_lexer;
|
||||
|
||||
/**
|
||||
* Initializes the argument parser. If you wish to change the default behaviour
|
||||
* you may pass an array of options as the first argument. Valid options are
|
||||
* `'help'` and `'strict'`, each a boolean.
|
||||
*
|
||||
* `'help'` is `true` by default, `'strict'` is false by default.
|
||||
*
|
||||
* @param array $options An array of options for this parser.
|
||||
*/
|
||||
public function __construct($options = array()) {
|
||||
$options += array(
|
||||
'strict' => false,
|
||||
'input' => array_slice($_SERVER['argv'], 1)
|
||||
);
|
||||
|
||||
$this->_input = $options['input'];
|
||||
$this->setStrict($options['strict']);
|
||||
|
||||
if (isset($options['flags'])) {
|
||||
$this->addFlags($options['flags']);
|
||||
}
|
||||
if (isset($options['options'])) {
|
||||
$this->addOptions($options['options']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of arguments found by the defined definitions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getArguments() {
|
||||
if (!isset($this->_parsed)) {
|
||||
$this->parse();
|
||||
}
|
||||
return $this->_parsed;
|
||||
}
|
||||
|
||||
public function getHelpScreen() {
|
||||
return new HelpScreen($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the parsed arguments as JSON.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function asJSON() {
|
||||
return json_encode($this->_parsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a given argument was parsed.
|
||||
*
|
||||
* @param mixed $offset An Argument object or the name of the argument.
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset) {
|
||||
if ($offset instanceOf Argument) {
|
||||
$offset = $offset->key;
|
||||
}
|
||||
|
||||
return array_key_exists($offset, $this->_parsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parsed argument's value.
|
||||
*
|
||||
* @param mixed $offset An Argument object or the name of the argument.
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
if ($offset instanceOf Argument) {
|
||||
$offset = $offset->key;
|
||||
}
|
||||
|
||||
if (isset($this->_parsed[$offset])) {
|
||||
return $this->_parsed[$offset];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a parsed argument.
|
||||
*
|
||||
* @param mixed $offset An Argument object or the name of the argument.
|
||||
* @param mixed $value The value to set
|
||||
*/
|
||||
public function offsetSet($offset, $value) {
|
||||
if ($offset instanceOf Argument) {
|
||||
$offset = $offset->key;
|
||||
}
|
||||
|
||||
$this->_parsed[$offset] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset a parsed argument.
|
||||
*
|
||||
* @param mixed $offset An Argument object or the name of the argument.
|
||||
*/
|
||||
public function offsetUnset($offset) {
|
||||
if ($offset instanceOf Argument) {
|
||||
$offset = $offset->key;
|
||||
}
|
||||
|
||||
unset($this->_parsed[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a flag (boolean argument) to the argument list.
|
||||
*
|
||||
* @param mixed $flag A string representing the flag, or an array of strings.
|
||||
* @param array $settings An array of settings for this flag.
|
||||
* @setting string description A description to be shown in --help.
|
||||
* @setting bool default The default value for this flag.
|
||||
* @setting bool stackable Whether the flag is repeatable to increase the value.
|
||||
* @setting array aliases Other ways to trigger this flag.
|
||||
* @return $this
|
||||
*/
|
||||
public function addFlag($flag, $settings = array()) {
|
||||
if (is_string($settings)) {
|
||||
$settings = array('description' => $settings);
|
||||
}
|
||||
if (is_array($flag)) {
|
||||
$settings['aliases'] = $flag;
|
||||
$flag = array_shift($settings['aliases']);
|
||||
}
|
||||
if (isset($this->_flags[$flag])) {
|
||||
$this->_warn('flag already exists: ' . $flag);
|
||||
return $this;
|
||||
}
|
||||
|
||||
$settings += array(
|
||||
'default' => false,
|
||||
'stackable' => false,
|
||||
'description' => null,
|
||||
'aliases' => array()
|
||||
);
|
||||
|
||||
$this->_flags[$flag] = $settings;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple flags at once. The input array should be keyed with the
|
||||
* primary flag character, and the values should be the settings array
|
||||
* used by {addFlag}.
|
||||
*
|
||||
* @param array $flags An array of flags to add
|
||||
* @return $this
|
||||
*/
|
||||
public function addFlags($flags) {
|
||||
foreach ($flags as $flag => $settings) {
|
||||
if (is_numeric($flag)) {
|
||||
$this->_warn('No flag character given');
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addFlag($flag, $settings);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an option (string argument) to the argument list.
|
||||
*
|
||||
* @param mixed $option A string representing the option, or an array of strings.
|
||||
* @param array $settings An array of settings for this option.
|
||||
* @setting string description A description to be shown in --help.
|
||||
* @setting bool default The default value for this option.
|
||||
* @setting array aliases Other ways to trigger this option.
|
||||
* @return $this
|
||||
*/
|
||||
public function addOption($option, $settings = array()) {
|
||||
if (is_string($settings)) {
|
||||
$settings = array('description' => $settings);
|
||||
}
|
||||
if (is_array($option)) {
|
||||
$settings['aliases'] = $option;
|
||||
$option = array_shift($settings['aliases']);
|
||||
}
|
||||
if (isset($this->_options[$option])) {
|
||||
$this->_warn('option already exists: ' . $option);
|
||||
return $this;
|
||||
}
|
||||
|
||||
$settings += array(
|
||||
'default' => null,
|
||||
'description' => null,
|
||||
'aliases' => array()
|
||||
);
|
||||
|
||||
$this->_options[$option] = $settings;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple options at once. The input array should be keyed with the
|
||||
* primary option string, and the values should be the settings array
|
||||
* used by {addOption}.
|
||||
*
|
||||
* @param array $options An array of options to add
|
||||
* @return $this
|
||||
*/
|
||||
public function addOptions($options) {
|
||||
foreach ($options as $option => $settings) {
|
||||
if (is_numeric($option)) {
|
||||
$this->_warn('No option string given');
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addOption($option, $settings);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable strict mode. If strict mode is active any invalid
|
||||
* arguments found by the parser will throw `cli\arguments\InvalidArguments`.
|
||||
*
|
||||
* Even if strict is disabled, invalid arguments are logged and can be
|
||||
* retrieved with `cli\Arguments::getInvalidArguments()`.
|
||||
*
|
||||
* @param bool $strict True to enable, false to disable.
|
||||
* @return $this
|
||||
*/
|
||||
public function setStrict($strict) {
|
||||
$this->_strict = (bool)$strict;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of invalid arguments the parser found.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getInvalidArguments() {
|
||||
return $this->_invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a flag by primary matcher or any defined aliases.
|
||||
*
|
||||
* @param mixed $flag Either a string representing the flag or an
|
||||
* cli\arguments\Argument object.
|
||||
* @return array
|
||||
*/
|
||||
public function getFlag($flag) {
|
||||
if ($flag instanceOf Argument) {
|
||||
$obj = $flag;
|
||||
$flag = $flag->value;
|
||||
}
|
||||
|
||||
if (isset($this->_flags[$flag])) {
|
||||
return $this->_flags[$flag];
|
||||
}
|
||||
|
||||
foreach ($this->_flags as $master => $settings) {
|
||||
if (in_array($flag, (array)$settings['aliases'])) {
|
||||
if (isset($obj)) {
|
||||
$obj->key = $master;
|
||||
}
|
||||
|
||||
$cache[$flag] =& $settings;
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getFlags() {
|
||||
return $this->_flags;
|
||||
}
|
||||
|
||||
public function hasFlags() {
|
||||
return !empty($this->_flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given argument is defined as a flag.
|
||||
*
|
||||
* @param mixed $argument Either a string representing the flag or an
|
||||
* cli\arguments\Argument object.
|
||||
* @return bool
|
||||
*/
|
||||
public function isFlag($argument) {
|
||||
return (null !== $this->getFlag($argument));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given flag is stackable.
|
||||
*
|
||||
* @param mixed $flag Either a string representing the flag or an
|
||||
* cli\arguments\Argument object.
|
||||
* @return bool
|
||||
*/
|
||||
public function isStackable($flag) {
|
||||
$settings = $this->getFlag($flag);
|
||||
|
||||
return isset($settings) && (true === $settings['stackable']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an option by primary matcher or any defined aliases.
|
||||
*
|
||||
* @param mixed $option Either a string representing the option or an
|
||||
* cli\arguments\Argument object.
|
||||
* @return array
|
||||
*/
|
||||
public function getOption($option) {
|
||||
if ($option instanceOf Argument) {
|
||||
$obj = $option;
|
||||
$option = $option->value;
|
||||
}
|
||||
|
||||
if (isset($this->_options[$option])) {
|
||||
return $this->_options[$option];
|
||||
}
|
||||
|
||||
foreach ($this->_options as $master => $settings) {
|
||||
if (in_array($option, (array)$settings['aliases'])) {
|
||||
if (isset($obj)) {
|
||||
$obj->key = $master;
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getOptions() {
|
||||
return $this->_options;
|
||||
}
|
||||
|
||||
public function hasOptions() {
|
||||
return !empty($this->_options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given argument is defined as an option.
|
||||
*
|
||||
* @param mixed $argument Either a string representing the option or an
|
||||
* cli\arguments\Argument object.
|
||||
* @return bool
|
||||
*/
|
||||
public function isOption($argument) {
|
||||
return (null != $this->getOption($argument));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the argument list with the given options. The returned argument list
|
||||
* will use either the first long name given or the first name in the list
|
||||
* if a long name is not given.
|
||||
*
|
||||
* @return array
|
||||
* @throws arguments\InvalidArguments
|
||||
*/
|
||||
public function parse() {
|
||||
$this->_invalid = array();
|
||||
$this->_parsed = array();
|
||||
$this->_lexer = new Lexer($this->_input);
|
||||
|
||||
$this->_applyDefaults();
|
||||
|
||||
foreach ($this->_lexer as $argument) {
|
||||
if ($this->_parseFlag($argument)) {
|
||||
continue;
|
||||
}
|
||||
if ($this->_parseOption($argument)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array_push($this->_invalid, $argument->raw);
|
||||
}
|
||||
|
||||
if ($this->_strict && !empty($this->_invalid)) {
|
||||
throw new InvalidArguments($this->_invalid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This applies the default values, if any, of all of the
|
||||
* flags and options, so that if there is a default value
|
||||
* it will be available.
|
||||
*/
|
||||
private function _applyDefaults() {
|
||||
foreach($this->_flags as $flag => $settings) {
|
||||
$this[$flag] = $settings['default'];
|
||||
}
|
||||
|
||||
foreach($this->_options as $option => $settings) {
|
||||
// If the default is 0 we should still let it be set.
|
||||
if (!empty($settings['default']) || $settings['default'] === 0) {
|
||||
$this[$option] = $settings['default'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function _warn($message) {
|
||||
trigger_error('[' . __CLASS__ .'] ' . $message, E_USER_WARNING);
|
||||
}
|
||||
|
||||
private function _parseFlag($argument) {
|
||||
if (!$this->isFlag($argument)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isStackable($argument)) {
|
||||
if (!isset($this[$argument])) {
|
||||
$this[$argument->key] = 0;
|
||||
}
|
||||
|
||||
$this[$argument->key] += 1;
|
||||
} else {
|
||||
$this[$argument->key] = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function _parseOption($option) {
|
||||
if (!$this->isOption($option)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Peak ahead to make sure we get a value.
|
||||
if ($this->_lexer->end() || !$this->_lexer->peek->isValue) {
|
||||
$optionSettings = $this->getOption($option->key);
|
||||
|
||||
if (empty($optionSettings['default']) && $optionSettings !== 0) {
|
||||
// Oops! Got no value and no default , throw a warning and continue.
|
||||
$this->_warn('no value given for ' . $option->raw);
|
||||
$this[$option->key] = null;
|
||||
} else {
|
||||
// No value and we have a default, so we set to the default
|
||||
$this[$option->key] = $optionSettings['default'];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Store as array and join to string after looping for values
|
||||
$values = array();
|
||||
|
||||
// Loop until we find a flag in peak-ahead
|
||||
foreach ($this->_lexer as $value) {
|
||||
array_push($values, $value->raw);
|
||||
|
||||
if (!$this->_lexer->end() && !$this->_lexer->peek->isValue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this[$option->key] = join($values, ' ');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli;
|
||||
|
||||
/**
|
||||
* Change the color of text.
|
||||
*
|
||||
* Reference: http://graphcomp.com/info/specs/ansi_col.html#colors
|
||||
*/
|
||||
class Colors {
|
||||
static protected $_colors = array(
|
||||
'color' => array(
|
||||
'black' => 30,
|
||||
'red' => 31,
|
||||
'green' => 32,
|
||||
'yellow' => 33,
|
||||
'blue' => 34,
|
||||
'magenta' => 35,
|
||||
'cyan' => 36,
|
||||
'white' => 37
|
||||
),
|
||||
'style' => array(
|
||||
'bright' => 1,
|
||||
'dim' => 2,
|
||||
'underline' => 4,
|
||||
'blink' => 5,
|
||||
'reverse' => 7,
|
||||
'hidden' => 8
|
||||
),
|
||||
'background' => array(
|
||||
'black' => 40,
|
||||
'red' => 41,
|
||||
'green' => 42,
|
||||
'yellow' => 43,
|
||||
'blue' => 44,
|
||||
'magenta' => 45,
|
||||
'cyan' => 46,
|
||||
'white' => 47
|
||||
)
|
||||
);
|
||||
static protected $_enabled = null;
|
||||
|
||||
static protected $_string_cache = array();
|
||||
|
||||
static public function enable($force = true) {
|
||||
self::$_enabled = $force === true ? true : null;
|
||||
}
|
||||
|
||||
static public function disable($force = true) {
|
||||
self::$_enabled = $force === true ? false : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we should colorize output based on local flags and shell type.
|
||||
*
|
||||
* Only check the shell type if `Colors::$_enabled` is null and `$colored` is null.
|
||||
*/
|
||||
static public function shouldColorize($colored = null) {
|
||||
return self::$_enabled === true ||
|
||||
(self::$_enabled !== false &&
|
||||
($colored === true ||
|
||||
($colored !== false && Streams::isTty())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color.
|
||||
*
|
||||
* @param string $color The name of the color or style to set.
|
||||
* @return string
|
||||
*/
|
||||
static public function color($color) {
|
||||
if (!is_array($color)) {
|
||||
$color = compact('color');
|
||||
}
|
||||
|
||||
$color += array('color' => null, 'style' => null, 'background' => null);
|
||||
|
||||
if ($color['color'] == 'reset') {
|
||||
return "\033[0m";
|
||||
}
|
||||
|
||||
$colors = array();
|
||||
foreach (array('color', 'style', 'background') as $type) {
|
||||
$code = $color[$type];
|
||||
if (isset(self::$_colors[$type][$code])) {
|
||||
$colors[] = self::$_colors[$type][$code];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($colors)) {
|
||||
$colors[] = 0;
|
||||
}
|
||||
|
||||
return "\033[" . join(';', $colors) . "m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorize a string using helpful string formatters. If the `Streams::$out` points to a TTY coloring will be enabled,
|
||||
* otherwise disabled. You can control this check with the `$colored` parameter.
|
||||
*
|
||||
* @param string $string
|
||||
* @param boolean $colored Force enable or disable the colorized output. If left as `null` the TTY will control coloring.
|
||||
* @return string
|
||||
*/
|
||||
static public function colorize($string, $colored = null) {
|
||||
$passed = $string;
|
||||
|
||||
if (!self::shouldColorize($colored)) {
|
||||
$return = self::decolorize( $passed, 2 /*keep_encodings*/ );
|
||||
self::cacheString($passed, $return);
|
||||
return $return;
|
||||
}
|
||||
|
||||
$md5 = md5($passed);
|
||||
if (isset(self::$_string_cache[$md5]['colorized'])) {
|
||||
return self::$_string_cache[$md5]['colorized'];
|
||||
}
|
||||
|
||||
$string = str_replace('%%', '%¾', $string);
|
||||
|
||||
foreach (self::getColors() as $key => $value) {
|
||||
$string = str_replace($key, self::color($value), $string);
|
||||
}
|
||||
|
||||
$string = str_replace('%¾', '%', $string);
|
||||
self::cacheString($passed, $string);
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove color information from a string.
|
||||
*
|
||||
* @param string $string A string with color information.
|
||||
* @param int $keep Optional. If the 1 bit is set, color tokens (eg "%n") won't be stripped. If the 2 bit is set, color encodings (ANSI escapes) won't be stripped. Default 0.
|
||||
* @return string A string with color information removed.
|
||||
*/
|
||||
static public function decolorize( $string, $keep = 0 ) {
|
||||
if ( ! ( $keep & 1 ) ) {
|
||||
// Get rid of color tokens if they exist
|
||||
$string = str_replace('%%', '%¾', $string);
|
||||
$string = str_replace(array_keys(self::getColors()), '', $string);
|
||||
$string = str_replace('%¾', '%', $string);
|
||||
}
|
||||
|
||||
if ( ! ( $keep & 2 ) ) {
|
||||
// Remove color encoding if it exists
|
||||
foreach (self::getColors() as $key => $value) {
|
||||
$string = str_replace(self::color($value), '', $string);
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache the original, colorized, and decolorized versions of a string.
|
||||
*
|
||||
* @param string $passed The original string before colorization.
|
||||
* @param string $colorized The string after running through self::colorize.
|
||||
* @param string $deprecated Optional. Not used. Default null.
|
||||
*/
|
||||
static public function cacheString( $passed, $colorized, $deprecated = null ) {
|
||||
self::$_string_cache[md5($passed)] = array(
|
||||
'passed' => $passed,
|
||||
'colorized' => $colorized,
|
||||
'decolorized' => self::decolorize($passed), // Not very useful but keep for BC.
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of the string without color codes.
|
||||
*
|
||||
* @param string $string the string to measure
|
||||
* @return int
|
||||
*/
|
||||
static public function length($string) {
|
||||
return safe_strlen( self::decolorize( $string ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width (length in characters) of the string without color codes if enabled.
|
||||
*
|
||||
* @param string $string The string to measure.
|
||||
* @param bool $pre_colorized Optional. Set if the string is pre-colorized. Default false.
|
||||
* @param string|bool $encoding Optional. The encoding of the string. Default false.
|
||||
* @return int
|
||||
*/
|
||||
static public function width( $string, $pre_colorized = false, $encoding = false ) {
|
||||
return strwidth( $pre_colorized || self::shouldColorize() ? self::decolorize( $string, $pre_colorized ? 1 /*keep_tokens*/ : 0 ) : $string, $encoding );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad the string to a certain display length.
|
||||
*
|
||||
* @param string $string The string to pad.
|
||||
* @param int $length The display length.
|
||||
* @param bool $pre_colorized Optional. Set if the string is pre-colorized. Default false.
|
||||
* @param string|bool $encoding Optional. The encoding of the string. Default false.
|
||||
* @param int $pad_type Optional. Can be STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH. If pad_type is not specified it is assumed to be STR_PAD_RIGHT.
|
||||
* @return string
|
||||
*/
|
||||
static public function pad( $string, $length, $pre_colorized = false, $encoding = false, $pad_type = STR_PAD_RIGHT ) {
|
||||
$real_length = self::width( $string, $pre_colorized, $encoding );
|
||||
$diff = strlen( $string ) - $real_length;
|
||||
$length += $diff;
|
||||
|
||||
return str_pad( $string, $length, ' ', $pad_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color mapping array.
|
||||
*
|
||||
* @return array Array of color tokens mapped to colors and styles.
|
||||
*/
|
||||
static public function getColors() {
|
||||
return array(
|
||||
'%y' => array('color' => 'yellow'),
|
||||
'%g' => array('color' => 'green'),
|
||||
'%b' => array('color' => 'blue'),
|
||||
'%r' => array('color' => 'red'),
|
||||
'%p' => array('color' => 'magenta'),
|
||||
'%m' => array('color' => 'magenta'),
|
||||
'%c' => array('color' => 'cyan'),
|
||||
'%w' => array('color' => 'white'),
|
||||
'%k' => array('color' => 'black'),
|
||||
'%n' => array('color' => 'reset'),
|
||||
'%Y' => array('color' => 'yellow', 'style' => 'bright'),
|
||||
'%G' => array('color' => 'green', 'style' => 'bright'),
|
||||
'%B' => array('color' => 'blue', 'style' => 'bright'),
|
||||
'%R' => array('color' => 'red', 'style' => 'bright'),
|
||||
'%P' => array('color' => 'magenta', 'style' => 'bright'),
|
||||
'%M' => array('color' => 'magenta', 'style' => 'bright'),
|
||||
'%C' => array('color' => 'cyan', 'style' => 'bright'),
|
||||
'%W' => array('color' => 'white', 'style' => 'bright'),
|
||||
'%K' => array('color' => 'black', 'style' => 'bright'),
|
||||
'%N' => array('color' => 'reset', 'style' => 'bright'),
|
||||
'%3' => array('background' => 'yellow'),
|
||||
'%2' => array('background' => 'green'),
|
||||
'%4' => array('background' => 'blue'),
|
||||
'%1' => array('background' => 'red'),
|
||||
'%5' => array('background' => 'magenta'),
|
||||
'%6' => array('background' => 'cyan'),
|
||||
'%7' => array('background' => 'white'),
|
||||
'%0' => array('background' => 'black'),
|
||||
'%F' => array('style' => 'blink'),
|
||||
'%U' => array('style' => 'underline'),
|
||||
'%8' => array('style' => 'reverse'),
|
||||
'%9' => array('style' => 'bright'),
|
||||
'%_' => array('style' => 'bright')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cached string values.
|
||||
*
|
||||
* @return array The cached string values.
|
||||
*/
|
||||
static public function getStringCache() {
|
||||
return self::$_string_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the string cache.
|
||||
*/
|
||||
static public function clearStringCache() {
|
||||
self::$_string_cache = array();
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli;
|
||||
|
||||
abstract class Memoize {
|
||||
protected $_memoCache = array();
|
||||
|
||||
public function __get($name) {
|
||||
if (isset($this->_memoCache[$name])) {
|
||||
return $this->_memoCache[$name];
|
||||
}
|
||||
|
||||
// Hide probable private methods
|
||||
if (0 == strncmp($name, '_', 1)) {
|
||||
return ($this->_memoCache[$name] = null);
|
||||
}
|
||||
|
||||
if (!method_exists($this, $name)) {
|
||||
return ($this->_memoCache[$name] = null);
|
||||
}
|
||||
|
||||
$method = array($this, $name);
|
||||
($this->_memoCache[$name] = call_user_func($method));
|
||||
return $this->_memoCache[$name];
|
||||
}
|
||||
|
||||
protected function _unmemo($name) {
|
||||
if ($name === true) {
|
||||
$this->_memoCache = array();
|
||||
} else {
|
||||
unset($this->_memoCache[$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli;
|
||||
|
||||
use cli\Streams;
|
||||
|
||||
/**
|
||||
* The `Notify` class is the basis of all feedback classes, such as Indicators
|
||||
* and Progress meters. The default behaviour is to refresh output after 100ms
|
||||
* have passed. This is done to preventing the screen from flickering and keep
|
||||
* slowdowns from output to a minimum.
|
||||
*
|
||||
* The most basic form of Notifier has no maxim, and simply displays a series
|
||||
* of characters to indicate progress is being made.
|
||||
*/
|
||||
abstract class Notify {
|
||||
protected $_current = 0;
|
||||
protected $_first = true;
|
||||
protected $_interval;
|
||||
protected $_message;
|
||||
protected $_start;
|
||||
protected $_timer;
|
||||
|
||||
/**
|
||||
* Instatiates a Notification object.
|
||||
*
|
||||
* @param string $msg The text to display next to the Notifier.
|
||||
* @param int $interval The interval in milliseconds between updates.
|
||||
*/
|
||||
public function __construct($msg, $interval = 100) {
|
||||
$this->_message = $msg;
|
||||
$this->_interval = (int)$interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be used to print out the Notifier. This method is
|
||||
* called from `cli\Notify::tick()` after `cli\Notify::$_interval` has passed.
|
||||
*
|
||||
* @abstract
|
||||
* @param boolean $finish
|
||||
* @see cli\Notify::tick()
|
||||
*/
|
||||
abstract public function display($finish = false);
|
||||
|
||||
/**
|
||||
* Reset the notifier state so the same instance can be used in multiple loops.
|
||||
*/
|
||||
public function reset() {
|
||||
$this->_current = 0;
|
||||
$this->_first = true;
|
||||
$this->_start = null;
|
||||
$this->_timer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted tick count.
|
||||
*
|
||||
* @return string The formatted tick count.
|
||||
*/
|
||||
public function current() {
|
||||
return number_format($this->_current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the time elapsed since the Notifier was first ticked.
|
||||
*
|
||||
* @return int The elapsed time in seconds.
|
||||
*/
|
||||
public function elapsed() {
|
||||
if (!$this->_start) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$elapsed = time() - $this->_start;
|
||||
return $elapsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the speed (number of ticks per second) at which the Notifier
|
||||
* is being updated.
|
||||
*
|
||||
* @return int The number of ticks performed in 1 second.
|
||||
*/
|
||||
public function speed() {
|
||||
static $tick, $iteration = 0, $speed = 0;
|
||||
|
||||
if (!$this->_start) {
|
||||
return 0;
|
||||
} else if (!$tick) {
|
||||
$tick = $this->_start;
|
||||
}
|
||||
|
||||
$now = microtime(true);
|
||||
$span = $now - $tick;
|
||||
if ($span > 1) {
|
||||
$iteration++;
|
||||
$tick = $now;
|
||||
$speed = ($this->_current / $iteration) / $span;
|
||||
}
|
||||
|
||||
return $speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a time span given in seconds and formats it for display. The
|
||||
* returned string will be in MM:SS form.
|
||||
*
|
||||
* @param int $time The time span in seconds to format.
|
||||
* @return string The formatted time span.
|
||||
*/
|
||||
public function formatTime($time) {
|
||||
return floor($time / 60) . ':' . str_pad($time % 60, 2, 0, STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish our Notification display. Should be called after the Notifier is
|
||||
* no longer needed.
|
||||
*
|
||||
* @see cli\Notify::display()
|
||||
*/
|
||||
public function finish() {
|
||||
Streams::out("\r");
|
||||
$this->display(true);
|
||||
Streams::line();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments are tick counter by the given amount. If no amount is provided,
|
||||
* the ticker is incremented by 1.
|
||||
*
|
||||
* @param int $increment The amount to increment by.
|
||||
*/
|
||||
public function increment($increment = 1) {
|
||||
$this->_current += $increment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the display should be updated or not according to
|
||||
* our interval setting.
|
||||
*
|
||||
* @return boolean `true` if the display should be updated, `false` otherwise.
|
||||
*/
|
||||
public function shouldUpdate() {
|
||||
$now = microtime(true) * 1000;
|
||||
|
||||
if (empty($this->_timer)) {
|
||||
$this->_start = (int)(($this->_timer = $now) / 1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (($now - $this->_timer) > $this->_interval) {
|
||||
$this->_timer = $now;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is the meat of all Notifiers. First we increment the ticker
|
||||
* and then update the display if enough time has passed since our last tick.
|
||||
*
|
||||
* @param int $increment The amount to increment by.
|
||||
* @see cli\Notify::increment()
|
||||
* @see cli\Notify::shouldUpdate()
|
||||
* @see cli\Notify::display()
|
||||
*/
|
||||
public function tick($increment = 1) {
|
||||
$this->increment($increment);
|
||||
|
||||
if ($this->shouldUpdate()) {
|
||||
Streams::out("\r");
|
||||
$this->display();
|
||||
}
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli;
|
||||
|
||||
/**
|
||||
* A more complex type of Notifier, `Progress` Notifiers always have a maxim
|
||||
* value and generally show some form of percent complete or estimated time
|
||||
* to completion along with the standard Notifier displays.
|
||||
*
|
||||
* @see cli\Notify
|
||||
*/
|
||||
abstract class Progress extends \cli\Notify {
|
||||
protected $_total = 0;
|
||||
|
||||
/**
|
||||
* Instantiates a Progress Notifier.
|
||||
*
|
||||
* @param string $msg The text to display next to the Notifier.
|
||||
* @param int $total The total number of ticks we will be performing.
|
||||
* @param int $interval The interval in milliseconds between updates.
|
||||
* @see cli\Progress::setTotal()
|
||||
*/
|
||||
public function __construct($msg, $total, $interval = 100) {
|
||||
parent::__construct($msg, $interval);
|
||||
$this->setTotal($total);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the max increments for this progress notifier.
|
||||
*
|
||||
* @param int $total The total number of times this indicator should be `tick`ed.
|
||||
* @throws \InvalidArgumentException Thrown if the `$total` is less than 0.
|
||||
*/
|
||||
public function setTotal($total) {
|
||||
$this->_total = (int)$total;
|
||||
|
||||
if ($this->_total < 0) {
|
||||
throw new \InvalidArgumentException('Maximum value out of range, must be positive.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the progress state so the same instance can be used in multiple loops.
|
||||
*/
|
||||
public function reset($total = null) {
|
||||
parent::reset();
|
||||
|
||||
if ($total) {
|
||||
$this->setTotal($total);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Behaves in a similar manner to `cli\Notify::current()`, but the output
|
||||
* is padded to match the length of `cli\Progress::total()`.
|
||||
*
|
||||
* @return string The formatted and padded tick count.
|
||||
* @see cli\Progress::total()
|
||||
*/
|
||||
public function current() {
|
||||
$size = strlen($this->total());
|
||||
return str_pad(parent::current(), $size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted total expected ticks.
|
||||
*
|
||||
* @return string The formatted total ticks.
|
||||
*/
|
||||
public function total() {
|
||||
return number_format($this->_total);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the estimated total time for the tick count to reach the
|
||||
* total ticks given.
|
||||
*
|
||||
* @return int The estimated total number of seconds for all ticks to be
|
||||
* completed. This is not the estimated time left, but total.
|
||||
* @see cli\Notify::speed()
|
||||
* @see cli\Notify::elapsed()
|
||||
*/
|
||||
public function estimated() {
|
||||
$speed = $this->speed();
|
||||
if (!$speed || !$this->elapsed()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$estimated = round($this->_total / $speed);
|
||||
return $estimated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the current tick count to the total ticks given at instatiation
|
||||
* time before passing on to `cli\Notify::finish()`.
|
||||
*/
|
||||
public function finish() {
|
||||
$this->_current = $this->_total;
|
||||
parent::finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments are tick counter by the given amount. If no amount is provided,
|
||||
* the ticker is incremented by 1.
|
||||
*
|
||||
* @param int $increment The amount to increment by.
|
||||
*/
|
||||
public function increment($increment = 1) {
|
||||
$this->_current = min($this->_total, $this->_current + $increment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the percentage completed.
|
||||
*
|
||||
* @return float The percent completed.
|
||||
*/
|
||||
public function percent() {
|
||||
if ($this->_total == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return ($this->_current / $this->_total);
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli;
|
||||
|
||||
/**
|
||||
* The `Shell` class is a utility class for shell related tasks such as
|
||||
* information on width.
|
||||
*/
|
||||
class Shell {
|
||||
|
||||
/**
|
||||
* Returns the number of columns the current shell has for display.
|
||||
*
|
||||
* @return int The number of columns.
|
||||
* @todo Test on more systems.
|
||||
*/
|
||||
static public function columns() {
|
||||
static $columns;
|
||||
|
||||
if ( getenv( 'PHP_CLI_TOOLS_TEST_SHELL_COLUMNS_RESET' ) ) {
|
||||
$columns = null;
|
||||
}
|
||||
if ( null === $columns ) {
|
||||
if ( function_exists( 'exec' ) ) {
|
||||
if ( self::is_windows() ) {
|
||||
// Cater for shells such as Cygwin and Git bash where `mode CON` returns an incorrect value for columns.
|
||||
if ( ( $shell = getenv( 'SHELL' ) ) && preg_match( '/(?:bash|zsh)(?:\.exe)?$/', $shell ) && getenv( 'TERM' ) ) {
|
||||
$columns = (int) exec( 'tput cols' );
|
||||
}
|
||||
if ( ! $columns ) {
|
||||
$return_var = -1;
|
||||
$output = array();
|
||||
exec( 'mode CON', $output, $return_var );
|
||||
if ( 0 === $return_var && $output ) {
|
||||
// Look for second line ending in ": <number>" (searching for "Columns:" will fail on non-English locales).
|
||||
if ( preg_match( '/:\s*[0-9]+\n[^:]+:\s*([0-9]+)\n/', implode( "\n", $output ), $matches ) ) {
|
||||
$columns = (int) $matches[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( ! ( $columns = (int) getenv( 'COLUMNS' ) ) ) {
|
||||
$size = exec( '/usr/bin/env stty size 2>/dev/null' );
|
||||
if ( '' !== $size && preg_match( '/[0-9]+ ([0-9]+)/', $size, $matches ) ) {
|
||||
$columns = (int) $matches[1];
|
||||
}
|
||||
if ( ! $columns ) {
|
||||
if ( getenv( 'TERM' ) ) {
|
||||
$columns = (int) exec( '/usr/bin/env tput cols 2>/dev/null' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $columns ) {
|
||||
$columns = 80; // default width of cmd window on Windows OS
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the output of the current script is a TTY or a pipe / redirect
|
||||
*
|
||||
* Returns true if STDOUT output is being redirected to a pipe or a file; false is
|
||||
* output is being sent directly to the terminal.
|
||||
*
|
||||
* If an env variable SHELL_PIPE exists, returned result depends it's
|
||||
* value. Strings like 1, 0, yes, no, that validate to booleans are accepted.
|
||||
*
|
||||
* To enable ASCII formatting even when shell is piped, use the
|
||||
* ENV variable SHELL_PIPE=0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static public function isPiped() {
|
||||
$shellPipe = getenv('SHELL_PIPE');
|
||||
|
||||
if ($shellPipe !== false) {
|
||||
return filter_var($shellPipe, FILTER_VALIDATE_BOOLEAN);
|
||||
} else {
|
||||
return (function_exists('posix_isatty') && !posix_isatty(STDOUT));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses `stty` to hide input/output completely.
|
||||
* @param boolean $hidden Will hide/show the next data. Defaults to true.
|
||||
*/
|
||||
static public function hide($hidden = true) {
|
||||
system( 'stty ' . ( $hidden? '-echo' : 'echo' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this shell in Windows?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static private function is_windows() {
|
||||
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
namespace cli;
|
||||
|
||||
class Streams {
|
||||
|
||||
protected static $out = STDOUT;
|
||||
protected static $in = STDIN;
|
||||
protected static $err = STDERR;
|
||||
|
||||
static function _call( $func, $args ) {
|
||||
$method = __CLASS__ . '::' . $func;
|
||||
return call_user_func_array( $method, $args );
|
||||
}
|
||||
|
||||
static public function isTty() {
|
||||
return (function_exists('posix_isatty') && posix_isatty(static::$out));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles rendering strings. If extra scalar arguments are given after the `$msg`
|
||||
* the string will be rendered with `sprintf`. If the second argument is an `array`
|
||||
* then each key in the array will be the placeholder name. Placeholders are of the
|
||||
* format {:key}.
|
||||
*
|
||||
* @param string $msg The message to render.
|
||||
* @param mixed ... Either scalar arguments or a single array argument.
|
||||
* @return string The rendered string.
|
||||
*/
|
||||
public static function render( $msg ) {
|
||||
$args = func_get_args();
|
||||
|
||||
// No string replacement is needed
|
||||
if( count( $args ) == 1 || ( is_string( $args[1] ) && '' === $args[1] ) ) {
|
||||
return Colors::shouldColorize() ? Colors::colorize( $msg ) : $msg;
|
||||
}
|
||||
|
||||
// If the first argument is not an array just pass to sprintf
|
||||
if( !is_array( $args[1] ) ) {
|
||||
// Colorize the message first so sprintf doesn't bitch at us
|
||||
if ( Colors::shouldColorize() ) {
|
||||
$args[0] = Colors::colorize( $args[0] );
|
||||
}
|
||||
|
||||
// Escape percent characters for sprintf
|
||||
$args[0] = preg_replace('/(%([^\w]|$))/', "%$1", $args[0]);
|
||||
|
||||
return call_user_func_array( 'sprintf', $args );
|
||||
}
|
||||
|
||||
// Here we do named replacement so formatting strings are more understandable
|
||||
foreach( $args[1] as $key => $value ) {
|
||||
$msg = str_replace( '{:' . $key . '}', $value, $msg );
|
||||
}
|
||||
return Colors::shouldColorize() ? Colors::colorize( $msg ) : $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for printing to `STDOUT`. The message and parameters are passed
|
||||
* through `sprintf` before output.
|
||||
*
|
||||
* @param string $msg The message to output in `printf` format.
|
||||
* @param mixed ... Either scalar arguments or a single array argument.
|
||||
* @return void
|
||||
* @see \cli\render()
|
||||
*/
|
||||
public static function out( $msg ) {
|
||||
fwrite( static::$out, self::_call( 'render', func_get_args() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads `$msg` to the width of the shell before passing to `cli\out`.
|
||||
*
|
||||
* @param string $msg The message to pad and pass on.
|
||||
* @param mixed ... Either scalar arguments or a single array argument.
|
||||
* @return void
|
||||
* @see cli\out()
|
||||
*/
|
||||
public static function out_padded( $msg ) {
|
||||
$msg = self::_call( 'render', func_get_args() );
|
||||
self::out( str_pad( $msg, \cli\Shell::columns() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message to `STDOUT` with a newline appended. See `\cli\out` for
|
||||
* more documentation.
|
||||
*
|
||||
* @see cli\out()
|
||||
*/
|
||||
public static function line( $msg = '' ) {
|
||||
// func_get_args is empty if no args are passed even with the default above.
|
||||
$args = array_merge( func_get_args(), array( '' ) );
|
||||
$args[0] .= "\n";
|
||||
|
||||
self::_call( 'out', $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for printing to `STDERR`. The message and parameters are passed
|
||||
* through `sprintf` before output.
|
||||
*
|
||||
* @param string $msg The message to output in `printf` format. With no string,
|
||||
* a newline is printed.
|
||||
* @param mixed ... Either scalar arguments or a single array argument.
|
||||
* @return void
|
||||
*/
|
||||
public static function err( $msg = '' ) {
|
||||
// func_get_args is empty if no args are passed even with the default above.
|
||||
$args = array_merge( func_get_args(), array( '' ) );
|
||||
$args[0] .= "\n";
|
||||
fwrite( static::$err, self::_call( 'render', $args ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes input from `STDIN` in the given format. If an end of transmission
|
||||
* character is sent (^D), an exception is thrown.
|
||||
*
|
||||
* @param string $format A valid input format. See `fscanf` for documentation.
|
||||
* If none is given, all input up to the first newline
|
||||
* is accepted.
|
||||
* @param boolean $hide If true will hide what the user types in.
|
||||
* @return string The input with whitespace trimmed.
|
||||
* @throws \Exception Thrown if ctrl-D (EOT) is sent as input.
|
||||
*/
|
||||
public static function input( $format = null, $hide = false ) {
|
||||
if ( $hide )
|
||||
Shell::hide();
|
||||
|
||||
if( $format ) {
|
||||
fscanf( static::$in, $format . "\n", $line );
|
||||
} else {
|
||||
$line = fgets( static::$in );
|
||||
}
|
||||
|
||||
if ( $hide ) {
|
||||
Shell::hide( false );
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
if( $line === false ) {
|
||||
throw new \Exception( 'Caught ^D during input' );
|
||||
}
|
||||
|
||||
return trim( $line );
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an input prompt. If no default value is provided the prompt will
|
||||
* continue displaying until input is received.
|
||||
*
|
||||
* @param string $question The question to ask the user.
|
||||
* @param bool|string $default A default value if the user provides no input.
|
||||
* @param string $marker A string to append to the question and default value
|
||||
* on display.
|
||||
* @param boolean $hide Optionally hides what the user types in.
|
||||
* @return string The users input.
|
||||
* @see cli\input()
|
||||
*/
|
||||
public static function prompt( $question, $default = null, $marker = ': ', $hide = false ) {
|
||||
if( $default && strpos( $question, '[' ) === false ) {
|
||||
$question .= ' [' . $default . ']';
|
||||
}
|
||||
|
||||
while( true ) {
|
||||
self::out( $question . $marker );
|
||||
$line = self::input( null, $hide );
|
||||
|
||||
if ( trim( $line ) !== '' )
|
||||
return $line;
|
||||
if( $default !== false )
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents a user with a multiple choice question, useful for 'yes/no' type
|
||||
* questions (which this public static function defaults too).
|
||||
*
|
||||
* @param string $question The question to ask the user.
|
||||
* @param string $choice A string of characters allowed as a response. Case is ignored.
|
||||
* @param string $default The default choice. NULL if a default is not allowed.
|
||||
* @return string The users choice.
|
||||
* @see cli\prompt()
|
||||
*/
|
||||
public static function choose( $question, $choice = 'yn', $default = 'n' ) {
|
||||
if( !is_string( $choice ) ) {
|
||||
$choice = join( '', $choice );
|
||||
}
|
||||
|
||||
// Make every choice character lowercase except the default
|
||||
$choice = str_ireplace( $default, strtoupper( $default ), strtolower( $choice ) );
|
||||
// Seperate each choice with a forward-slash
|
||||
$choices = trim( join( '/', preg_split( '//', $choice ) ), '/' );
|
||||
|
||||
while( true ) {
|
||||
$line = self::prompt( sprintf( '%s? [%s]', $question, $choices ), $default, '' );
|
||||
|
||||
if( stripos( $choice, $line ) !== false ) {
|
||||
return strtolower( $line );
|
||||
}
|
||||
if( !empty( $default ) ) {
|
||||
return strtolower( $default );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an array of strings as a menu where a user can enter a number to
|
||||
* choose an option. The array must be a single dimension with either strings
|
||||
* or objects with a `__toString()` method.
|
||||
*
|
||||
* @param array $items The list of items the user can choose from.
|
||||
* @param string $default The index of the default item.
|
||||
* @param string $title The message displayed to the user when prompted.
|
||||
* @return string The index of the chosen item.
|
||||
* @see cli\line()
|
||||
* @see cli\input()
|
||||
* @see cli\err()
|
||||
*/
|
||||
public static function menu( $items, $default = null, $title = 'Choose an item' ) {
|
||||
$map = array_values( $items );
|
||||
|
||||
if( $default && strpos( $title, '[' ) === false && isset( $items[$default] ) ) {
|
||||
$title .= ' [' . $items[$default] . ']';
|
||||
}
|
||||
|
||||
foreach( $map as $idx => $item ) {
|
||||
self::line( ' %d. %s', $idx + 1, (string)$item );
|
||||
}
|
||||
self::line();
|
||||
|
||||
while( true ) {
|
||||
fwrite( static::$out, sprintf( '%s: ', $title ) );
|
||||
$line = self::input();
|
||||
|
||||
if( is_numeric( $line ) ) {
|
||||
$line--;
|
||||
if( isset( $map[$line] ) ) {
|
||||
return array_search( $map[$line], $items );
|
||||
}
|
||||
|
||||
if( $line < 0 || $line >= count( $map ) ) {
|
||||
self::err( 'Invalid menu selection: out of range' );
|
||||
}
|
||||
} else if( isset( $default ) ) {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets one of the streams (input, output, or error) to a `stream` type resource.
|
||||
*
|
||||
* Valid $whichStream values are:
|
||||
* - 'in' (default: STDIN)
|
||||
* - 'out' (default: STDOUT)
|
||||
* - 'err' (default: STDERR)
|
||||
*
|
||||
* Any custom streams will be closed for you on shutdown, so please don't close stream
|
||||
* resources used with this method.
|
||||
*
|
||||
* @param string $whichStream The stream property to update
|
||||
* @param resource $stream The new stream resource to use
|
||||
* @return void
|
||||
* @throws \Exception Thrown if $stream is not a resource of the 'stream' type.
|
||||
*/
|
||||
public static function setStream( $whichStream, $stream ) {
|
||||
if( !is_resource( $stream ) || get_resource_type( $stream ) !== 'stream' ) {
|
||||
throw new \Exception( 'Invalid resource type!' );
|
||||
}
|
||||
if( property_exists( __CLASS__, $whichStream ) ) {
|
||||
static::${$whichStream} = $stream;
|
||||
}
|
||||
register_shutdown_function( function() use ($stream) {
|
||||
fclose( $stream );
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli;
|
||||
|
||||
use cli\Shell;
|
||||
use cli\Streams;
|
||||
use cli\table\Ascii;
|
||||
use cli\table\Renderer;
|
||||
use cli\table\Tabular;
|
||||
|
||||
/**
|
||||
* The `Table` class is used to display data in a tabular format.
|
||||
*/
|
||||
class Table {
|
||||
protected $_renderer;
|
||||
protected $_headers = array();
|
||||
protected $_footers = array();
|
||||
protected $_width = array();
|
||||
protected $_rows = array();
|
||||
|
||||
/**
|
||||
* Initializes the `Table` class.
|
||||
*
|
||||
* There are 3 ways to instantiate this class:
|
||||
*
|
||||
* 1. Pass an array of strings as the first parameter for the column headers
|
||||
* and a 2-dimensional array as the second parameter for the data rows.
|
||||
* 2. Pass an array of hash tables (string indexes instead of numerical)
|
||||
* where each hash table is a row and the indexes of the *first* hash
|
||||
* table are used as the header values.
|
||||
* 3. Pass nothing and use `setHeaders()` and `addRow()` or `setRows()`.
|
||||
*
|
||||
* @param array $headers Headers used in this table. Optional.
|
||||
* @param array $rows The rows of data for this table. Optional.
|
||||
* @param array $footers Footers used in this table. Optional.
|
||||
*/
|
||||
public function __construct(array $headers = null, array $rows = null, array $footers = null) {
|
||||
if (!empty($headers)) {
|
||||
// If all the rows is given in $headers we use the keys from the
|
||||
// first row for the header values
|
||||
if ($rows === null) {
|
||||
$rows = $headers;
|
||||
$keys = array_keys(array_shift($headers));
|
||||
$headers = array();
|
||||
|
||||
foreach ($keys as $header) {
|
||||
$headers[$header] = $header;
|
||||
}
|
||||
}
|
||||
|
||||
$this->setHeaders($headers);
|
||||
$this->setRows($rows);
|
||||
}
|
||||
|
||||
if (!empty($footers)) {
|
||||
$this->setFooters($footers);
|
||||
}
|
||||
|
||||
if (Shell::isPiped()) {
|
||||
$this->setRenderer(new Tabular());
|
||||
} else {
|
||||
$this->setRenderer(new Ascii());
|
||||
}
|
||||
}
|
||||
|
||||
public function resetTable()
|
||||
{
|
||||
$this->_headers = array();
|
||||
$this->_width = array();
|
||||
$this->_rows = array();
|
||||
$this->_footers = array();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the renderer used by this table.
|
||||
*
|
||||
* @param table\Renderer $renderer The renderer to use for output.
|
||||
* @see table\Renderer
|
||||
* @see table\Ascii
|
||||
* @see table\Tabular
|
||||
*/
|
||||
public function setRenderer(Renderer $renderer) {
|
||||
$this->_renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through the row and sets the maximum width for each column.
|
||||
*
|
||||
* @param array $row The table row.
|
||||
* @return array $row
|
||||
*/
|
||||
protected function checkRow(array $row) {
|
||||
foreach ($row as $column => $str) {
|
||||
$width = Colors::width( $str, $this->isAsciiPreColorized( $column ) );
|
||||
if (!isset($this->_width[$column]) || $width > $this->_width[$column]) {
|
||||
$this->_width[$column] = $width;
|
||||
}
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the table to `STDOUT` using `cli\line()`.
|
||||
*
|
||||
* If STDOUT is a pipe or redirected to a file, should output simple
|
||||
* tab-separated text. Otherwise, renders table with ASCII table borders
|
||||
*
|
||||
* @uses cli\Shell::isPiped() Determine what format to output
|
||||
*
|
||||
* @see cli\Table::renderRow()
|
||||
*/
|
||||
public function display() {
|
||||
foreach( $this->getDisplayLines() as $line ) {
|
||||
Streams::line( $line );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table lines to output.
|
||||
*
|
||||
* @see cli\Table::display()
|
||||
* @see cli\Table::renderRow()
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDisplayLines() {
|
||||
$this->_renderer->setWidths($this->_width, $fallback = true);
|
||||
$border = $this->_renderer->border();
|
||||
|
||||
$out = array();
|
||||
if (isset($border)) {
|
||||
$out[] = $border;
|
||||
}
|
||||
$out[] = $this->_renderer->row($this->_headers);
|
||||
if (isset($border)) {
|
||||
$out[] = $border;
|
||||
}
|
||||
|
||||
foreach ($this->_rows as $row) {
|
||||
$row = $this->_renderer->row($row);
|
||||
$row = explode( PHP_EOL, $row );
|
||||
$out = array_merge( $out, $row );
|
||||
}
|
||||
|
||||
if (isset($border)) {
|
||||
$out[] = $border;
|
||||
}
|
||||
|
||||
if ($this->_footers) {
|
||||
$out[] = $this->_renderer->row($this->_footers);
|
||||
if (isset($border)) {
|
||||
$out[] = $border;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the table by a column. Must be called before `cli\Table::display()`.
|
||||
*
|
||||
* @param int $column The index of the column to sort by.
|
||||
*/
|
||||
public function sort($column) {
|
||||
if (!isset($this->_headers[$column])) {
|
||||
trigger_error('No column with index ' . $column, E_USER_NOTICE);
|
||||
return;
|
||||
}
|
||||
|
||||
usort($this->_rows, function($a, $b) use ($column) {
|
||||
return strcmp($a[$column], $b[$column]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the headers of the table.
|
||||
*
|
||||
* @param array $headers An array of strings containing column header names.
|
||||
*/
|
||||
public function setHeaders(array $headers) {
|
||||
$this->_headers = $this->checkRow($headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the footers of the table.
|
||||
*
|
||||
* @param array $footers An array of strings containing column footers names.
|
||||
*/
|
||||
public function setFooters(array $footers) {
|
||||
$this->_footers = $this->checkRow($footers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a row to the table.
|
||||
*
|
||||
* @param array $row The row data.
|
||||
* @see cli\Table::checkRow()
|
||||
*/
|
||||
public function addRow(array $row) {
|
||||
$this->_rows[] = $this->checkRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all previous rows and adds the given rows.
|
||||
*
|
||||
* @param array $rows A 2-dimensional array of row data.
|
||||
* @see cli\Table::addRow()
|
||||
*/
|
||||
public function setRows(array $rows) {
|
||||
$this->_rows = array();
|
||||
foreach ($rows as $row) {
|
||||
$this->addRow($row);
|
||||
}
|
||||
}
|
||||
|
||||
public function countRows() {
|
||||
return count($this->_rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether items in an Ascii table are pre-colorized.
|
||||
*
|
||||
* @param bool|array $precolorized A boolean to set all columns in the table as pre-colorized, or an array of booleans keyed by column index (number) to set individual columns as pre-colorized.
|
||||
* @see cli\Ascii::setPreColorized()
|
||||
*/
|
||||
public function setAsciiPreColorized( $pre_colorized ) {
|
||||
if ( $this->_renderer instanceof Ascii ) {
|
||||
$this->_renderer->setPreColorized( $pre_colorized );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a column in an Ascii table pre-colorized?
|
||||
*
|
||||
* @param int $column Column index to check.
|
||||
* @return bool True if whole Ascii table is marked as pre-colorized, or if the individual column is pre-colorized; else false.
|
||||
* @see cli\Ascii::isPreColorized()
|
||||
*/
|
||||
private function isAsciiPreColorized( $column ) {
|
||||
if ( $this->_renderer instanceof Ascii ) {
|
||||
return $this->_renderer->isPreColorized( $column );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author Ryan Sullivan <rsullivan@connectstudios.com>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli;
|
||||
|
||||
/**
|
||||
* The `Tree` class is used to display data in a tree-like format.
|
||||
*/
|
||||
class Tree {
|
||||
|
||||
protected $_renderer;
|
||||
protected $_data = array();
|
||||
|
||||
/**
|
||||
* Sets the renderer used by this tree.
|
||||
*
|
||||
* @param tree\Renderer $renderer The renderer to use for output.
|
||||
* @see tree\Renderer
|
||||
* @see tree\Ascii
|
||||
* @see tree\Markdown
|
||||
*/
|
||||
public function setRenderer(tree\Renderer $renderer) {
|
||||
$this->_renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data.
|
||||
* Format:
|
||||
* [
|
||||
* 'Label' => [
|
||||
* 'Thing' => ['Thing'],
|
||||
* ],
|
||||
* 'Thing',
|
||||
* ]
|
||||
* @param array $data
|
||||
*/
|
||||
public function setData(array $data)
|
||||
{
|
||||
$this->_data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the tree and return it as a string.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return $this->_renderer->render($this->_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the rendered tree
|
||||
*/
|
||||
public function display()
|
||||
{
|
||||
echo $this->render();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\arguments;
|
||||
|
||||
use cli\Memoize;
|
||||
|
||||
/**
|
||||
* Represents an Argument or a value and provides several helpers related to parsing an argument list.
|
||||
*/
|
||||
class Argument extends Memoize {
|
||||
/**
|
||||
* The canonical name of this argument, used for aliasing.
|
||||
*
|
||||
* @param string
|
||||
*/
|
||||
public $key;
|
||||
|
||||
private $_argument;
|
||||
private $_raw;
|
||||
|
||||
/**
|
||||
* @param string $argument The raw argument, leading dashes included.
|
||||
*/
|
||||
public function __construct($argument) {
|
||||
$this->_raw = $argument;
|
||||
$this->key =& $this->_argument;
|
||||
|
||||
if ($this->isLong) {
|
||||
$this->_argument = substr($this->_raw, 2);
|
||||
} else if ($this->isShort) {
|
||||
$this->_argument = substr($this->_raw, 1);
|
||||
} else {
|
||||
$this->_argument = $this->_raw;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw input as a string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return (string)$this->_raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted argument string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function value() {
|
||||
return $this->_argument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw input.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function raw() {
|
||||
return $this->_raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the string matches the pattern for long arguments.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isLong() {
|
||||
return (0 == strncmp($this->_raw, '--', 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the string matches the pattern for short arguments.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isShort() {
|
||||
return !$this->isLong && (0 == strncmp($this->_raw, '-', 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the string matches the pattern for arguments.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArgument() {
|
||||
return $this->isShort() || $this->isLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the string matches the pattern for values.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValue() {
|
||||
return !$this->isArgument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the argument is short but contains several characters. Each
|
||||
* character is considered a separate argument.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canExplode() {
|
||||
return $this->isShort && strlen($this->_argument) > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all but the first character of the argument, removing them from the
|
||||
* objects representation at the same time.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function exploded() {
|
||||
$exploded = array();
|
||||
|
||||
for ($i = strlen($this->_argument); $i > 0; $i--) {
|
||||
array_push($exploded, $this->_argument[$i - 1]);
|
||||
}
|
||||
|
||||
$this->_argument = array_pop($exploded);
|
||||
$this->_raw = '-' . $this->_argument;
|
||||
return $exploded;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\arguments;
|
||||
|
||||
use cli\Arguments;
|
||||
|
||||
/**
|
||||
* Arguments help screen renderer
|
||||
*/
|
||||
class HelpScreen {
|
||||
protected $_flags = array();
|
||||
protected $_maxFlag = 0;
|
||||
protected $_options = array();
|
||||
protected $_maxOption = 0;
|
||||
|
||||
public function __construct(Arguments $arguments) {
|
||||
$this->setArguments($arguments);
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
public function setArguments(Arguments $arguments) {
|
||||
$this->consumeArgumentFlags($arguments);
|
||||
$this->consumeArgumentOptions($arguments);
|
||||
}
|
||||
|
||||
public function consumeArgumentFlags(Arguments $arguments) {
|
||||
$data = $this->_consume($arguments->getFlags());
|
||||
|
||||
$this->_flags = $data[0];
|
||||
$this->_flagMax = $data[1];
|
||||
}
|
||||
|
||||
public function consumeArgumentOptions(Arguments $arguments) {
|
||||
$data = $this->_consume($arguments->getOptions());
|
||||
|
||||
$this->_options = $data[0];
|
||||
$this->_optionMax = $data[1];
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$help = array();
|
||||
|
||||
array_push($help, $this->_renderFlags());
|
||||
array_push($help, $this->_renderOptions());
|
||||
|
||||
return join($help, "\n\n");
|
||||
}
|
||||
|
||||
private function _renderFlags() {
|
||||
if (empty($this->_flags)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "Flags\n" . $this->_renderScreen($this->_flags, $this->_flagMax);
|
||||
}
|
||||
|
||||
private function _renderOptions() {
|
||||
if (empty($this->_options)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "Options\n" . $this->_renderScreen($this->_options, $this->_optionMax);
|
||||
}
|
||||
|
||||
private function _renderScreen($options, $max) {
|
||||
$help = array();
|
||||
foreach ($options as $option => $settings) {
|
||||
$formatted = ' ' . str_pad($option, $max);
|
||||
|
||||
$dlen = 80 - 4 - $max;
|
||||
|
||||
$description = str_split($settings['description'], $dlen);
|
||||
$formatted.= ' ' . array_shift($description);
|
||||
|
||||
if ($settings['default']) {
|
||||
$formatted .= ' [default: ' . $settings['default'] . ']';
|
||||
}
|
||||
|
||||
$pad = str_repeat(' ', $max + 3);
|
||||
while ($desc = array_shift($description)) {
|
||||
$formatted .= "\n${pad}${desc}";
|
||||
}
|
||||
|
||||
array_push($help, $formatted);
|
||||
}
|
||||
|
||||
return join($help, "\n");
|
||||
}
|
||||
|
||||
private function _consume($options) {
|
||||
$max = 0;
|
||||
$out = array();
|
||||
|
||||
foreach ($options as $option => $settings) {
|
||||
$names = array('--' . $option);
|
||||
|
||||
foreach ($settings['aliases'] as $alias) {
|
||||
array_push($names, '-' . $alias);
|
||||
}
|
||||
|
||||
$names = join($names, ', ');
|
||||
$max = max(strlen($names), $max);
|
||||
$out[$names] = $settings;
|
||||
}
|
||||
|
||||
return array($out, $max);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\arguments;
|
||||
|
||||
/**
|
||||
* Thrown when undefined arguments are detected in strict mode.
|
||||
*/
|
||||
class InvalidArguments extends \InvalidArgumentException {
|
||||
protected $arguments;
|
||||
|
||||
/**
|
||||
* @param array $arguments A list of arguments that do not fit the profile.
|
||||
*/
|
||||
public function __construct(array $arguments) {
|
||||
$this->arguments = $arguments;
|
||||
$this->message = $this->_generateMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments that caused the exception.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getArguments() {
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
private function _generateMessage() {
|
||||
return 'unknown argument' .
|
||||
(count($this->arguments) > 1 ? 's' : '') .
|
||||
': ' . join($this->arguments, ', ');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\arguments;
|
||||
|
||||
use cli\Memoize;
|
||||
|
||||
class Lexer extends Memoize implements \Iterator {
|
||||
private $_items = array();
|
||||
private $_index = 0;
|
||||
private $_length = 0;
|
||||
private $_first = true;
|
||||
|
||||
/**
|
||||
* @param array $items A list of strings to process as tokens.
|
||||
*/
|
||||
public function __construct(array $items) {
|
||||
$this->_items = $items;
|
||||
$this->_length = count($items);
|
||||
}
|
||||
|
||||
/**
|
||||
* The current token.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function current() {
|
||||
return $this->_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek ahead to the next token without moving the cursor.
|
||||
*
|
||||
* @return Argument
|
||||
*/
|
||||
public function peek() {
|
||||
return new Argument($this->_items[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the cursor forward 1 element if it is valid.
|
||||
*/
|
||||
public function next() {
|
||||
if ($this->valid()) {
|
||||
$this->_shift();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current position of the cursor.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function key() {
|
||||
return $this->_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forward 1 element and, if the method hasn't been called before, reset
|
||||
* the cursor's position to 0.
|
||||
*/
|
||||
public function rewind() {
|
||||
$this->_shift();
|
||||
if ($this->_first) {
|
||||
$this->_index = 0;
|
||||
$this->_first = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the cursor has not reached the end of the list.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function valid() {
|
||||
return ($this->_index < $this->_length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an element to the front of the stack.
|
||||
*
|
||||
* @param mixed $item The value to set
|
||||
*/
|
||||
public function unshift($item) {
|
||||
array_unshift($this->_items, $item);
|
||||
$this->_length += 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the cursor is at the end of the list.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function end() {
|
||||
return ($this->_index + 1) == $this->_length;
|
||||
}
|
||||
|
||||
private function _shift() {
|
||||
$this->_item = new Argument(array_shift($this->_items));
|
||||
$this->_index += 1;
|
||||
$this->_explode();
|
||||
$this->_unmemo('peek');
|
||||
}
|
||||
|
||||
private function _explode() {
|
||||
if (!$this->_item->canExplode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->_item->exploded as $piece) {
|
||||
$this->unshift('-' . $piece);
|
||||
}
|
||||
}
|
||||
}
|
||||
+415
@@ -0,0 +1,415 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli;
|
||||
|
||||
/**
|
||||
* Handles rendering strings. If extra scalar arguments are given after the `$msg`
|
||||
* the string will be rendered with `sprintf`. If the second argument is an `array`
|
||||
* then each key in the array will be the placeholder name. Placeholders are of the
|
||||
* format {:key}.
|
||||
*
|
||||
* @param string $msg The message to render.
|
||||
* @param mixed ... Either scalar arguments or a single array argument.
|
||||
* @return string The rendered string.
|
||||
*/
|
||||
function render( $msg ) {
|
||||
return Streams::_call( 'render', func_get_args() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for printing to `STDOUT`. The message and parameters are passed
|
||||
* through `sprintf` before output.
|
||||
*
|
||||
* @param string $msg The message to output in `printf` format.
|
||||
* @param mixed ... Either scalar arguments or a single array argument.
|
||||
* @return void
|
||||
* @see \cli\render()
|
||||
*/
|
||||
function out( $msg ) {
|
||||
Streams::_call( 'out', func_get_args() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads `$msg` to the width of the shell before passing to `cli\out`.
|
||||
*
|
||||
* @param string $msg The message to pad and pass on.
|
||||
* @param mixed ... Either scalar arguments or a single array argument.
|
||||
* @return void
|
||||
* @see cli\out()
|
||||
*/
|
||||
function out_padded( $msg ) {
|
||||
Streams::_call( 'out_padded', func_get_args() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message to `STDOUT` with a newline appended. See `\cli\out` for
|
||||
* more documentation.
|
||||
*
|
||||
* @see cli\out()
|
||||
*/
|
||||
function line( $msg = '' ) {
|
||||
Streams::_call( 'line', func_get_args() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for printing to `STDERR`. The message and parameters are passed
|
||||
* through `sprintf` before output.
|
||||
*
|
||||
* @param string $msg The message to output in `printf` format. With no string,
|
||||
* a newline is printed.
|
||||
* @param mixed ... Either scalar arguments or a single array argument.
|
||||
* @return void
|
||||
*/
|
||||
function err( $msg = '' ) {
|
||||
Streams::_call( 'err', func_get_args() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes input from `STDIN` in the given format. If an end of transmission
|
||||
* character is sent (^D), an exception is thrown.
|
||||
*
|
||||
* @param string $format A valid input format. See `fscanf` for documentation.
|
||||
* If none is given, all input up to the first newline
|
||||
* is accepted.
|
||||
* @return string The input with whitespace trimmed.
|
||||
* @throws \Exception Thrown if ctrl-D (EOT) is sent as input.
|
||||
*/
|
||||
function input( $format = null ) {
|
||||
return Streams::input( $format );
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an input prompt. If no default value is provided the prompt will
|
||||
* continue displaying until input is received.
|
||||
*
|
||||
* @param string $question The question to ask the user.
|
||||
* @param string $default A default value if the user provides no input.
|
||||
* @param string $marker A string to append to the question and default value on display.
|
||||
* @param boolean $hide If the user input should be hidden
|
||||
* @return string The users input.
|
||||
* @see cli\input()
|
||||
*/
|
||||
function prompt( $question, $default = false, $marker = ': ', $hide = false ) {
|
||||
return Streams::prompt( $question, $default, $marker, $hide );
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents a user with a multiple choice question, useful for 'yes/no' type
|
||||
* questions (which this function defaults too).
|
||||
*
|
||||
* @param string $question The question to ask the user.
|
||||
* @param string $choice
|
||||
* @param string|null $default The default choice. NULL if a default is not allowed.
|
||||
* @internal param string $valid A string of characters allowed as a response. Case
|
||||
* is ignored.
|
||||
* @return string The users choice.
|
||||
* @see cli\prompt()
|
||||
*/
|
||||
function choose( $question, $choice = 'yn', $default = 'n' ) {
|
||||
return Streams::choose( $question, $choice, $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the same as {@see choose()}, but always asks yes/no and returns a boolean
|
||||
*
|
||||
* @param string $question The question to ask the user.
|
||||
* @param bool|null $default The default choice, in a boolean format.
|
||||
* @return bool
|
||||
*/
|
||||
function confirm( $question, $default = false ) {
|
||||
if ( is_bool( $default ) ) {
|
||||
$default = $default? 'y' : 'n';
|
||||
}
|
||||
$result = choose( $question, 'yn', $default );
|
||||
return $result == 'y';
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an array of strings as a menu where a user can enter a number to
|
||||
* choose an option. The array must be a single dimension with either strings
|
||||
* or objects with a `__toString()` method.
|
||||
*
|
||||
* @param array $items The list of items the user can choose from.
|
||||
* @param string $default The index of the default item.
|
||||
* @param string $title The message displayed to the user when prompted.
|
||||
* @return string The index of the chosen item.
|
||||
* @see cli\line()
|
||||
* @see cli\input()
|
||||
* @see cli\err()
|
||||
*/
|
||||
function menu( $items, $default = null, $title = 'Choose an item' ) {
|
||||
return Streams::menu( $items, $default, $title );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts an encoding-safe way of getting string length. If intl extension or PCRE with '\X' or mb_string extension aren't
|
||||
* available, falls back to basic strlen.
|
||||
*
|
||||
* @param string $str The string to check.
|
||||
* @param string|bool $encoding Optional. The encoding of the string. Default false.
|
||||
* @return int Numeric value that represents the string's length
|
||||
*/
|
||||
function safe_strlen( $str, $encoding = false ) {
|
||||
// Allow for selective testings - "1" bit set tests grapheme_strlen(), "2" preg_match_all( '/\X/u' ), "4" mb_strlen(), "other" strlen().
|
||||
$test_safe_strlen = getenv( 'PHP_CLI_TOOLS_TEST_SAFE_STRLEN' );
|
||||
|
||||
// Assume UTF-8 if no encoding given - `grapheme_strlen()` will return null if given non-UTF-8 string.
|
||||
if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_icu() && null !== ( $length = grapheme_strlen( $str ) ) ) {
|
||||
if ( ! $test_safe_strlen || ( $test_safe_strlen & 1 ) ) {
|
||||
return $length;
|
||||
}
|
||||
}
|
||||
// Assume UTF-8 if no encoding given - `preg_match_all()` will return false if given non-UTF-8 string.
|
||||
if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_pcre_x() && false !== ( $length = preg_match_all( '/\X/u', $str, $dummy /*needed for PHP 5.3*/ ) ) ) {
|
||||
if ( ! $test_safe_strlen || ( $test_safe_strlen & 2 ) ) {
|
||||
return $length;
|
||||
}
|
||||
}
|
||||
// Legacy encodings and old PHPs will reach here.
|
||||
if ( function_exists( 'mb_strlen' ) && ( $encoding || function_exists( 'mb_detect_encoding' ) ) ) {
|
||||
if ( ! $encoding ) {
|
||||
$encoding = mb_detect_encoding( $str, null, true /*strict*/ );
|
||||
}
|
||||
$length = $encoding ? mb_strlen( $str, $encoding ) : mb_strlen( $str ); // mbstring funcs can fail if given `$encoding` arg that evals to false.
|
||||
if ( 'UTF-8' === $encoding ) {
|
||||
// Subtract combining characters.
|
||||
$length -= preg_match_all( get_unicode_regexs( 'm' ), $str, $dummy /*needed for PHP 5.3*/ );
|
||||
}
|
||||
if ( ! $test_safe_strlen || ( $test_safe_strlen & 4 ) ) {
|
||||
return $length;
|
||||
}
|
||||
}
|
||||
return strlen( $str );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts an encoding-safe way of getting a substring. If intl extension or PCRE with '\X' or mb_string extension aren't
|
||||
* available, falls back to substr().
|
||||
*
|
||||
* @param string $str The input string.
|
||||
* @param int $start The starting position of the substring.
|
||||
* @param int|bool|null $length Optional, unless $is_width is set. Maximum length of the substring. Default false. Negative not supported.
|
||||
* @param int|bool $is_width Optional. If set and encoding is UTF-8, $length (which must be specified) is interpreted as spacing width. Default false.
|
||||
* @param string|bool $encoding Optional. The encoding of the string. Default false.
|
||||
* @return bool|string False if given unsupported args, otherwise substring of string specified by start and length parameters
|
||||
*/
|
||||
function safe_substr( $str, $start, $length = false, $is_width = false, $encoding = false ) {
|
||||
// Negative $length or $is_width and $length not specified not supported.
|
||||
if ( $length < 0 || ( $is_width && ( null === $length || false === $length ) ) ) {
|
||||
return false;
|
||||
}
|
||||
// Need this for normalization below and other uses.
|
||||
$safe_strlen = safe_strlen( $str, $encoding );
|
||||
|
||||
// Normalize `$length` when not specified - PHP 5.3 substr takes false as full length, PHP > 5.3 takes null.
|
||||
if ( null === $length || false === $length ) {
|
||||
$length = $safe_strlen;
|
||||
}
|
||||
// Normalize `$start` - various methods treat this differently.
|
||||
if ( $start > $safe_strlen ) {
|
||||
return '';
|
||||
}
|
||||
if ( $start < 0 && -$start > $safe_strlen ) {
|
||||
$start = 0;
|
||||
}
|
||||
|
||||
// Allow for selective testings - "1" bit set tests grapheme_substr(), "2" preg_split( '/\X/' ), "4" mb_substr(), "8" substr().
|
||||
$test_safe_substr = getenv( 'PHP_CLI_TOOLS_TEST_SAFE_SUBSTR' );
|
||||
|
||||
// Assume UTF-8 if no encoding given - `grapheme_substr()` will return false (not null like `grapheme_strlen()`) if given non-UTF-8 string.
|
||||
if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_icu() && false !== ( $try = grapheme_substr( $str, $start, $length ) ) ) {
|
||||
if ( ! $test_safe_substr || ( $test_safe_substr & 1 ) ) {
|
||||
return $is_width ? _safe_substr_eaw( $try, $length ) : $try;
|
||||
}
|
||||
}
|
||||
// Assume UTF-8 if no encoding given - `preg_split()` returns a one element array if given non-UTF-8 string (PHP bug) so need to check `preg_last_error()`.
|
||||
if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_pcre_x() ) {
|
||||
if ( false !== ( $try = preg_split( '/(\X)/u', $str, $safe_strlen + 1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ) ) && ! preg_last_error() ) {
|
||||
$try = implode( '', array_slice( $try, $start, $length ) );
|
||||
if ( ! $test_safe_substr || ( $test_safe_substr & 2 ) ) {
|
||||
return $is_width ? _safe_substr_eaw( $try, $length ) : $try;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Legacy encodings and old PHPs will reach here.
|
||||
if ( function_exists( 'mb_substr' ) && ( $encoding || function_exists( 'mb_detect_encoding' ) ) ) {
|
||||
if ( ! $encoding ) {
|
||||
$encoding = mb_detect_encoding( $str, null, true /*strict*/ );
|
||||
}
|
||||
// Bug: not adjusting for combining chars.
|
||||
$try = $encoding ? mb_substr( $str, $start, $length, $encoding ) : mb_substr( $str, $start, $length ); // mbstring funcs can fail if given `$encoding` arg that evals to false.
|
||||
if ( 'UTF-8' === $encoding && $is_width ) {
|
||||
$try = _safe_substr_eaw( $try, $length );
|
||||
}
|
||||
if ( ! $test_safe_substr || ( $test_safe_substr & 4 ) ) {
|
||||
return $try;
|
||||
}
|
||||
}
|
||||
return substr( $str, $start, $length );
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function used by `safe_substr()` to adjust for East Asian double-width chars.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function _safe_substr_eaw( $str, $length ) {
|
||||
// Set the East Asian Width regex.
|
||||
$eaw_regex = get_unicode_regexs( 'eaw' );
|
||||
|
||||
// If there's any East Asian double-width chars...
|
||||
if ( preg_match( $eaw_regex, $str ) ) {
|
||||
// Note that if the length ends in the middle of a double-width char, the char is excluded, not included.
|
||||
|
||||
// See if it's all EAW.
|
||||
if ( function_exists( 'mb_substr' ) && preg_match_all( $eaw_regex, $str, $dummy /*needed for PHP 5.3*/ ) === $length ) {
|
||||
// Just halve the length so (rounded down to a minimum of 1).
|
||||
$str = mb_substr( $str, 0, max( (int) ( $length / 2 ), 1 ), 'UTF-8' );
|
||||
} else {
|
||||
// Explode string into an array of UTF-8 chars. Based on core `_mb_substr()` in "wp-includes/compat.php".
|
||||
$chars = preg_split( '/([\x00-\x7f\xc2-\xf4][^\x00-\x7f\xc2-\xf4]*)/', $str, $length + 1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
|
||||
$cnt = min( count( $chars ), $length );
|
||||
$width = $length;
|
||||
|
||||
for ( $length = 0; $length < $cnt && $width > 0; $length++ ) {
|
||||
$width -= preg_match( $eaw_regex, $chars[ $length ] ) ? 2 : 1;
|
||||
}
|
||||
// Round down to a minimum of 1.
|
||||
if ( $width < 0 && $length > 1 ) {
|
||||
$length--;
|
||||
}
|
||||
return join( '', array_slice( $chars, 0, $length ) );
|
||||
}
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* An encoding-safe way of padding string length for display
|
||||
*
|
||||
* @param string $string The string to pad.
|
||||
* @param int $length The length to pad it to.
|
||||
* @param string|bool $encoding Optional. The encoding of the string. Default false.
|
||||
* @return string
|
||||
*/
|
||||
function safe_str_pad( $string, $length, $encoding = false ) {
|
||||
$real_length = strwidth( $string, $encoding );
|
||||
$diff = strlen( $string ) - $real_length;
|
||||
$length += $diff;
|
||||
|
||||
return str_pad( $string, $length );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get width of string, ie length in characters, taking into account multi-byte and mark characters for UTF-8, and multi-byte for non-UTF-8.
|
||||
*
|
||||
* @param string $string The string to check.
|
||||
* @param string|bool $encoding Optional. The encoding of the string. Default false.
|
||||
* @return int The string's width.
|
||||
*/
|
||||
function strwidth( $string, $encoding = false ) {
|
||||
// Set the East Asian Width and Mark regexs.
|
||||
list( $eaw_regex, $m_regex ) = get_unicode_regexs();
|
||||
|
||||
// Allow for selective testings - "1" bit set tests grapheme_strlen(), "2" preg_match_all( '/\X/u' ), "4" mb_strwidth(), "other" safe_strlen().
|
||||
$test_strwidth = getenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH' );
|
||||
|
||||
// Assume UTF-8 if no encoding given - `grapheme_strlen()` will return null if given non-UTF-8 string.
|
||||
if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_icu() && null !== ( $width = grapheme_strlen( $string ) ) ) {
|
||||
if ( ! $test_strwidth || ( $test_strwidth & 1 ) ) {
|
||||
return $width + preg_match_all( $eaw_regex, $string, $dummy /*needed for PHP 5.3*/ );
|
||||
}
|
||||
}
|
||||
// Assume UTF-8 if no encoding given - `preg_match_all()` will return false if given non-UTF-8 string.
|
||||
if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_pcre_x() && false !== ( $width = preg_match_all( '/\X/u', $string, $dummy /*needed for PHP 5.3*/ ) ) ) {
|
||||
if ( ! $test_strwidth || ( $test_strwidth & 2 ) ) {
|
||||
return $width + preg_match_all( $eaw_regex, $string, $dummy /*needed for PHP 5.3*/ );
|
||||
}
|
||||
}
|
||||
// Legacy encodings and old PHPs will reach here.
|
||||
if ( function_exists( 'mb_strwidth' ) && ( $encoding || function_exists( 'mb_detect_encoding' ) ) ) {
|
||||
if ( ! $encoding ) {
|
||||
$encoding = mb_detect_encoding( $string, null, true /*strict*/ );
|
||||
}
|
||||
$width = $encoding ? mb_strwidth( $string, $encoding ) : mb_strwidth( $string ); // mbstring funcs can fail if given `$encoding` arg that evals to false.
|
||||
if ( 'UTF-8' === $encoding ) {
|
||||
// Subtract combining characters.
|
||||
$width -= preg_match_all( $m_regex, $string, $dummy /*needed for PHP 5.3*/ );
|
||||
}
|
||||
if ( ! $test_strwidth || ( $test_strwidth & 4 ) ) {
|
||||
return $width;
|
||||
}
|
||||
}
|
||||
return safe_strlen( $string, $encoding );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether ICU is modern enough not to flake out.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function can_use_icu() {
|
||||
static $can_use_icu = null;
|
||||
|
||||
if ( null === $can_use_icu ) {
|
||||
// Choosing ICU 54, Unicode 7.0.
|
||||
$can_use_icu = defined( 'INTL_ICU_VERSION' ) && version_compare( INTL_ICU_VERSION, '54.1', '>=' ) && function_exists( 'grapheme_strlen' ) && function_exists( 'grapheme_substr' );
|
||||
}
|
||||
|
||||
return $can_use_icu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether PCRE Unicode extended grapheme cluster '\X' is available for use.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function can_use_pcre_x() {
|
||||
static $can_use_pcre_x = null;
|
||||
|
||||
if ( null === $can_use_pcre_x ) {
|
||||
// '\X' introduced (as Unicde extended grapheme cluster) in PCRE 8.32 - see https://vcs.pcre.org/pcre/code/tags/pcre-8.32/ChangeLog?view=markup line 53.
|
||||
// Older versions of PCRE were bundled with PHP <= 5.3.23 & <= 5.4.13.
|
||||
$pcre_version = substr( PCRE_VERSION, 0, strspn( PCRE_VERSION, '0123456789.' ) ); // Remove any trailing date stuff.
|
||||
$can_use_pcre_x = version_compare( $pcre_version, '8.32', '>=' ) && false !== @preg_match( '/\X/u', '' );
|
||||
}
|
||||
|
||||
return $can_use_pcre_x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the regexs generated from Unicode data.
|
||||
*
|
||||
* @param string $idx Optional. Return a specific regex only. Default null.
|
||||
* @return array|string Returns keyed array if not given $idx or $idx doesn't exist, otherwise the specific regex string.
|
||||
*/
|
||||
function get_unicode_regexs( $idx = null ) {
|
||||
static $eaw_regex; // East Asian Width regex. Characters that count as 2 characters as they're "wide" or "fullwidth". See http://www.unicode.org/reports/tr11/tr11-19.html
|
||||
static $m_regex; // Mark characters regex (Unicode property "M") - mark combining "Mc", mark enclosing "Me" and mark non-spacing "Mn" chars that should be ignored for spacing purposes.
|
||||
if ( null === $eaw_regex ) {
|
||||
// Load both regexs generated from Unicode data.
|
||||
require __DIR__ . '/unicode/regex.php';
|
||||
}
|
||||
|
||||
if ( null !== $idx ) {
|
||||
if ( 'eaw' === $idx ) {
|
||||
return $eaw_regex;
|
||||
}
|
||||
if ( 'm' === $idx ) {
|
||||
return $m_regex;
|
||||
}
|
||||
}
|
||||
|
||||
return array( $eaw_regex, $m_regex, );
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\notify;
|
||||
|
||||
use cli\Notify;
|
||||
use cli\Streams;
|
||||
|
||||
/**
|
||||
* A Notifer that displays a string of periods.
|
||||
*/
|
||||
class Dots extends Notify {
|
||||
protected $_dots;
|
||||
protected $_format = '{:msg}{:dots} ({:elapsed}, {:speed}/s)';
|
||||
protected $_iteration;
|
||||
|
||||
/**
|
||||
* Instatiates a Notification object.
|
||||
*
|
||||
* @param string $msg The text to display next to the Notifier.
|
||||
* @param int $dots The number of dots to iterate through.
|
||||
* @param int $interval The interval in milliseconds between updates.
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($msg, $dots = 3, $interval = 100) {
|
||||
parent::__construct($msg, $interval);
|
||||
$this->_dots = (int)$dots;
|
||||
|
||||
if ($this->_dots <= 0) {
|
||||
throw new \InvalidArgumentException('Dot count out of range, must be positive.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the correct number of dots to `STDOUT` with the time elapsed and
|
||||
* tick speed.
|
||||
*
|
||||
* @param boolean $finish `true` if this was called from
|
||||
* `cli\Notify::finish()`, `false` otherwise.
|
||||
* @see cli\out_padded()
|
||||
* @see cli\Notify::formatTime()
|
||||
* @see cli\Notify::speed()
|
||||
*/
|
||||
public function display($finish = false) {
|
||||
$repeat = $this->_dots;
|
||||
if (!$finish) {
|
||||
$repeat = $this->_iteration++ % $repeat;
|
||||
}
|
||||
|
||||
$msg = $this->_message;
|
||||
$dots = str_pad(str_repeat('.', $repeat), $this->_dots);
|
||||
$speed = number_format(round($this->speed()));
|
||||
$elapsed = $this->formatTime($this->elapsed());
|
||||
|
||||
Streams::out_padded($this->_format, compact('msg', 'dots', 'speed', 'elapsed'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\notify;
|
||||
|
||||
use cli\Notify;
|
||||
use cli\Streams;
|
||||
|
||||
/**
|
||||
* The `Spinner` Notifier displays an ASCII spinner.
|
||||
*/
|
||||
class Spinner extends Notify {
|
||||
protected $_chars = '-\|/';
|
||||
protected $_format = '{:msg} {:char} ({:elapsed}, {:speed}/s)';
|
||||
protected $_iteration = 0;
|
||||
|
||||
/**
|
||||
* Prints the current spinner position to `STDOUT` with the time elapsed
|
||||
* and tick speed.
|
||||
*
|
||||
* @param boolean $finish `true` if this was called from
|
||||
* `cli\Notify::finish()`, `false` otherwise.
|
||||
* @see cli\out_padded()
|
||||
* @see cli\Notify::formatTime()
|
||||
* @see cli\Notify::speed()
|
||||
*/
|
||||
public function display($finish = false) {
|
||||
$msg = $this->_message;
|
||||
$idx = $this->_iteration++ % strlen($this->_chars);
|
||||
$char = $this->_chars[$idx];
|
||||
$speed = number_format(round($this->speed()));
|
||||
$elapsed = $this->formatTime($this->elapsed());
|
||||
|
||||
Streams::out_padded($this->_format, compact('msg', 'char', 'elapsed', 'speed'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\progress;
|
||||
|
||||
use cli;
|
||||
use cli\Notify;
|
||||
use cli\Progress;
|
||||
use cli\Shell;
|
||||
use cli\Streams;
|
||||
|
||||
/**
|
||||
* Displays a progress bar spanning the entire shell.
|
||||
*
|
||||
* Basic format:
|
||||
*
|
||||
* ^MSG PER% [======================= ] 00:00 / 00:00$
|
||||
*/
|
||||
class Bar extends Progress {
|
||||
protected $_bars = '=>';
|
||||
protected $_formatMessage = '{:msg} {:percent}% [';
|
||||
protected $_formatTiming = '] {:elapsed} / {:estimated}';
|
||||
protected $_format = '{:msg}{:bar}{:timing}';
|
||||
|
||||
/**
|
||||
* Prints the progress bar to the screen with percent complete, elapsed time
|
||||
* and estimated total time.
|
||||
*
|
||||
* @param boolean $finish `true` if this was called from
|
||||
* `cli\Notify::finish()`, `false` otherwise.
|
||||
* @see cli\out()
|
||||
* @see cli\Notify::formatTime()
|
||||
* @see cli\Notify::elapsed()
|
||||
* @see cli\Progress::estimated();
|
||||
* @see cli\Progress::percent()
|
||||
* @see cli\Shell::columns()
|
||||
*/
|
||||
public function display($finish = false) {
|
||||
$_percent = $this->percent();
|
||||
|
||||
$percent = str_pad(floor($_percent * 100), 3);
|
||||
$msg = $this->_message;
|
||||
$msg = Streams::render($this->_formatMessage, compact('msg', 'percent'));
|
||||
|
||||
$estimated = $this->formatTime($this->estimated());
|
||||
$elapsed = str_pad($this->formatTime($this->elapsed()), strlen($estimated));
|
||||
$timing = Streams::render($this->_formatTiming, compact('elapsed', 'estimated'));
|
||||
|
||||
$size = Shell::columns();
|
||||
$size -= strlen($msg . $timing);
|
||||
if ( $size < 0 ) {
|
||||
$size = 0;
|
||||
}
|
||||
|
||||
$bar = str_repeat($this->_bars[0], floor($_percent * $size)) . $this->_bars[1];
|
||||
// substr is needed to trim off the bar cap at 100%
|
||||
$bar = substr(str_pad($bar, $size, ' '), 0, $size);
|
||||
|
||||
Streams::out($this->_format, compact('msg', 'bar', 'timing'));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method augments the base definition from cli\Notify to optionally
|
||||
* allow passing a new message.
|
||||
*
|
||||
* @param int $increment The amount to increment by.
|
||||
* @param string $msg The text to display next to the Notifier. (optional)
|
||||
* @see cli\Notify::tick()
|
||||
*/
|
||||
public function tick($increment = 1, $msg = null) {
|
||||
if ($msg) {
|
||||
$this->_message = $msg;
|
||||
}
|
||||
Notify::tick($increment);
|
||||
}
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\table;
|
||||
|
||||
use cli\Colors;
|
||||
use cli\Shell;
|
||||
|
||||
/**
|
||||
* The ASCII renderer renders tables with ASCII borders.
|
||||
*/
|
||||
class Ascii extends Renderer {
|
||||
protected $_characters = array(
|
||||
'corner' => '+',
|
||||
'line' => '-',
|
||||
'border' => '|',
|
||||
'padding' => ' ',
|
||||
);
|
||||
protected $_border = null;
|
||||
protected $_constraintWidth = null;
|
||||
protected $_pre_colorized = false;
|
||||
|
||||
/**
|
||||
* Set the widths of each column in the table.
|
||||
*
|
||||
* @param array $widths The widths of the columns.
|
||||
* @param bool $fallback Whether to use these values as fallback only.
|
||||
*/
|
||||
public function setWidths(array $widths, $fallback = false) {
|
||||
if ($fallback) {
|
||||
foreach ( $this->_widths as $index => $value ) {
|
||||
$widths[$index] = $value;
|
||||
}
|
||||
}
|
||||
$this->_widths = $widths;
|
||||
|
||||
if ( is_null( $this->_constraintWidth ) ) {
|
||||
$this->_constraintWidth = (int) Shell::columns();
|
||||
}
|
||||
$col_count = count( $widths );
|
||||
$col_borders_count = $col_count ? ( ( $col_count - 1 ) * strlen( $this->_characters['border'] ) ) : 0;
|
||||
$table_borders_count = strlen( $this->_characters['border'] ) * 2;
|
||||
$col_padding_count = $col_count * strlen( $this->_characters['padding'] ) * 2;
|
||||
$max_width = $this->_constraintWidth - $col_borders_count - $table_borders_count - $col_padding_count;
|
||||
|
||||
if ( $widths && $max_width && array_sum( $widths ) > $max_width ) {
|
||||
|
||||
$avg = floor( $max_width / count( $widths ) );
|
||||
$resize_widths = array();
|
||||
$extra_width = 0;
|
||||
foreach( $widths as $width ) {
|
||||
if ( $width > $avg ) {
|
||||
$resize_widths[] = $width;
|
||||
} else {
|
||||
$extra_width = $extra_width + ( $avg - $width );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $resize_widths ) && $extra_width ) {
|
||||
$avg_extra_width = floor( $extra_width / count( $resize_widths ) );
|
||||
foreach( $widths as &$width ) {
|
||||
if ( in_array( $width, $resize_widths ) ) {
|
||||
$width = $avg + $avg_extra_width;
|
||||
array_shift( $resize_widths );
|
||||
// Last item gets the cake
|
||||
if ( empty( $resize_widths ) ) {
|
||||
$width = 0; // Zero it so not in sum.
|
||||
$width = $max_width - array_sum( $widths );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->_widths = $widths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the constraint width for the table
|
||||
*
|
||||
* @param int $constraintWidth
|
||||
*/
|
||||
public function setConstraintWidth( $constraintWidth ) {
|
||||
$this->_constraintWidth = $constraintWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the characters used for rendering the Ascii table.
|
||||
*
|
||||
* The keys `corner`, `line` and `border` are used in rendering.
|
||||
*
|
||||
* @param $characters array Characters used in rendering.
|
||||
*/
|
||||
public function setCharacters(array $characters) {
|
||||
$this->_characters = array_merge($this->_characters, $characters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a border for the top and bottom and separating the headers from the
|
||||
* table rows.
|
||||
*
|
||||
* @return string The table border.
|
||||
*/
|
||||
public function border() {
|
||||
if (!isset($this->_border)) {
|
||||
$this->_border = $this->_characters['corner'];
|
||||
foreach ($this->_widths as $width) {
|
||||
$this->_border .= str_repeat($this->_characters['line'], $width + 2);
|
||||
$this->_border .= $this->_characters['corner'];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_border;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a row for output.
|
||||
*
|
||||
* @param array $row The table row.
|
||||
* @return string The formatted table row.
|
||||
*/
|
||||
public function row( array $row ) {
|
||||
|
||||
$extra_row_count = 0;
|
||||
|
||||
if ( count( $row ) > 0 ) {
|
||||
$extra_rows = array_fill( 0, count( $row ), array() );
|
||||
|
||||
foreach( $row as $col => $value ) {
|
||||
|
||||
$value = str_replace( array( "\r\n", "\n" ), ' ', $value );
|
||||
|
||||
$col_width = $this->_widths[ $col ];
|
||||
$encoding = function_exists( 'mb_detect_encoding' ) ? mb_detect_encoding( $value, null, true /*strict*/ ) : false;
|
||||
$original_val_width = Colors::width( $value, self::isPreColorized( $col ), $encoding );
|
||||
if ( $col_width && $original_val_width > $col_width ) {
|
||||
$row[ $col ] = \cli\safe_substr( $value, 0, $col_width, true /*is_width*/, $encoding );
|
||||
$value = \cli\safe_substr( $value, \cli\safe_strlen( $row[ $col ], $encoding ), null /*length*/, false /*is_width*/, $encoding );
|
||||
$i = 0;
|
||||
do {
|
||||
$extra_value = \cli\safe_substr( $value, 0, $col_width, true /*is_width*/, $encoding );
|
||||
$val_width = Colors::width( $extra_value, self::isPreColorized( $col ), $encoding );
|
||||
if ( $val_width ) {
|
||||
$extra_rows[ $col ][] = $extra_value;
|
||||
$value = \cli\safe_substr( $value, \cli\safe_strlen( $extra_value, $encoding ), null /*length*/, false /*is_width*/, $encoding );
|
||||
$i++;
|
||||
if ( $i > $extra_row_count ) {
|
||||
$extra_row_count = $i;
|
||||
}
|
||||
}
|
||||
} while( $value );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$row = array_map(array($this, 'padColumn'), $row, array_keys($row));
|
||||
array_unshift($row, ''); // First border
|
||||
array_push($row, ''); // Last border
|
||||
|
||||
$ret = join($this->_characters['border'], $row);
|
||||
if ( $extra_row_count ) {
|
||||
foreach( $extra_rows as $col => $col_values ) {
|
||||
while( count( $col_values ) < $extra_row_count ) {
|
||||
$col_values[] = '';
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
$row_values = array();
|
||||
$has_more = false;
|
||||
foreach( $extra_rows as $col => &$col_values ) {
|
||||
$row_values[ $col ] = array_shift( $col_values );
|
||||
if ( count( $col_values ) ) {
|
||||
$has_more = true;
|
||||
}
|
||||
}
|
||||
|
||||
$row_values = array_map(array($this, 'padColumn'), $row_values, array_keys($row_values));
|
||||
array_unshift($row_values, ''); // First border
|
||||
array_push($row_values, ''); // Last border
|
||||
|
||||
$ret .= PHP_EOL . join($this->_characters['border'], $row_values);
|
||||
} while( $has_more );
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function padColumn($content, $column) {
|
||||
return $this->_characters['padding'] . Colors::pad( $content, $this->_widths[ $column ], $this->isPreColorized( $column ) ) . $this->_characters['padding'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether items are pre-colorized.
|
||||
*
|
||||
* @param bool|array $colorized A boolean to set all columns in the table as pre-colorized, or an array of booleans keyed by column index (number) to set individual columns as pre-colorized.
|
||||
*/
|
||||
public function setPreColorized( $pre_colorized ) {
|
||||
$this->_pre_colorized = $pre_colorized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a column pre-colorized?
|
||||
*
|
||||
* @param int $column Column index to check.
|
||||
* @return bool True if whole table is marked as pre-colorized, or if the individual column is pre-colorized; else false.
|
||||
*/
|
||||
public function isPreColorized( $column ) {
|
||||
if ( is_bool( $this->_pre_colorized ) ) {
|
||||
return $this->_pre_colorized;
|
||||
}
|
||||
if ( is_array( $this->_pre_colorized ) && isset( $this->_pre_colorized[ $column ] ) ) {
|
||||
return $this->_pre_colorized[ $column ];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\table;
|
||||
|
||||
/**
|
||||
* Table renderers are used to change how a table is displayed.
|
||||
*/
|
||||
abstract class Renderer {
|
||||
protected $_widths = array();
|
||||
|
||||
public function __construct(array $widths = array()) {
|
||||
$this->setWidths($widths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the widths of each column in the table.
|
||||
*
|
||||
* @param array $widths The widths of the columns.
|
||||
* @param bool $fallback Whether to use these values as fallback only.
|
||||
*/
|
||||
public function setWidths(array $widths, $fallback = false) {
|
||||
if ($fallback) {
|
||||
foreach ( $this->_widths as $index => $value ) {
|
||||
$widths[$index] = $value;
|
||||
}
|
||||
}
|
||||
$this->_widths = $widths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a border for the top and bottom and separating the headers from the
|
||||
* table rows.
|
||||
*
|
||||
* @return string The table border.
|
||||
*/
|
||||
public function border() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a row for output.
|
||||
*
|
||||
* @param array $row The table row.
|
||||
* @return string The formatted table row.
|
||||
*/
|
||||
abstract public function row(array $row);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author James Logsdon <dwarf@girsbrain.org>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\table;
|
||||
|
||||
/**
|
||||
* The tabular renderer is used for displaying data in a tabular format.
|
||||
*/
|
||||
class Tabular extends Renderer {
|
||||
/**
|
||||
* Renders a row for output.
|
||||
*
|
||||
* @param array $row The table row.
|
||||
* @return string The formatted table row.
|
||||
*/
|
||||
public function row(array $row) {
|
||||
return implode("\t", array_values($row));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author Ryan Sullivan <rsullivan@connectstudios.com>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\tree;
|
||||
|
||||
/**
|
||||
* The ASCII renderer renders trees with ASCII lines.
|
||||
*/
|
||||
class Ascii extends Renderer {
|
||||
|
||||
/**
|
||||
* @param array $tree
|
||||
* @return string
|
||||
*/
|
||||
public function render(array $tree)
|
||||
{
|
||||
$output = '';
|
||||
|
||||
$treeIterator = new \RecursiveTreeIterator(
|
||||
new \RecursiveArrayIterator($tree),
|
||||
\RecursiveTreeIterator::SELF_FIRST
|
||||
);
|
||||
|
||||
foreach ($treeIterator as $val)
|
||||
{
|
||||
$output .= $val . "\n";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author Ryan Sullivan <rsullivan@connectstudios.com>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\tree;
|
||||
|
||||
/**
|
||||
* The ASCII renderer renders trees with ASCII lines.
|
||||
*/
|
||||
class Markdown extends Renderer {
|
||||
|
||||
/**
|
||||
* How many spaces to indent by
|
||||
* @var int
|
||||
*/
|
||||
protected $_padding = 2;
|
||||
|
||||
/**
|
||||
* @param int $padding Optional. Default 2.
|
||||
*/
|
||||
function __construct($padding = null)
|
||||
{
|
||||
if ($padding)
|
||||
{
|
||||
$this->_padding = $padding;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the tree
|
||||
*
|
||||
* @param array $tree
|
||||
* @param int $level Optional
|
||||
* @return string
|
||||
*/
|
||||
public function render(array $tree, $level = 0)
|
||||
{
|
||||
$output = '';
|
||||
|
||||
foreach ($tree as $label => $next)
|
||||
{
|
||||
|
||||
if (is_string($next))
|
||||
{
|
||||
$label = $next;
|
||||
}
|
||||
|
||||
// Output the label
|
||||
$output .= sprintf("%s- %s\n", str_repeat(' ', $level * $this->_padding), $label);
|
||||
|
||||
// Next level
|
||||
if (is_array($next))
|
||||
{
|
||||
$output .= $this->render($next, $level + 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP Command Line Tools
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this package in the file LICENSE.
|
||||
*
|
||||
* @author Ryan Sullivan <rsullivan@connectstudios.com>
|
||||
* @copyright 2010 James Logsdom (http://girsbrain.org)
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
|
||||
namespace cli\tree;
|
||||
|
||||
/**
|
||||
* Tree renderers are used to change how a tree is displayed.
|
||||
*/
|
||||
abstract class Renderer {
|
||||
|
||||
/**
|
||||
* @param array $tree
|
||||
* @return string|null
|
||||
*/
|
||||
abstract public function render(array $tree);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
// Generated by "gen_east_asian_width.php" from "http://www.unicode.org/Public/10.0.0/ucd/EastAsianWidth.txt".
|
||||
$eaw_regex = '/\xe1(?:\x84[\x80-\xbf]|\x85[\x80-\x9f])|\xe2(?:\x8c[\x9a\x9b\xa9\xaa]|\x8f[\xa9-\xac\xb0\xb3]|\x97[\xbd\xbe]|\x98[\x94\x95]|\x99[\x88-\x93\xbf]|\x9a[\x93\xa1\xaa\xab\xbd\xbe]|\x9b[\x84\x85\x8e\x94\xaa\xb2\xb3\xb5\xba\xbd]|\x9c[\x85\x8a\x8b\xa8]|\x9d[\x8c\x8e\x93-\x95\x97]|\x9e[\x95-\x97\xb0\xbf]|\xac[\x9b\x9c]|\xad[\x90\x95]|\xba[\x80-\x99\x9b-\xbf]|\xbb[\x80-\xb3]|[\xbc-\xbe][\x80-\xbf]|\xbf[\x80-\x95\xb0-\xbb])|\xe3(?:\x80[\x80-\xbe]|\x81[\x81-\xbf]|\x82[\x80-\x96\x99-\xbf]|\x83[\x80-\xbf]|\x84[\x85-\xae\xb1-\xbf]|\x85[\x80-\xbf]|\x86[\x80-\x8e\x90-\xba]|\x87[\x80-\xa3\xb0-\xbf]|\x88[\x80-\x9e\xa0-\xbf]|\x89[\x80-\x87\x90-\xbf]|\x8a[\x80-\xbf]|\x8b[\x80-\xbe]|[\x8c-\xbf][\x80-\xbf])|\xe4(?:[\x80-\xb6][\x80-\xbf]|[\xb8-\xbf][\x80-\xbf])|[\xe5-\xe9][\x80-\xbf][\x80-\xbf]|\xea(?:[\x80-\x91][\x80-\xbf]|\x92[\x80-\x8c\x90-\xbf]|\x93[\x80-\x86]|\xa5[\xa0-\xbc]|[\xb0-\xbf][\x80-\xbf])|[\xeb\xec][\x80-\xbf][\x80-\xbf]|\xed(?:[\x80-\x9d][\x80-\xbf]|\x9e[\x80-\xa3])|\xef(?:[\xa4-\xab][\x80-\xbf]|\xb8[\x90-\x99\xb0-\xbf]|\xb9[\x80-\x92\x94-\xa6\xa8-\xab]|\xbc[\x81-\xbf]|\xbd[\x80-\xa0]|\xbf[\xa0-\xa6])|\xf0(?:\x96\xbf[\xa0\xa1]|\x97[\x80-\xbf][\x80-\xbf]|\x98(?:[\x80-\x9e][\x80-\xbf]|\x9f[\x80-\xac]|[\xa0-\xaa][\x80-\xbf]|\xab[\x80-\xb2])|\x9b(?:[\x80-\x83][\x80-\xbf]|\x84[\x80-\x9e]|\x85[\xb0-\xbf]|[\x86-\x8a][\x80-\xbf]|\x8b[\x80-\xbb])|\x9f(?:\x80\x84|\x83\x8f|\x86[\x8e\x91-\x9a]|\x88[\x80-\x82\x90-\xbb]|\x89[\x80-\x88\x90\x91\xa0-\xa5]|\x8c[\x80-\xa0\xad-\xb5\xb7-\xbf]|\x8d[\x80-\xbc\xbe\xbf]|\x8e[\x80-\x93\xa0-\xbf]|\x8f[\x80-\x8a\x8f-\x93\xa0-\xb0\xb4\xb8-\xbf]|\x90[\x80-\xbe]|\x91[\x80\x82-\xbf]|\x92[\x80-\xbf]|\x93[\x80-\xbc\xbf]|\x94[\x80-\xbd]|\x95[\x8b-\x8e\x90-\xa7\xba]|\x96[\x95\x96\xa4]|\x97[\xbb-\xbf]|\x98[\x80-\xbf]|\x99[\x80-\x8f]|\x9a[\x80-\xbf]|\x9b[\x80-\x85\x8c\x90-\x92\xab\xac\xb4-\xb8]|\xa4[\x90-\xbe]|\xa5[\x80-\x8c\x90-\xab]|\xa6[\x80-\x97]|\xa7[\x80\x90-\xa6])|[\xa0-\xae][\x80-\xbf][\x80-\xbf]|\xaf(?:[\x80-\xbe][\x80-\xbf]|\xbf[\x80-\xbd])|[\xb0-\xbe][\x80-\xbf][\x80-\xbf]|\xbf(?:[\x80-\xbe][\x80-\xbf]|\xbf[\x80-\xbd]))/'; // 181738 code points.
|
||||
|
||||
// Generated by "gen_cat_regex_alts.php" from "http://www.unicode.org/Public/10.0.0/ucd/UnicodeData.txt".
|
||||
$m_regex = '/\xcc[\x80-\xbf]|\xcd[\x80-\xaf]|\xd2[\x83-\x89]|\xd6[\x91-\xbd\xbf]|\xd7[\x81\x82\x84\x85\x87]|\xd8[\x90-\x9a]|\xd9[\x8b-\x9f\xb0]|\xdb[\x96-\x9c\x9f-\xa4\xa7\xa8\xaa-\xad]|\xdc[\x91\xb0-\xbf]|\xdd[\x80-\x8a]|\xde[\xa6-\xb0]|\xdf[\xab-\xb3]|\xe0(?:\xa0[\x96-\x99\x9b-\xa3\xa5-\xa7\xa9-\xad]|\xa1[\x99-\x9b]|\xa3[\x94-\xa1\xa3-\xbf]|\xa4[\x80-\x83\xba-\xbc\xbe\xbf]|\xa5[\x80-\x8f\x91-\x97\xa2\xa3]|\xa6[\x81-\x83\xbc\xbe\xbf]|\xa7[\x80-\x84\x87\x88\x8b-\x8d\x97\xa2\xa3]|\xa8[\x81-\x83\xbc\xbe\xbf]|\xa9[\x80-\x82\x87\x88\x8b-\x8d\x91\xb0\xb1\xb5]|\xaa[\x81-\x83\xbc\xbe\xbf]|\xab[\x80-\x85\x87-\x89\x8b-\x8d\xa2\xa3\xba-\xbf]|\xac[\x81-\x83\xbc\xbe\xbf]|\xad[\x80-\x84\x87\x88\x8b-\x8d\x96\x97\xa2\xa3]|\xae[\x82\xbe\xbf]|\xaf[\x80-\x82\x86-\x88\x8a-\x8d\x97]|\xb0[\x80-\x83\xbe\xbf]|\xb1[\x80-\x84\x86-\x88\x8a-\x8d\x95\x96\xa2\xa3]|\xb2[\x81-\x83\xbc\xbe\xbf]|\xb3[\x80-\x84\x86-\x88\x8a-\x8d\x95\x96\xa2\xa3]|\xb4[\x80-\x83\xbb\xbc\xbe\xbf]|\xb5[\x80-\x84\x86-\x88\x8a-\x8d\x97\xa2\xa3]|\xb6[\x82\x83]|\xb7[\x8a\x8f-\x94\x96\x98-\x9f\xb2\xb3]|\xb8[\xb1\xb4-\xba]|\xb9[\x87-\x8e]|\xba[\xb1\xb4-\xb9\xbb\xbc]|\xbb[\x88-\x8d]|\xbc[\x98\x99\xb5\xb7\xb9\xbe\xbf]|\xbd[\xb1-\xbf]|\xbe[\x80-\x84\x86\x87\x8d-\x97\x99-\xbc]|\xbf\x86)|\xe1(?:\x80[\xab-\xbe]|\x81[\x96-\x99\x9e-\xa0\xa2-\xa4\xa7-\xad\xb1-\xb4]|\x82[\x82-\x8d\x8f\x9a-\x9d]|\x8d[\x9d-\x9f]|\x9c[\x92-\x94\xb2-\xb4]|\x9d[\x92\x93\xb2\xb3]|\x9e[\xb4-\xbf]|\x9f[\x80-\x93\x9d]|\xa0[\x8b-\x8d]|\xa2[\x85\x86\xa9]|\xa4[\xa0-\xab\xb0-\xbb]|\xa8[\x97-\x9b]|\xa9[\x95-\x9e\xa0-\xbc\xbf]|\xaa[\xb0-\xbe]|\xac[\x80-\x84\xb4-\xbf]|\xad[\x80-\x84\xab-\xb3]|\xae[\x80-\x82\xa1-\xad]|\xaf[\xa6-\xb3]|\xb0[\xa4-\xb7]|\xb3[\x90-\x92\x94-\xa8\xad\xb2-\xb4\xb7-\xb9]|\xb7[\x80-\xb9\xbb-\xbf])|\xe2(?:\x83[\x90-\xb0]|\xb3[\xaf-\xb1]|\xb5\xbf|\xb7[\xa0-\xbf])|\xe3(?:\x80[\xaa-\xaf]|\x82[\x99\x9a])|\xea(?:\x99[\xaf-\xb2\xb4-\xbd]|\x9a[\x9e\x9f]|\x9b[\xb0\xb1]|\xa0[\x82\x86\x8b\xa3-\xa7]|\xa2[\x80\x81\xb4-\xbf]|\xa3[\x80-\x85\xa0-\xb1]|\xa4[\xa6-\xad]|\xa5[\x87-\x93]|\xa6[\x80-\x83\xb3-\xbf]|\xa7[\x80\xa5]|\xa8[\xa9-\xb6]|\xa9[\x83\x8c\x8d\xbb-\xbd]|\xaa[\xb0\xb2-\xb4\xb7\xb8\xbe\xbf]|\xab[\x81\xab-\xaf\xb5\xb6]|\xaf[\xa3-\xaa\xac\xad])|\xef(?:\xac\x9e|\xb8[\x80-\x8f\xa0-\xaf])|\xf0(?:\x90(?:\x87\xbd|\x8b\xa0|\x8d[\xb6-\xba]|\xa8[\x81-\x83\x85\x86\x8c-\x8f\xb8-\xba\xbf]|\xab[\xa5\xa6])|\x91(?:\x80[\x80-\x82\xb8-\xbf]|\x81[\x80-\x86\xbf]|\x82[\x80-\x82\xb0-\xba]|\x84[\x80-\x82\xa7-\xb4]|\x85\xb3|\x86[\x80-\x82\xb3-\xbf]|\x87[\x80\x8a-\x8c]|\x88[\xac-\xb7\xbe]|\x8b[\x9f-\xaa]|\x8c[\x80-\x83\xbc\xbe\xbf]|\x8d[\x80-\x84\x87\x88\x8b-\x8d\x97\xa2\xa3\xa6-\xac\xb0-\xb4]|\x90[\xb5-\xbf]|\x91[\x80-\x86]|\x92[\xb0-\xbf]|\x93[\x80-\x83]|\x96[\xaf-\xb5\xb8-\xbf]|\x97[\x80\x9c\x9d]|\x98[\xb0-\xbf]|\x99\x80|\x9a[\xab-\xb7]|\x9c[\x9d-\xab]|\xa8[\x81-\x8a\xb3-\xb9\xbb-\xbe]|\xa9[\x87\x91-\x9b]|\xaa[\x8a-\x99]|\xb0[\xaf-\xb6\xb8-\xbf]|\xb2[\x92-\xa7\xa9-\xb6]|\xb4[\xb1-\xb6\xba\xbc\xbd\xbf]|\xb5[\x80-\x85\x87])|\x96(?:\xab[\xb0-\xb4]|\xac[\xb0-\xb6]|\xbd[\x91-\xbe]|\xbe[\x8f-\x92])|\x9b\xb2[\x9d\x9e]|\x9d(?:\x85[\xa5-\xa9\xad-\xb2\xbb-\xbf]|\x86[\x80-\x82\x85-\x8b\xaa-\xad]|\x89[\x82-\x84]|\xa8[\x80-\xb6\xbb-\xbf]|\xa9[\x80-\xac\xb5]|\xaa[\x84\x9b-\x9f\xa1-\xaf])|\x9e(?:\x80[\x80-\x86\x88-\x98\x9b-\xa1\xa3\xa4\xa6-\xaa]|\xa3[\x90-\x96]|\xa5[\x84-\x8a]))|\xf3\xa0(?:[\x84-\x86][\x80-\xbf]|\x87[\x80-\xaf])/'; // 2177 code points.
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<phpunit
|
||||
bootstrap="tests/bootstrap.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite>
|
||||
<directory prefix="test-" suffix=".php">tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
error_reporting(-1);
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
|
||||
$args = new cli\Arguments(array(
|
||||
'flags' => array(
|
||||
'verbose' => array(
|
||||
'description' => 'Turn on verbose mode',
|
||||
'aliases' => array('v')
|
||||
),
|
||||
'c' => array(
|
||||
'description' => 'A counter to test stackable',
|
||||
'stackable' => true
|
||||
)
|
||||
),
|
||||
'options' => array(
|
||||
'user' => array(
|
||||
'description' => 'Username for authentication',
|
||||
'aliases' => array('u')
|
||||
)
|
||||
),
|
||||
'strict' => true
|
||||
));
|
||||
|
||||
try {
|
||||
$args->parse();
|
||||
} catch (cli\InvalidArguments $e) {
|
||||
echo $e->getMessage() . "\n\n";
|
||||
}
|
||||
|
||||
print_r($args->getArguments());
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
require dirname( dirname( __FILE__ ) ) . '/lib/cli/cli.php';
|
||||
|
||||
/**
|
||||
* Compatibility with PHPUnit 6+
|
||||
*/
|
||||
if ( class_exists( 'PHPUnit\Runner\Version' ) ) {
|
||||
require_once dirname( __FILE__ ) . '/phpunit6-compat.php';
|
||||
}
|
||||
|
||||
function cli_autoload( $className ) {
|
||||
$className = ltrim($className, '\\');
|
||||
$fileName = '';
|
||||
$namespace = '';
|
||||
if ($lastNsPos = strrpos($className, '\\')) {
|
||||
$namespace = substr($className, 0, $lastNsPos);
|
||||
$className = substr($className, $lastNsPos + 1);
|
||||
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
|
||||
|
||||
if ( 'cli' !== substr( $fileName, 0, 3 ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
require dirname( dirname( __FILE__ ) ) . '/lib/' . $fileName;
|
||||
}
|
||||
|
||||
spl_autoload_register( 'cli_autoload' );
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
// From core "tests/phpunit/includes/phpunit6-compat.php" without `getTickets()` (see https://core.trac.wordpress.org/ticket/39822).
|
||||
|
||||
if ( class_exists( 'PHPUnit\Runner\Version' ) && version_compare( PHPUnit\Runner\Version::id(), '6.0', '>=' ) ) {
|
||||
|
||||
class_alias( 'PHPUnit\Framework\TestCase', 'PHPUnit_Framework_TestCase' );
|
||||
class_alias( 'PHPUnit\Framework\Exception', 'PHPUnit_Framework_Exception' );
|
||||
class_alias( 'PHPUnit\Framework\ExpectationFailedException', 'PHPUnit_Framework_ExpectationFailedException' );
|
||||
class_alias( 'PHPUnit\Framework\Error\Notice', 'PHPUnit_Framework_Error_Notice' );
|
||||
class_alias( 'PHPUnit\Framework\Error\Warning', 'PHPUnit_Framework_Error_Warning' );
|
||||
class_alias( 'PHPUnit\Framework\Test', 'PHPUnit_Framework_Test' );
|
||||
class_alias( 'PHPUnit\Framework\Warning', 'PHPUnit_Framework_Warning' );
|
||||
class_alias( 'PHPUnit\Framework\AssertionFailedError', 'PHPUnit_Framework_AssertionFailedError' );
|
||||
class_alias( 'PHPUnit\Framework\TestSuite', 'PHPUnit_Framework_TestSuite' );
|
||||
class_alias( 'PHPUnit\Framework\TestListener', 'PHPUnit_Framework_TestListener' );
|
||||
class_alias( 'PHPUnit\Util\GlobalState', 'PHPUnit_Util_GlobalState' );
|
||||
class_alias( 'PHPUnit\Util\Getopt', 'PHPUnit_Util_Getopt' );
|
||||
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
use cli\Arguments;
|
||||
|
||||
/**
|
||||
* Class TestArguments
|
||||
* @todo add more tests to increase coverage
|
||||
*
|
||||
* @backupGlobals enabled
|
||||
*/
|
||||
class TestArguments extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* Array of expected settings
|
||||
* @var array
|
||||
*/
|
||||
protected $settings = null;
|
||||
|
||||
/**
|
||||
* Array of flags
|
||||
* @var array
|
||||
*/
|
||||
protected $flags = null;
|
||||
|
||||
/**
|
||||
* Array of expected options
|
||||
* @var array
|
||||
*/
|
||||
protected $options = null;
|
||||
|
||||
/**
|
||||
* Clear the $_SERVER['argv'] array
|
||||
*/
|
||||
public static function clearArgv()
|
||||
{
|
||||
$_SERVER['argv'] = array();
|
||||
$_SERVER['argc'] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more element(s) at the end of the $_SERVER['argv'] array
|
||||
*
|
||||
* @param array $args: value(s) to add to the argv array
|
||||
*/
|
||||
public static function pushToArgv($args)
|
||||
{
|
||||
if (is_string($args)) {
|
||||
$args = explode(' ', $args);
|
||||
}
|
||||
|
||||
foreach ($args as $arg) {
|
||||
array_push($_SERVER['argv'], $arg);
|
||||
}
|
||||
|
||||
$_SERVER['argc'] += count($args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up valid flags and options
|
||||
*/
|
||||
public function setUp()
|
||||
{
|
||||
self::clearArgv();
|
||||
self::pushToArgv('my_script.php');
|
||||
|
||||
$this->flags = array(
|
||||
'flag1' => array(
|
||||
'aliases' => 'f',
|
||||
'description' => 'Test flag 1'
|
||||
),
|
||||
'flag2' => array(
|
||||
'description' => 'Test flag 2'
|
||||
)
|
||||
);
|
||||
|
||||
$this->options = array(
|
||||
'option1' => array(
|
||||
'aliases' => 'o',
|
||||
'description' => 'Test option 1'
|
||||
),
|
||||
'option2' => array(
|
||||
'aliases' => array('x', 'y'),
|
||||
'description' => 'Test option 2 with default',
|
||||
'default' => 'some default value'
|
||||
)
|
||||
);
|
||||
|
||||
$this->settings = array(
|
||||
'strict' => true,
|
||||
'flags' => $this->flags,
|
||||
'options' => $this->options
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down fixtures
|
||||
*/
|
||||
public function tearDown()
|
||||
{
|
||||
$this->flags = null;
|
||||
$this->options = null;
|
||||
$this->settings = null;
|
||||
self::clearArgv();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding a flag, getting a flag and getting all flags
|
||||
*/
|
||||
public function testAddFlags()
|
||||
{
|
||||
$args = new cli\Arguments($this->settings);
|
||||
|
||||
$expectedFlags = $this->flags;
|
||||
$expectedFlags['flag1']['default'] = false;
|
||||
$expectedFlags['flag1']['stackable'] = false;
|
||||
$expectedFlags['flag2']['default'] = false;
|
||||
$expectedFlags['flag2']['stackable'] = false;
|
||||
$expectedFlags['flag2']['aliases'] = array();
|
||||
|
||||
$this->assertSame($expectedFlags, $args->getFlags());
|
||||
|
||||
$this->assertSame($expectedFlags['flag1'], $args->getFlag('flag1'));
|
||||
$this->assertSame($expectedFlags['flag1'], $args->getFlag('f'));
|
||||
|
||||
$expectedFlag1Argument = new cli\arguments\Argument('-f');
|
||||
$this->assertSame($expectedFlags['flag1'], $args->getFlag($expectedFlag1Argument));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adding a option, getting a option and getting all options
|
||||
*/
|
||||
public function testAddOptions()
|
||||
{
|
||||
$args = new cli\Arguments($this->settings);
|
||||
|
||||
$expectedOptions = $this->options;
|
||||
$expectedOptions['option1']['default'] = null;
|
||||
|
||||
$this->assertSame($expectedOptions, $args->getOptions());
|
||||
|
||||
$this->assertSame($expectedOptions['option1'], $args->getOption('option1'));
|
||||
$this->assertSame($expectedOptions['option1'], $args->getOption('o'));
|
||||
|
||||
$expectedOption1Argument = new cli\arguments\Argument('-o');
|
||||
$this->assertSame($expectedOptions['option1'], $args->getOption($expectedOption1Argument));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider with valid args and options
|
||||
*
|
||||
* @return array set of args and expected parsed values
|
||||
*/
|
||||
public function settingsWithValidOptions()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
array('-o', 'option_value', '-f'),
|
||||
array('option1' => 'option_value', 'flag1' => true)
|
||||
),
|
||||
array(
|
||||
array('--option1', 'option_value', '--flag1'),
|
||||
array('option1' => 'option_value', 'flag1' => true)
|
||||
),
|
||||
array(
|
||||
array('-f', '--option1', 'option_value'),
|
||||
array('flag1' => true, 'option1' => 'option_value')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider with missing options
|
||||
*
|
||||
* @return array set of args and expected parsed values
|
||||
*/
|
||||
public function settingsWithMissingOptions()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
array('-f', '--option1'),
|
||||
array('flag1' => true, 'option1' => 'Error should be triggered')
|
||||
),
|
||||
array(
|
||||
array('--option1', '-f'),
|
||||
array('option1' => 'Error should be triggered', 'flag1' => true)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider with missing options. The default value should be populated
|
||||
*
|
||||
* @return array set of args and expected parsed values
|
||||
*/
|
||||
public function settingsWithMissingOptionsWithDefault()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
array('-f', '--option2'),
|
||||
array('flag1' => true, 'option2' => 'some default value')
|
||||
),
|
||||
array(
|
||||
array('--option2', '-f'),
|
||||
array('option2' => 'some default value', 'flag1' => true)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function settingsWithNoOptionsWithDefault()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
array(),
|
||||
array('flag1' => false, 'flag2' => false, 'option2' => 'some default value')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic private testParse method.
|
||||
*
|
||||
* @param array $args arguments as they appear in the cli
|
||||
* @param array $expectedValues expected values after parsing
|
||||
*/
|
||||
private function _testParse($cliParams, $expectedValues)
|
||||
{
|
||||
self::pushToArgv($cliParams);
|
||||
|
||||
$args = new cli\Arguments($this->settings);
|
||||
$args->parse();
|
||||
|
||||
foreach ($expectedValues as $name => $value) {
|
||||
if ($args->isFlag($name)) {
|
||||
$this->assertEquals($value, $args[$name]);
|
||||
}
|
||||
|
||||
if ($args->isOption($name)) {
|
||||
$this->assertEquals($value, $args[$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args arguments as they appear in the cli
|
||||
* @param array $expectedValues expected values after parsing
|
||||
*
|
||||
* @dataProvider settingsWithValidOptions
|
||||
*/
|
||||
public function testParseWithValidOptions($cliParams, $expectedValues)
|
||||
{
|
||||
$this->_testParse($cliParams, $expectedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args arguments as they appear in the cli
|
||||
* @param array $expectedValues expected values after parsing
|
||||
* @dataProvider settingsWithMissingOptions
|
||||
* @expectedException PHPUnit_Framework_Error_Warning
|
||||
* @expectedExceptionMessage no value given for --option1
|
||||
*/
|
||||
public function testParseWithMissingOptions($cliParams, $expectedValues)
|
||||
{
|
||||
$this->_testParse($cliParams, $expectedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args arguments as they appear in the cli
|
||||
* @param array $expectedValues expected values after parsing
|
||||
* @dataProvider settingsWithMissingOptionsWithDefault
|
||||
*/
|
||||
public function testParseWithMissingOptionsWithDefault($cliParams, $expectedValues)
|
||||
{
|
||||
$this->_testParse($cliParams, $expectedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args arguments as they appear in the cli
|
||||
* @param array $expectedValues expected values after parsing
|
||||
* @dataProvider settingsWithNoOptionsWithDefault
|
||||
*/
|
||||
public function testParseWithNoOptionsWithDefault($cliParams, $expectedValues) {
|
||||
$this->_testParse($cliParams, $expectedValues);
|
||||
}
|
||||
}
|
||||
+553
@@ -0,0 +1,553 @@
|
||||
<?php
|
||||
|
||||
use cli\Colors;
|
||||
|
||||
class testsCli extends PHPUnit_Framework_TestCase {
|
||||
|
||||
function setUp() {
|
||||
// Reset enable state
|
||||
\cli\Colors::enable( null );
|
||||
|
||||
// Empty the cache
|
||||
\cli\Colors::clearStringCache();
|
||||
}
|
||||
|
||||
function test_string_length() {
|
||||
$this->assertEquals( \cli\Colors::length( 'x' ), 1 );
|
||||
$this->assertEquals( \cli\Colors::length( '日' ), 1 );
|
||||
}
|
||||
|
||||
function test_string_width() {
|
||||
$this->assertEquals( \cli\Colors::width( 'x' ), 1 );
|
||||
$this->assertEquals( \cli\Colors::width( '日' ), 2 ); // Double-width char.
|
||||
}
|
||||
|
||||
function test_encoded_string_length() {
|
||||
|
||||
$this->assertEquals( \cli\Colors::length( 'hello' ), 5 );
|
||||
$this->assertEquals( \cli\Colors::length( 'óra' ), 3 );
|
||||
$this->assertEquals( \cli\Colors::length( '日本語' ), 3 );
|
||||
|
||||
}
|
||||
|
||||
function test_encoded_string_width() {
|
||||
|
||||
$this->assertEquals( \cli\Colors::width( 'hello' ), 5 );
|
||||
$this->assertEquals( \cli\Colors::width( 'óra' ), 3 );
|
||||
$this->assertEquals( \cli\Colors::width( '日本語' ), 6 ); // 3 double-width chars.
|
||||
|
||||
}
|
||||
|
||||
function test_encoded_string_pad() {
|
||||
|
||||
$this->assertEquals( 6, strlen( \cli\Colors::pad( 'hello', 6 ) ) );
|
||||
$this->assertEquals( 7, strlen( \cli\Colors::pad( 'óra', 6 ) ) ); // special characters take one byte
|
||||
$this->assertEquals( 9, strlen( \cli\Colors::pad( '日本語', 6 ) ) ); // each character takes two bytes
|
||||
$this->assertEquals( 17, strlen( \cli\Colors::pad( 'עִבְרִית', 6 ) ) ); // process Hebrew vowels
|
||||
$this->assertEquals( 6, strlen( \cli\Colors::pad( 'hello', 6, false, false, STR_PAD_RIGHT ) ) );
|
||||
$this->assertEquals( 7, strlen( \cli\Colors::pad( 'óra', 6, false, false, STR_PAD_LEFT ) ) ); // special characters take one byte
|
||||
$this->assertEquals( 9, strlen( \cli\Colors::pad( '日本語', 6, false, false, STR_PAD_BOTH ) ) ); // each character takes two bytes
|
||||
$this->assertSame( 4, strpos( \cli\Colors::pad( 'hello', 10, false, false, STR_PAD_RIGHT ), 'o' ) );
|
||||
$this->assertSame( 9, strpos( \cli\Colors::pad( 'hello', 10, false, false, STR_PAD_LEFT ), 'o' ) );
|
||||
$this->assertSame( 6, strpos( \cli\Colors::pad( 'hello', 10, false, false, STR_PAD_BOTH ), 'o' ) );
|
||||
$this->assertSame( 1, strpos( \cli\Colors::pad( 'hello', 10, false, false, STR_PAD_RIGHT ), 'e' ) );
|
||||
$this->assertSame( 6, strpos( \cli\Colors::pad( 'hello', 10, false, false, STR_PAD_LEFT ), 'e' ) );
|
||||
$this->assertSame( 3, strpos( \cli\Colors::pad( 'hello', 10, false, false, STR_PAD_BOTH ), 'e' ) );
|
||||
}
|
||||
|
||||
function test_colorized_string_pad() {
|
||||
// Colors enabled.
|
||||
Colors::enable( true );
|
||||
|
||||
$x = Colors::colorize( '%Gx%n', true ); // colorized `x` string
|
||||
$ora = Colors::colorize( "%Góra%n", true ); // colorized `óra` string
|
||||
|
||||
$this->assertSame( 22, strlen( Colors::pad( $x, 11 ) ) );
|
||||
$this->assertSame( 22, strlen( Colors::pad( $x, 11, false /*pre_colorized*/ ) ) );
|
||||
$this->assertSame( 22, strlen( Colors::pad( $x, 11, true /*pre_colorized*/ ) ) );
|
||||
|
||||
$this->assertSame( 23, strlen( Colors::pad( $ora, 11 ) ) ); // +1 for two-byte "ó".
|
||||
$this->assertSame( 23, strlen( Colors::pad( $ora, 11, false /*pre_colorized*/ ) ) );
|
||||
$this->assertSame( 23, strlen( Colors::pad( $ora, 11, true /*pre_colorized*/ ) ) );
|
||||
|
||||
// Colors disabled.
|
||||
Colors::disable( true );
|
||||
$this->assertFalse( Colors::shouldColorize() );
|
||||
|
||||
$this->assertSame( 20, strlen( Colors::pad( $x, 20 ) ) );
|
||||
$this->assertSame( 20, strlen( Colors::pad( $x, 20, false /*pre_colorized*/ ) ) );
|
||||
$this->assertSame( 31, strlen( Colors::pad( $x, 20, true /*pre_colorized*/ ) ) );
|
||||
|
||||
$this->assertSame( 21, strlen( Colors::pad( $ora, 20 ) ) ); // +1 for two-byte "ó".
|
||||
$this->assertSame( 21, strlen( Colors::pad( $ora, 20, false /*pre_colorized*/ ) ) );
|
||||
$this->assertSame( 32, strlen( Colors::pad( $ora, 20, true /*pre_colorized*/ ) ) );
|
||||
}
|
||||
|
||||
function test_encoded_substr() {
|
||||
|
||||
$this->assertEquals( \cli\safe_substr( \cli\Colors::pad( 'hello', 6), 0, 2 ), 'he' );
|
||||
$this->assertEquals( \cli\safe_substr( \cli\Colors::pad( 'óra', 6), 0, 2 ), 'ór' );
|
||||
$this->assertEquals( \cli\safe_substr( \cli\Colors::pad( '日本語', 6), 0, 2 ), '日本' );
|
||||
|
||||
$this->assertSame( 'el', \cli\safe_substr( Colors::pad( 'hello', 6 ), 1, 2 ) );
|
||||
|
||||
$this->assertSame( 'a ', \cli\safe_substr( Colors::pad( 'óra', 6 ), 2, 2 ) );
|
||||
$this->assertSame( ' ', \cli\safe_substr( Colors::pad( 'óra', 6 ), 5, 2 ) );
|
||||
|
||||
$this->assertSame( '本語', \cli\safe_substr( Colors::pad( '日本語', 8 ), 1, 2 ) );
|
||||
$this->assertSame( '語 ', \cli\safe_substr( Colors::pad( '日本語', 8 ), 2, 2 ) );
|
||||
$this->assertSame( ' ', \cli\safe_substr( Colors::pad( '日本語', 8 ), -1 ) );
|
||||
$this->assertSame( ' ', \cli\safe_substr( Colors::pad( '日本語', 8 ), -1, 2 ) );
|
||||
$this->assertSame( '語 ', \cli\safe_substr( Colors::pad( '日本語', 8 ), -3, 3 ) );
|
||||
}
|
||||
|
||||
function test_various_substr() {
|
||||
// Save.
|
||||
$test_safe_substr = getenv( 'PHP_CLI_TOOLS_TEST_SAFE_SUBSTR' );
|
||||
if ( function_exists( 'mb_detect_order' ) ) {
|
||||
$mb_detect_order = mb_detect_order();
|
||||
}
|
||||
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SAFE_SUBSTR' );
|
||||
|
||||
// Latin, kana, Latin, Latin combining, Thai combining, Hangul.
|
||||
$str = 'lムnöม้p를'; // 18 bytes.
|
||||
|
||||
// Large string.
|
||||
$large_str_str_start = 65536 * 2;
|
||||
$large_str = str_repeat( 'a', $large_str_str_start ) . $str;
|
||||
$large_str_len = strlen( $large_str ); // 128K + 18 bytes.
|
||||
|
||||
if ( \cli\can_use_icu() ) {
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SAFE_SUBSTR=1' ); // Tests grapheme_substr().
|
||||
$this->assertSame( '', \cli\safe_substr( $str, 0, 0 ) );
|
||||
$this->assertSame( 'l', \cli\safe_substr( $str, 0, 1 ) );
|
||||
$this->assertSame( 'lム', \cli\safe_substr( $str, 0, 2 ) );
|
||||
$this->assertSame( 'lムn', \cli\safe_substr( $str, 0, 3 ) );
|
||||
$this->assertSame( 'lムnö', \cli\safe_substr( $str, 0, 4 ) );
|
||||
$this->assertSame( 'lムnöม้', \cli\safe_substr( $str, 0, 5 ) );
|
||||
$this->assertSame( 'lムnöม้p', \cli\safe_substr( $str, 0, 6 ) );
|
||||
$this->assertSame( 'lムnöม้p를', \cli\safe_substr( $str, 0, 7 ) );
|
||||
$this->assertSame( 'lムnöม้p를', \cli\safe_substr( $str, 0, 8 ) );
|
||||
$this->assertSame( '', \cli\safe_substr( $str, 19 ) ); // Start too large.
|
||||
$this->assertSame( '', \cli\safe_substr( $str, 19, 7 ) ); // Start too large, with length.
|
||||
$this->assertSame( '', \cli\safe_substr( $str, 8 ) ); // Start same as length.
|
||||
$this->assertSame( '', \cli\safe_substr( $str, 8, 0 ) ); // Start same as length, with zero length.
|
||||
$this->assertSame( '를', \cli\safe_substr( $str, -1 ) );
|
||||
$this->assertSame( 'p를', \cli\safe_substr( $str, -2 ) );
|
||||
$this->assertSame( 'ม้p를', \cli\safe_substr( $str, -3 ) );
|
||||
$this->assertSame( 'öม้p를', \cli\safe_substr( $str, -4 ) );
|
||||
$this->assertSame( 'öม้p', \cli\safe_substr( $str, -4, 3 ) );
|
||||
$this->assertSame( 'nö', \cli\safe_substr( $str, -5, 2 ) );
|
||||
$this->assertSame( 'ム', \cli\safe_substr( $str, -6, 1 ) );
|
||||
$this->assertSame( 'ムnöม้p를', \cli\safe_substr( $str, -6 ) );
|
||||
$this->assertSame( 'lムnöม้p를', \cli\safe_substr( $str, -7 ) );
|
||||
$this->assertSame( 'lムnö', \cli\safe_substr( $str, -7, 4 ) );
|
||||
$this->assertSame( 'lムnöม้p를', \cli\safe_substr( $str, -8 ) );
|
||||
$this->assertSame( 'lムnöม้p를', \cli\safe_substr( $str, -9 ) ); // Negative start too large.
|
||||
|
||||
$this->assertSame( $large_str, \cli\safe_substr( $large_str, 0 ) );
|
||||
$this->assertSame( '', \cli\safe_substr( $large_str, $large_str_str_start, 0 ) );
|
||||
$this->assertSame( 'l', \cli\safe_substr( $large_str, $large_str_str_start, 1 ) );
|
||||
$this->assertSame( 'lム', \cli\safe_substr( $large_str, $large_str_str_start, 2 ) );
|
||||
$this->assertSame( 'p를', \cli\safe_substr( $large_str, -2 ) );
|
||||
}
|
||||
|
||||
if ( \cli\can_use_pcre_x() ) {
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SAFE_SUBSTR=2' ); // Tests preg_split( '/\X/u' ).
|
||||
$this->assertSame( '', \cli\safe_substr( $str, 0, 0 ) );
|
||||
$this->assertSame( 'l', \cli\safe_substr( $str, 0, 1 ) );
|
||||
$this->assertSame( 'lム', \cli\safe_substr( $str, 0, 2 ) );
|
||||
$this->assertSame( 'lムn', \cli\safe_substr( $str, 0, 3 ) );
|
||||
$this->assertSame( 'lムnö', \cli\safe_substr( $str, 0, 4 ) );
|
||||
$this->assertSame( 'lムnöม้', \cli\safe_substr( $str, 0, 5 ) );
|
||||
$this->assertSame( 'lムnöม้p', \cli\safe_substr( $str, 0, 6 ) );
|
||||
$this->assertSame( 'lムnöม้p를', \cli\safe_substr( $str, 0, 7 ) );
|
||||
$this->assertSame( 'lムnöม้p를', \cli\safe_substr( $str, 0, 8 ) );
|
||||
$this->assertSame( '', \cli\safe_substr( $str, 19 ) ); // Start too large.
|
||||
$this->assertSame( '', \cli\safe_substr( $str, 19, 7 ) ); // Start too large, with length.
|
||||
$this->assertSame( '', \cli\safe_substr( $str, 8 ) ); // Start same as length.
|
||||
$this->assertSame( '', \cli\safe_substr( $str, 8, 0 ) ); // Start same as length, with zero length.
|
||||
$this->assertSame( '를', \cli\safe_substr( $str, -1 ) );
|
||||
$this->assertSame( 'p를', \cli\safe_substr( $str, -2 ) );
|
||||
$this->assertSame( 'ม้p를', \cli\safe_substr( $str, -3 ) );
|
||||
$this->assertSame( 'öม้p를', \cli\safe_substr( $str, -4 ) );
|
||||
$this->assertSame( 'öม้p', \cli\safe_substr( $str, -4, 3 ) );
|
||||
$this->assertSame( 'nö', \cli\safe_substr( $str, -5, 2 ) );
|
||||
$this->assertSame( 'ム', \cli\safe_substr( $str, -6, 1 ) );
|
||||
$this->assertSame( 'ムnöม้p를', \cli\safe_substr( $str, -6 ) );
|
||||
$this->assertSame( 'lムnöม้p를', \cli\safe_substr( $str, -7 ) );
|
||||
$this->assertSame( 'lムnö', \cli\safe_substr( $str, -7, 4 ) );
|
||||
$this->assertSame( 'lムnöม้p를', \cli\safe_substr( $str, -8 ) );
|
||||
$this->assertSame( 'lムnöม้p를', \cli\safe_substr( $str, -9 ) ); // Negative start too large.
|
||||
|
||||
$this->assertSame( $large_str, \cli\safe_substr( $large_str, 0 ) );
|
||||
$this->assertSame( '', \cli\safe_substr( $large_str, $large_str_str_start, 0 ) );
|
||||
$this->assertSame( 'l', \cli\safe_substr( $large_str, $large_str_str_start, 1 ) );
|
||||
$this->assertSame( 'lム', \cli\safe_substr( $large_str, $large_str_str_start, 2 ) );
|
||||
$this->assertSame( 'p를', \cli\safe_substr( $large_str, -2 ) );
|
||||
}
|
||||
|
||||
if ( function_exists( 'mb_substr' ) ) {
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SAFE_SUBSTR=4' ); // Tests mb_substr().
|
||||
$this->assertSame( '', \cli\safe_substr( $str, 0, 0 ) );
|
||||
$this->assertSame( 'l', \cli\safe_substr( $str, 0, 1 ) );
|
||||
$this->assertSame( 'lム', \cli\safe_substr( $str, 0, 2 ) );
|
||||
$this->assertSame( 'lムn', \cli\safe_substr( $str, 0, 3 ) );
|
||||
$this->assertSame( 'lムno', \cli\safe_substr( $str, 0, 4 ) ); // Wrong.
|
||||
}
|
||||
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SAFE_SUBSTR=8' ); // Tests substr().
|
||||
$this->assertSame( '', \cli\safe_substr( $str, 0, 0 ) );
|
||||
$this->assertSame( 'l', \cli\safe_substr( $str, 0, 1 ) );
|
||||
$this->assertSame( "l\xe3", \cli\safe_substr( $str, 0, 2 ) ); // Corrupt.
|
||||
$this->assertSame( '', \cli\safe_substr( $str, strlen( $str ) + 1 ) ); // Return '' not false to match behavior of other methods when `$start` > strlen.
|
||||
|
||||
// Non-UTF-8 - both grapheme_substr() and preg_split( '/\X/u' ) will fail.
|
||||
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SAFE_SUBSTR' );
|
||||
|
||||
if ( function_exists( 'mb_substr' ) && function_exists( 'mb_detect_order' ) ) {
|
||||
// Latin-1
|
||||
mb_detect_order( array( 'UTF-8', 'ISO-8859-1' ) );
|
||||
$str = "\xe0b\xe7"; // "àbç" in ISO-8859-1
|
||||
$this->assertSame( "\xe0b", \cli\safe_substr( $str, 0, 2 ) );
|
||||
$this->assertSame( "\xe0b", mb_substr( $str, 0, 2, 'ISO-8859-1' ) );
|
||||
}
|
||||
|
||||
// Restore.
|
||||
putenv( false == $test_safe_substr ? 'PHP_CLI_TOOLS_TEST_SAFE_SUBSTR' : "PHP_CLI_TOOLS_TEST_SAFE_SUBSTR=$test_safe_substr" );
|
||||
if ( function_exists( 'mb_detect_order' ) ) {
|
||||
mb_detect_order( $mb_detect_order );
|
||||
}
|
||||
}
|
||||
|
||||
function test_is_width_encoded_substr() {
|
||||
|
||||
$this->assertSame( 'he', \cli\safe_substr( Colors::pad( 'hello', 6 ), 0, 2, true /*is_width*/ ) );
|
||||
$this->assertSame( 'ór', \cli\safe_substr( Colors::pad( 'óra', 6 ), 0, 2, true /*is_width*/ ) );
|
||||
$this->assertSame( '日', \cli\safe_substr( Colors::pad( '日本語', 8 ), 0, 2, true /*is_width*/ ) );
|
||||
$this->assertSame( '日', \cli\safe_substr( Colors::pad( '日本語', 8 ), 0, 3, true /*is_width*/ ) );
|
||||
$this->assertSame( '日本', \cli\safe_substr( Colors::pad( '日本語', 8 ), 0, 4, true /*is_width*/ ) );
|
||||
$this->assertSame( '日本語', \cli\safe_substr( Colors::pad( '日本語', 8 ), 0, 6, true /*is_width*/ ) );
|
||||
$this->assertSame( '日本語 ', \cli\safe_substr( Colors::pad( '日本語', 8 ), 0, 7, true /*is_width*/ ) );
|
||||
|
||||
$this->assertSame( 'el', \cli\safe_substr( Colors::pad( 'hello', 6 ), 1, 2, true /*is_width*/ ) );
|
||||
|
||||
$this->assertSame( 'a ', \cli\safe_substr( Colors::pad( 'óra', 6 ), 2, 2, true /*is_width*/ ) );
|
||||
$this->assertSame( ' ', \cli\safe_substr( Colors::pad( 'óra', 6 ), 5, 2, true /*is_width*/ ) );
|
||||
|
||||
$this->assertSame( '', \cli\safe_substr( '1日4本語90', 0, 0, true /*is_width*/ ) );
|
||||
$this->assertSame( '1', \cli\safe_substr( '1日4本語90', 0, 1, true /*is_width*/ ) );
|
||||
$this->assertSame( '1', \cli\safe_substr( '1日4本語90', 0, 2, true /*is_width*/ ) );
|
||||
$this->assertSame( '1日', \cli\safe_substr( '1日4本語90', 0, 3, true /*is_width*/ ) );
|
||||
$this->assertSame( '1日4', \cli\safe_substr( '1日4本語90', 0, 4, true /*is_width*/ ) );
|
||||
$this->assertSame( '1日4', \cli\safe_substr( '1日4本語90', 0, 5, true /*is_width*/ ) );
|
||||
$this->assertSame( '1日4本', \cli\safe_substr( '1日4本語90', 0, 6, true /*is_width*/ ) );
|
||||
$this->assertSame( '1日4本', \cli\safe_substr( '1日4本語90', 0, 7, true /*is_width*/ ) );
|
||||
$this->assertSame( '1日4本語', \cli\safe_substr( '1日4本語90', 0, 8, true /*is_width*/ ) );
|
||||
$this->assertSame( '1日4本語9', \cli\safe_substr( '1日4本語90', 0, 9, true /*is_width*/ ) );
|
||||
$this->assertSame( '1日4本語90', \cli\safe_substr( '1日4本語90', 0, 10, true /*is_width*/ ) );
|
||||
$this->assertSame( '1日4本語90', \cli\safe_substr( '1日4本語90', 0, 11, true /*is_width*/ ) );
|
||||
|
||||
$this->assertSame( '日', \cli\safe_substr( '1日4本語90', 1, 2, true /*is_width*/ ) );
|
||||
$this->assertSame( '日4', \cli\safe_substr( '1日4本語90', 1, 3, true /*is_width*/ ) );
|
||||
$this->assertSame( '4本語9', \cli\safe_substr( '1日4本語90', 2, 6, true /*is_width*/ ) );
|
||||
|
||||
$this->assertSame( '本', \cli\safe_substr( '1日4本語90', 3, 1, true /*is_width*/ ) );
|
||||
$this->assertSame( '本', \cli\safe_substr( '1日4本語90', 3, 2, true /*is_width*/ ) );
|
||||
$this->assertSame( '本', \cli\safe_substr( '1日4本語90', 3, 3, true /*is_width*/ ) );
|
||||
$this->assertSame( '本語', \cli\safe_substr( '1日4本語90', 3, 4, true /*is_width*/ ) );
|
||||
$this->assertSame( '本語9', \cli\safe_substr( '1日4本語90', 3, 5, true /*is_width*/ ) );
|
||||
|
||||
$this->assertSame( '0', \cli\safe_substr( '1日4本語90', 6, 1, true /*is_width*/ ) );
|
||||
$this->assertSame( '', \cli\safe_substr( '1日4本語90', 7, 1, true /*is_width*/ ) );
|
||||
$this->assertSame( '', \cli\safe_substr( '1日4本語90', 6, 0, true /*is_width*/ ) );
|
||||
|
||||
$this->assertSame( '0', \cli\safe_substr( '1日4本語90', -1, 3, true /*is_width*/ ) );
|
||||
$this->assertSame( '90', \cli\safe_substr( '1日4本語90', -2, 3, true /*is_width*/ ) );
|
||||
$this->assertSame( '語9', \cli\safe_substr( '1日4本語90', -3, 3, true /*is_width*/ ) );
|
||||
$this->assertSame( '本語9', \cli\safe_substr( '1日4本語90', -4, 5, true /*is_width*/ ) );
|
||||
}
|
||||
|
||||
function test_colorized_string_length() {
|
||||
$this->assertEquals( \cli\Colors::length( \cli\Colors::colorize( '%Gx%n', true ) ), 1 );
|
||||
$this->assertEquals( \cli\Colors::length( \cli\Colors::colorize( '%G日%n', true ) ), 1 );
|
||||
}
|
||||
|
||||
function test_colorized_string_width() {
|
||||
// Colors enabled.
|
||||
Colors::enable( true );
|
||||
|
||||
$x = Colors::colorize( '%Gx%n', true );
|
||||
$dw = Colors::colorize( '%G日%n', true ); // Double-width char.
|
||||
|
||||
$this->assertSame( 1, Colors::width( $x ) );
|
||||
$this->assertSame( 1, Colors::width( $x, false /*pre_colorized*/ ) );
|
||||
$this->assertSame( 1, Colors::width( $x, true /*pre_colorized*/ ) );
|
||||
|
||||
$this->assertSame( 2, Colors::width( $dw ) );
|
||||
$this->assertSame( 2, Colors::width( $dw, false /*pre_colorized*/ ) );
|
||||
$this->assertSame( 2, Colors::width( $dw, true /*pre_colorized*/ ) );
|
||||
|
||||
// Colors disabled.
|
||||
Colors::disable( true );
|
||||
$this->assertFalse( Colors::shouldColorize() );
|
||||
|
||||
$this->assertSame( 12, Colors::width( $x ) );
|
||||
$this->assertSame( 12, Colors::width( $x, false /*pre_colorized*/ ) );
|
||||
$this->assertSame( 1, Colors::width( $x, true /*pre_colorized*/ ) );
|
||||
|
||||
$this->assertSame( 13, Colors::width( $dw ) );
|
||||
$this->assertSame( 13, Colors::width( $dw, false /*pre_colorized*/ ) );
|
||||
$this->assertSame( 2, Colors::width( $dw, true /*pre_colorized*/ ) );
|
||||
}
|
||||
|
||||
function test_colorize_string_is_colored() {
|
||||
$original = '%Gx';
|
||||
$colorized = "\033[32;1mx";
|
||||
|
||||
$this->assertEquals( \cli\Colors::colorize( $original, true ), $colorized );
|
||||
}
|
||||
|
||||
function test_colorize_when_colorize_is_forced() {
|
||||
$original = '%gx%n';
|
||||
|
||||
$this->assertEquals( \cli\Colors::colorize( $original, false ), 'x' );
|
||||
}
|
||||
|
||||
function test_binary_string_is_converted_back_to_original_string() {
|
||||
$string = 'x';
|
||||
$string_with_color = '%b' . $string;
|
||||
$colorized_string = "\033[34m$string";
|
||||
|
||||
// Ensure colorization is applied correctly
|
||||
$this->assertEquals( \cli\Colors::colorize( $string_with_color, true ), $colorized_string );
|
||||
|
||||
// Ensure that the colorization is reverted
|
||||
$this->assertEquals( \cli\Colors::decolorize( $colorized_string ), $string );
|
||||
}
|
||||
|
||||
function test_string_cache() {
|
||||
$string = 'x';
|
||||
$string_with_color = '%k' . $string;
|
||||
$colorized_string = "\033[30m$string";
|
||||
|
||||
// Ensure colorization works
|
||||
$this->assertEquals( \cli\Colors::colorize( $string_with_color, true ), $colorized_string );
|
||||
|
||||
// Test that the value was cached appropriately
|
||||
$test_cache = array(
|
||||
'passed' => $string_with_color,
|
||||
'colorized' => $colorized_string,
|
||||
'decolorized' => $string,
|
||||
);
|
||||
|
||||
$real_cache = \cli\Colors::getStringCache();
|
||||
|
||||
// Test that the cache value exists
|
||||
$this->assertTrue( isset( $real_cache[ md5( $string_with_color ) ] ) );
|
||||
|
||||
// Test that the cache value is correctly set
|
||||
$this->assertEquals( $test_cache, $real_cache[ md5( $string_with_color ) ] );
|
||||
}
|
||||
|
||||
function test_string_cache_colorize() {
|
||||
$string = 'x';
|
||||
$string_with_color = '%k' . $string;
|
||||
$colorized_string = "\033[30m$string";
|
||||
|
||||
// Colors enabled.
|
||||
Colors::enable( true );
|
||||
|
||||
// Ensure colorization works
|
||||
$this->assertSame( $colorized_string, Colors::colorize( $string_with_color ) );
|
||||
$this->assertSame( $colorized_string, Colors::colorize( $string_with_color ) );
|
||||
|
||||
// Colors disabled.
|
||||
Colors::disable( true );
|
||||
$this->assertFalse( Colors::shouldColorize() );
|
||||
|
||||
// Ensure it doesn't come from the cache.
|
||||
$this->assertSame( $string, Colors::colorize( $string_with_color ) );
|
||||
$this->assertSame( $string, Colors::colorize( $string_with_color ) );
|
||||
|
||||
// Check that escaped % isn't stripped on putting into cache.
|
||||
$string = 'x%%n';
|
||||
$string_with_color = '%k' . $string;
|
||||
$this->assertSame( 'x%n', Colors::colorize( $string_with_color ) );
|
||||
$this->assertSame( 'x%n', Colors::colorize( $string_with_color ) );
|
||||
}
|
||||
|
||||
function test_decolorize() {
|
||||
// Colors enabled.
|
||||
Colors::enable( true );
|
||||
|
||||
$string = '%kx%%n%n';
|
||||
$colorized_string = Colors::colorize( $string );
|
||||
$both_string = '%gfoo' . $colorized_string . 'bar%%%n';
|
||||
|
||||
$this->assertSame( 'x%n', Colors::decolorize( $string ) );
|
||||
$this->assertSame( 'x', Colors::decolorize( $colorized_string ) );
|
||||
$this->assertSame( 'fooxbar%', Colors::decolorize( $both_string ) );
|
||||
|
||||
$this->assertSame( $string, Colors::decolorize( $string, 1 /*keep_tokens*/ ) );
|
||||
$this->assertSame( 'x%n', Colors::decolorize( $colorized_string, 1 /*keep_tokens*/ ) );
|
||||
$this->assertSame( '%gfoox%nbar%%%n', Colors::decolorize( $both_string, 1 /*keep_tokens*/ ) );
|
||||
|
||||
$this->assertSame( 'x%n', Colors::decolorize( $string, 2 /*keep_encodings*/ ) );
|
||||
$this->assertSame( '[30mx[0m', Colors::decolorize( $colorized_string, 2 /*keep_encodings*/ ) );
|
||||
$this->assertSame( 'foo[30mx[0mbar%', Colors::decolorize( $both_string, 2 /*keep_encodings*/ ) );
|
||||
|
||||
$this->assertSame( $string, Colors::decolorize( $string, 3 /*noop*/ ) );
|
||||
$this->assertSame( $colorized_string, Colors::decolorize( $colorized_string, 3 /*noop*/ ) );
|
||||
$this->assertSame( $both_string, Colors::decolorize( $both_string, 3 /*noop*/ ) );
|
||||
}
|
||||
|
||||
function test_strwidth() {
|
||||
// Save.
|
||||
$test_strwidth = getenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH' );
|
||||
if ( function_exists( 'mb_detect_order' ) ) {
|
||||
$mb_detect_order = mb_detect_order();
|
||||
}
|
||||
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH' );
|
||||
|
||||
// UTF-8.
|
||||
|
||||
// 4 characters, one a double-width Han = 5 spacing chars, with 2 combining chars. Adapted from http://unicode.org/faq/char_combmark.html#7 (combining acute accent added after "a").
|
||||
$str = "a\xCC\x81\xE0\xA4\xA8\xE0\xA4\xBF\xE4\xBA\x9C\xF0\x90\x82\x83";
|
||||
|
||||
if ( \cli\can_use_icu() ) {
|
||||
$this->assertSame( 5, \cli\strwidth( $str ) ); // Tests grapheme_strlen().
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH=2' ); // Test preg_split( '/\X/u' ).
|
||||
$this->assertSame( 5, \cli\strwidth( $str ) );
|
||||
} else {
|
||||
$this->assertSame( 5, \cli\strwidth( $str ) ); // Tests preg_split( '/\X/u' ).
|
||||
}
|
||||
|
||||
if ( function_exists( 'mb_strwidth' ) && function_exists( 'mb_detect_order' ) ) {
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH=4' ); // Test mb_strwidth().
|
||||
mb_detect_order( array( 'UTF-8', 'ISO-8859-1' ) );
|
||||
$this->assertSame( 5, \cli\strwidth( $str ) );
|
||||
}
|
||||
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH=8' ); // Test safe_strlen().
|
||||
if ( \cli\can_use_icu() || \cli\can_use_pcre_x() ) {
|
||||
$this->assertSame( 4, \cli\strwidth( $str ) ); // safe_strlen() (correctly) does not account for double-width Han so out by 1.
|
||||
} elseif ( function_exists( 'mb_strlen' ) && function_exists( 'mb_detect_order' ) ) {
|
||||
$this->assertSame( 4, \cli\strwidth( $str ) ); // safe_strlen() (correctly) does not account for double-width Han so out by 1.
|
||||
$this->assertSame( 6, mb_strlen( $str, 'UTF-8' ) );
|
||||
} else {
|
||||
$this->assertSame( 16, \cli\strwidth( $str ) ); // strlen() - no. of bytes.
|
||||
$this->assertSame( 16, strlen( $str ) );
|
||||
}
|
||||
|
||||
// Nepali जस्ट ट॓स्ट गर्दै - 1st word: 3 spacing + 1 combining, 2nd word: 3 spacing + 2 combining, 3rd word: 3 spacing + 2 combining = 9 spacing chars + 2 spaces = 11 chars.
|
||||
$str = "\xe0\xa4\x9c\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\x9f \xe0\xa4\x9f\xe0\xa5\x93\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\x9f \xe0\xa4\x97\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa6\xe0\xa5\x88";
|
||||
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH' );
|
||||
|
||||
if ( \cli\can_use_icu() ) {
|
||||
$this->assertSame( 11, \cli\strwidth( $str ) ); // Tests grapheme_strlen().
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH=2' ); // Test preg_split( '/\X/u' ).
|
||||
$this->assertSame( 11, \cli\strwidth( $str ) );
|
||||
} else {
|
||||
$this->assertSame( 11, \cli\strwidth( $str ) ); // Tests preg_split( '/\X/u' ).
|
||||
}
|
||||
|
||||
if ( function_exists( 'mb_strwidth' ) && function_exists( 'mb_detect_order' ) ) {
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH=4' ); // Test mb_strwidth().
|
||||
mb_detect_order( array( 'UTF-8' ) );
|
||||
$this->assertSame( 11, \cli\strwidth( $str ) );
|
||||
}
|
||||
|
||||
// Non-UTF-8 - both grapheme_strlen() and preg_split( '/\X/u' ) will fail.
|
||||
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH' );
|
||||
|
||||
if ( function_exists( 'mb_strwidth' ) && function_exists( 'mb_detect_order' ) ) {
|
||||
// Latin-1
|
||||
mb_detect_order( array( 'UTF-8', 'ISO-8859-1' ) );
|
||||
$str = "\xe0b\xe7"; // "àbç" in ISO-8859-1
|
||||
$this->assertSame( 3, \cli\strwidth( $str ) ); // Test mb_strwidth().
|
||||
$this->assertSame( 3, mb_strwidth( $str, 'ISO-8859-1' ) );
|
||||
|
||||
// Shift JIS.
|
||||
mb_detect_order( array( 'UTF-8', 'SJIS' ) );
|
||||
$str = "\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd\x90\xa2\x8a\x45!"; // "こャにちは世界!" ("Hello world!") in Shift JIS - 7 double-width chars plus Latin exclamation mark.
|
||||
$this->assertSame( 15, \cli\strwidth( $str ) ); // Test mb_strwidth().
|
||||
$this->assertSame( 15, mb_strwidth( $str, 'SJIS' ) );
|
||||
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH=8' ); // Test safe_strlen().
|
||||
if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_detect_order' ) ) {
|
||||
$this->assertSame( 8, \cli\strwidth( $str ) ); // mb_strlen() - doesn't allow for double-width.
|
||||
$this->assertSame( 8, mb_strlen( $str, 'SJIS' ) );
|
||||
} else {
|
||||
$this->assertSame( 15, \cli\strwidth( $str ) ); // strlen() - no. of bytes.
|
||||
$this->assertSame( 15, strlen( $str ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Restore.
|
||||
putenv( false == $test_strwidth ? 'PHP_CLI_TOOLS_TEST_STRWIDTH' : "PHP_CLI_TOOLS_TEST_STRWIDTH=$test_strwidth" );
|
||||
if ( function_exists( 'mb_detect_order' ) ) {
|
||||
mb_detect_order( $mb_detect_order );
|
||||
}
|
||||
}
|
||||
|
||||
function test_safe_strlen() {
|
||||
// Save.
|
||||
$test_safe_strlen = getenv( 'PHP_CLI_TOOLS_TEST_SAFE_STRLEN' );
|
||||
if ( function_exists( 'mb_detect_order' ) ) {
|
||||
$mb_detect_order = mb_detect_order();
|
||||
}
|
||||
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SAFE_STRLEN' );
|
||||
|
||||
// UTF-8.
|
||||
|
||||
// ASCII l, 3-byte kana, ASCII n, ASCII o + 2-byte combining umlaut, 6-byte Thai combining, ASCII, 3-byte Hangul. grapheme length 7, bytes 18.
|
||||
$str = 'lムnöม้p를';
|
||||
|
||||
if ( \cli\can_use_icu() ) {
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SAFE_STRLEN' ); // Test grapheme_strlen().
|
||||
$this->assertSame( 7, \cli\safe_strlen( $str ) );
|
||||
if ( \cli\can_use_pcre_x() ) {
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SAFE_STRLEN=2' ); // Test preg_split( '/\X/u' ).
|
||||
$this->assertSame( 7, \cli\safe_strlen( $str ) );
|
||||
}
|
||||
} elseif ( \cli\can_use_pcre_x() ) {
|
||||
$this->assertSame( 7, \cli\safe_strlen( $str ) ); // Tests preg_split( '/\X/u' ).
|
||||
} else {
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SAFE_STRLEN=8' ); // Test strlen().
|
||||
$this->assertSame( 18, \cli\safe_strlen( $str ) ); // strlen() - no. of bytes.
|
||||
$this->assertSame( 18, strlen( $str ) );
|
||||
}
|
||||
|
||||
if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_detect_order' ) ) {
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SAFE_STRLEN=4' ); // Test mb_strlen().
|
||||
mb_detect_order( array( 'UTF-8', 'ISO-8859-1' ) );
|
||||
$this->assertSame( 7, \cli\safe_strlen( $str ) );
|
||||
$this->assertSame( 9, mb_strlen( $str, 'UTF-8' ) ); // mb_strlen() - counts the 2 combining chars.
|
||||
}
|
||||
|
||||
// Non-UTF-8 - both grapheme_strlen() and preg_split( '/\X/u' ) will fail.
|
||||
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SAFE_STRLEN' );
|
||||
|
||||
if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_detect_order' ) ) {
|
||||
// Latin-1
|
||||
mb_detect_order( array( 'UTF-8', 'ISO-8859-1' ) );
|
||||
$str = "\xe0b\xe7"; // "àbç" in ISO-8859-1
|
||||
$this->assertSame( 3, \cli\safe_strlen( $str ) ); // Test mb_strlen().
|
||||
$this->assertSame( 3, mb_strlen( $str, 'ISO-8859-1' ) );
|
||||
}
|
||||
|
||||
// Restore.
|
||||
putenv( false == $test_safe_strlen ? 'PHP_CLI_TOOLS_TEST_SAFE_STRLEN' : "PHP_CLI_TOOLS_TEST_SAFE_STRLEN=$test_safe_strlen" );
|
||||
if ( function_exists( 'mb_detect_order' ) ) {
|
||||
mb_detect_order( $mb_detect_order );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use cli\Colors;
|
||||
|
||||
class testsColors extends PHPUnit_Framework_TestCase {
|
||||
|
||||
/**
|
||||
* @dataProvider dataColors
|
||||
*/
|
||||
function testColors( $str, $color ) {
|
||||
// Colors enabled.
|
||||
Colors::enable( true );
|
||||
|
||||
$colored = Colors::color( $color );
|
||||
$this->assertSame( Colors::colorize( $str ), Colors::color( $color ) );
|
||||
if ( in_array( 'reset', $color ) ) {
|
||||
$this->assertTrue( false !== strpos( $colored, '[0m' ) );
|
||||
} else {
|
||||
$this->assertTrue( false === strpos( $colored, '[0m' ) );
|
||||
}
|
||||
}
|
||||
|
||||
function dataColors() {
|
||||
$ret = array();
|
||||
foreach ( Colors::getColors() as $str => $color ) {
|
||||
$ret[] = array( $str, $color );
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use cli\Shell;
|
||||
|
||||
/**
|
||||
* Class TestShell
|
||||
*/
|
||||
class TestShell extends PHPUnit_Framework_TestCase {
|
||||
|
||||
/**
|
||||
* Test getting TERM columns.
|
||||
*/
|
||||
function testColumns() {
|
||||
// Save.
|
||||
$env_term = getenv( 'TERM' );
|
||||
$env_columns = getenv( 'COLUMNS' );
|
||||
$env_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' );
|
||||
$env_shell_columns_reset = getenv( 'PHP_CLI_TOOLS_TEST_SHELL_COLUMNS_RESET' );
|
||||
|
||||
putenv( 'PHP_CLI_TOOLS_TEST_SHELL_COLUMNS_RESET=1' );
|
||||
|
||||
// No TERM should result in default 80.
|
||||
|
||||
putenv( 'TERM' );
|
||||
putenv( 'COLUMNS=80' );
|
||||
|
||||
putenv( 'WP_CLI_TEST_IS_WINDOWS=0' );
|
||||
$columns = cli\Shell::columns();
|
||||
$this->assertSame( 80, $columns );
|
||||
|
||||
putenv( 'WP_CLI_TEST_IS_WINDOWS=1' );
|
||||
$columns = cli\Shell::columns();
|
||||
$this->assertSame( 80, $columns );
|
||||
|
||||
// TERM and COLUMNS should result in whatever COLUMNS is.
|
||||
|
||||
putenv( 'TERM=vt100' );
|
||||
putenv( 'COLUMNS=100' );
|
||||
|
||||
putenv( 'WP_CLI_TEST_IS_WINDOWS=0' );
|
||||
$columns = cli\Shell::columns();
|
||||
$this->assertSame( 100, $columns );
|
||||
|
||||
putenv( 'WP_CLI_TEST_IS_WINDOWS=1' );
|
||||
$columns = cli\Shell::columns();
|
||||
$this->assertSame( 100, $columns );
|
||||
|
||||
// Restore.
|
||||
putenv( false === $env_term ? 'TERM' : "TERM=$env_term" );
|
||||
putenv( false === $env_columns ? 'COLUMNS' : "COLUMNS=$env_columns" );
|
||||
putenv( false === $env_is_windows ? 'WP_CLI_TEST_IS_WINDOWS' : "WP_CLI_TEST_IS_WINDOWS=$env_is_windows" );
|
||||
putenv( false === $env_shell_columns_reset ? 'PHP_CLI_TOOLS_TEST_SHELL_COLUMNS_RESET' : "PHP_CLI_TOOLS_TEST_SHELL_COLUMNS_RESET=$env_shell_columns_reset" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
use cli\Streams;
|
||||
use cli\Table;
|
||||
use cli\table\Ascii;
|
||||
use cli\Colors;
|
||||
|
||||
/**
|
||||
* Class Test_Table_Ascii
|
||||
*
|
||||
* Acceptance tests for ASCII table drawing.
|
||||
* It will redirect STDOUT to temporary file and check that output matches with expected
|
||||
*/
|
||||
class Test_Table_Ascii extends PHPUnit_Framework_TestCase {
|
||||
/**
|
||||
* @var string Path to temporary file, where STDOUT output will be redirected during tests
|
||||
*/
|
||||
private $_mockFile;
|
||||
/**
|
||||
* @var \cli\Table Instance
|
||||
*/
|
||||
private $_instance;
|
||||
|
||||
/**
|
||||
* Creates instance and redirects STDOUT to temporary file
|
||||
*/
|
||||
public function setUp() {
|
||||
$this->_mockFile = tempnam(sys_get_temp_dir(), 'temp');
|
||||
$resource = fopen($this->_mockFile, 'wb');
|
||||
Streams::setStream('out', $resource);
|
||||
|
||||
$this->_instance = new Table();
|
||||
$this->_instance->setRenderer(new Ascii());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans temporary file
|
||||
*/
|
||||
public function tearDown() {
|
||||
if (file_exists($this->_mockFile)) {
|
||||
unlink($this->_mockFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw simple One column table
|
||||
*/
|
||||
public function testDrawOneColumnTable() {
|
||||
$headers = array('Test Header');
|
||||
$rows = array(
|
||||
array('x'),
|
||||
);
|
||||
$output = <<<'OUT'
|
||||
+-------------+
|
||||
| Test Header |
|
||||
+-------------+
|
||||
| x |
|
||||
+-------------+
|
||||
|
||||
OUT;
|
||||
$this->assertInOutEquals(array($headers, $rows), $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw simple One column table with colored string
|
||||
* Output should look like:
|
||||
* +-------------+
|
||||
* | Test Header |
|
||||
* +-------------+
|
||||
* | x |
|
||||
* +-------------+
|
||||
*
|
||||
* where `x` character has green color.
|
||||
* At the same time it checks that `green` defined in `cli\Colors` really looks as `green`.
|
||||
*/
|
||||
public function testDrawOneColumnColoredTable() {
|
||||
Colors::enable( true );
|
||||
$headers = array('Test Header');
|
||||
$rows = array(
|
||||
array(Colors::colorize('%Gx%n', true)),
|
||||
);
|
||||
// green `x`
|
||||
$x = "\x1B\x5B\x33\x32\x3B\x31\x6Dx\x1B\x5B\x30\x6D";
|
||||
$output = <<<OUT
|
||||
+-------------+
|
||||
| Test Header |
|
||||
+-------------+
|
||||
| $x |
|
||||
+-------------+
|
||||
|
||||
OUT;
|
||||
$this->assertInOutEquals(array($headers, $rows), $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check it works with colors disabled.
|
||||
*/
|
||||
public function testDrawOneColumnColorDisabledTable() {
|
||||
Colors::disable( true );
|
||||
$this->assertFalse( Colors::shouldColorize() );
|
||||
$headers = array('Test Header');
|
||||
$rows = array(
|
||||
array('%Gx%n'),
|
||||
);
|
||||
$output = <<<OUT
|
||||
+-------------+
|
||||
| Test Header |
|
||||
+-------------+
|
||||
| %Gx%n |
|
||||
+-------------+
|
||||
|
||||
OUT;
|
||||
$this->assertInOutEquals(array($headers, $rows), $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that spacing and borders are handled correctly in table
|
||||
*/
|
||||
public function testSpacingInTable() {
|
||||
$headers = array('A', ' ', 'C', '');
|
||||
$rows = array(
|
||||
array(' ', 'B1', '', 'D1'),
|
||||
array('A2', '', ' C2', null),
|
||||
);
|
||||
$output = <<<'OUT'
|
||||
+-------+------+-----+----+
|
||||
| A | | C | |
|
||||
+-------+------+-----+----+
|
||||
| | B1 | | D1 |
|
||||
| A2 | | C2 | |
|
||||
+-------+------+-----+----+
|
||||
|
||||
OUT;
|
||||
$this->assertInOutEquals(array($headers, $rows), $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test correct table indentation and border positions for multibyte strings
|
||||
*/
|
||||
public function testTableWithMultibyteStrings() {
|
||||
$headers = array('German', 'French', 'Russian', 'Chinese');
|
||||
$rows = array(
|
||||
array('Schätzen', 'Apprécier', 'Оценить', '欣賞'),
|
||||
);
|
||||
$output = <<<'OUT'
|
||||
+----------+-----------+---------+---------+
|
||||
| German | French | Russian | Chinese |
|
||||
+----------+-----------+---------+---------+
|
||||
| Schätzen | Apprécier | Оценить | 欣賞 |
|
||||
+----------+-----------+---------+---------+
|
||||
|
||||
OUT;
|
||||
$this->assertInOutEquals(array($headers, $rows), $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that % gets escaped correctly.
|
||||
*/
|
||||
public function testTableWithPercentCharacters() {
|
||||
$headers = array( 'Heading', 'Heading2', 'Heading3' );
|
||||
$rows = array(
|
||||
array( '% at start', 'at end %', 'in % middle' )
|
||||
);
|
||||
$output = <<<'OUT'
|
||||
+------------+----------+-------------+
|
||||
| Heading | Heading2 | Heading3 |
|
||||
+------------+----------+-------------+
|
||||
| % at start | at end % | in % middle |
|
||||
+------------+----------+-------------+
|
||||
|
||||
OUT;
|
||||
$this->assertInOutEquals(array($headers, $rows), $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a % is appropriately padded in the table
|
||||
*/
|
||||
public function testTablePaddingWithPercentCharacters() {
|
||||
$headers = array( 'ID', 'post_title', 'post_name' );
|
||||
$rows = array(
|
||||
array(
|
||||
3,
|
||||
'10%',
|
||||
''
|
||||
),
|
||||
array(
|
||||
1,
|
||||
'Hello world!',
|
||||
'hello-world'
|
||||
),
|
||||
);
|
||||
$output = <<<'OUT'
|
||||
+----+--------------+-------------+
|
||||
| ID | post_title | post_name |
|
||||
+----+--------------+-------------+
|
||||
| 3 | 10% | |
|
||||
| 1 | Hello world! | hello-world |
|
||||
+----+--------------+-------------+
|
||||
|
||||
OUT;
|
||||
$this->assertInOutEquals(array($headers, $rows), $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw wide multiplication Table.
|
||||
* Example with many columns, many rows
|
||||
*/
|
||||
public function testDrawMultiplicationTable() {
|
||||
$maxFactor = 16;
|
||||
$headers = array_merge(array('x'), range(1, $maxFactor));
|
||||
for ($i = 1, $rows = array(); $i <= $maxFactor; ++$i) {
|
||||
$rows[] = array_merge(array($i), range($i, $i * $maxFactor, $i));
|
||||
}
|
||||
|
||||
$output = <<<'OUT'
|
||||
+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| x | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
|
||||
+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| 1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
|
||||
| 2 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 |
|
||||
| 3 | 3 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 | 39 | 42 | 45 | 48 |
|
||||
| 4 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | 64 |
|
||||
| 5 | 5 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 |
|
||||
| 6 | 6 | 12 | 18 | 24 | 30 | 36 | 42 | 48 | 54 | 60 | 66 | 72 | 78 | 84 | 90 | 96 |
|
||||
| 7 | 7 | 14 | 21 | 28 | 35 | 42 | 49 | 56 | 63 | 70 | 77 | 84 | 91 | 98 | 105 | 112 |
|
||||
| 8 | 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 88 | 96 | 104 | 112 | 120 | 128 |
|
||||
| 9 | 9 | 18 | 27 | 36 | 45 | 54 | 63 | 72 | 81 | 90 | 99 | 108 | 117 | 126 | 135 | 144 |
|
||||
| 10 | 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100 | 110 | 120 | 130 | 140 | 150 | 160 |
|
||||
| 11 | 11 | 22 | 33 | 44 | 55 | 66 | 77 | 88 | 99 | 110 | 121 | 132 | 143 | 154 | 165 | 176 |
|
||||
| 12 | 12 | 24 | 36 | 48 | 60 | 72 | 84 | 96 | 108 | 120 | 132 | 144 | 156 | 168 | 180 | 192 |
|
||||
| 13 | 13 | 26 | 39 | 52 | 65 | 78 | 91 | 104 | 117 | 130 | 143 | 156 | 169 | 182 | 195 | 208 |
|
||||
| 14 | 14 | 28 | 42 | 56 | 70 | 84 | 98 | 112 | 126 | 140 | 154 | 168 | 182 | 196 | 210 | 224 |
|
||||
| 15 | 15 | 30 | 45 | 60 | 75 | 90 | 105 | 120 | 135 | 150 | 165 | 180 | 195 | 210 | 225 | 240 |
|
||||
| 16 | 16 | 32 | 48 | 64 | 80 | 96 | 112 | 128 | 144 | 160 | 176 | 192 | 208 | 224 | 240 | 256 |
|
||||
+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
OUT;
|
||||
$this->assertInOutEquals(array($headers, $rows), $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a table with headers but no data
|
||||
*/
|
||||
public function testDrawWithHeadersNoData() {
|
||||
$headers = array('header 1', 'header 2');
|
||||
$rows = array();
|
||||
$output = <<<'OUT'
|
||||
+----------+----------+
|
||||
| header 1 | header 2 |
|
||||
+----------+----------+
|
||||
+----------+----------+
|
||||
|
||||
OUT;
|
||||
$this->assertInOutEquals(array($headers, $rows), $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that Input and Output equals,
|
||||
* Sugar method for fast access from tests
|
||||
*
|
||||
* @param array $input First element is header array, second element is rows array
|
||||
* @param mixed $output Expected output
|
||||
*/
|
||||
private function assertInOutEquals(array $input, $output) {
|
||||
$this->_instance->setHeaders($input[0]);
|
||||
$this->_instance->setRows($input[1]);
|
||||
$this->_instance->display();
|
||||
$this->assertOutFileEqualsWith($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that contents of input string and temporary file match
|
||||
*
|
||||
* @param mixed $expected Expected output
|
||||
*/
|
||||
private function assertOutFileEqualsWith($expected) {
|
||||
$this->assertEquals($expected, file_get_contents($this->_mockFile));
|
||||
}
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
use cli\Colors, cli\Table, cli\Table\Ascii;
|
||||
|
||||
/**
|
||||
* Tests for cli\Table
|
||||
*/
|
||||
class Test_Table extends PHPUnit_Framework_TestCase {
|
||||
|
||||
public function test_column_value_too_long_ascii() {
|
||||
|
||||
$constraint_width = 80;
|
||||
|
||||
$table = new cli\Table;
|
||||
$renderer = new cli\Table\Ascii;
|
||||
$renderer->setConstraintWidth( $constraint_width );
|
||||
$table->setRenderer( $renderer );
|
||||
$table->setHeaders( array( 'Field', 'Value' ) );
|
||||
$table->addRow( array( 'description', 'The 2012 theme for WordPress is a fully responsive theme that looks great on any device. Features include a front page template with its own widgets, an optional display font, styling for post formats on both index and single views, and an optional no-sidebar page template. Make it yours with a custom menu, header image, and background.' ) );
|
||||
$table->addRow( array( 'author', '<a href="http://wordpress.org/" title="Visit author homepage">the WordPress team</a>' ) );
|
||||
|
||||
$out = $table->getDisplayLines();
|
||||
$this->assertCount( 12, $out );
|
||||
$this->assertEquals( $constraint_width, strlen( $out[0] ) );
|
||||
$this->assertEquals( $constraint_width, strlen( $out[1] ) );
|
||||
$this->assertEquals( $constraint_width, strlen( $out[2] ) );
|
||||
$this->assertEquals( $constraint_width, strlen( $out[3] ) );
|
||||
$this->assertEquals( $constraint_width, strlen( $out[4] ) );
|
||||
$this->assertEquals( $constraint_width, strlen( $out[5] ) );
|
||||
$this->assertEquals( $constraint_width, strlen( $out[6] ) );
|
||||
$this->assertEquals( $constraint_width, strlen( $out[7] ) );
|
||||
$this->assertEquals( $constraint_width, strlen( $out[8] ) );
|
||||
$this->assertEquals( $constraint_width, strlen( $out[9] ) );
|
||||
$this->assertEquals( $constraint_width, strlen( $out[10] ) );
|
||||
$this->assertEquals( $constraint_width, strlen( $out[11] ) );
|
||||
|
||||
$constraint_width = 81;
|
||||
|
||||
$renderer = new cli\Table\Ascii;
|
||||
$renderer->setConstraintWidth( $constraint_width );
|
||||
$table->setRenderer( $renderer );
|
||||
|
||||
$out = $table->getDisplayLines();
|
||||
for ( $i = 0; $i < count( $out ); $i++ ) {
|
||||
$this->assertEquals( $constraint_width, strlen( $out[ $i ] ) );
|
||||
}
|
||||
}
|
||||
|
||||
public function test_column_value_too_long_with_multibytes() {
|
||||
|
||||
$constraint_width = 80;
|
||||
|
||||
$table = new cli\Table;
|
||||
$renderer = new cli\Table\Ascii;
|
||||
$renderer->setConstraintWidth( $constraint_width );
|
||||
$table->setRenderer( $renderer );
|
||||
$table->setHeaders( array( 'Field', 'Value' ) );
|
||||
$table->addRow( array( '1この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。2この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。', 'こんにちは' ) );
|
||||
$table->addRow( array( 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.', 'Hello' ) );
|
||||
|
||||
$out = $table->getDisplayLines();
|
||||
for ( $i = 0; $i < count( $out ); $i++ ) {
|
||||
$this->assertEquals( $constraint_width, \cli\strwidth( $out[$i] ) );
|
||||
}
|
||||
|
||||
$constraint_width = 81;
|
||||
|
||||
$renderer = new cli\Table\Ascii;
|
||||
$renderer->setConstraintWidth( $constraint_width );
|
||||
$table->setRenderer( $renderer );
|
||||
|
||||
$out = $table->getDisplayLines();
|
||||
for ( $i = 0; $i < count( $out ); $i++ ) {
|
||||
$this->assertEquals( $constraint_width, \cli\strwidth( $out[$i] ) );
|
||||
}
|
||||
}
|
||||
|
||||
public function test_column_odd_single_width_with_double_width() {
|
||||
|
||||
$dummy = new cli\Table;
|
||||
$renderer = new cli\Table\Ascii;
|
||||
|
||||
$strip_borders = function ( $a ) {
|
||||
return array_map( function ( $v ) {
|
||||
return substr( $v, 2, -2 );
|
||||
}, $a );
|
||||
};
|
||||
|
||||
$renderer->setWidths( array( 10 ) );
|
||||
|
||||
// 1 single-width, 6 double-width, 1 single-width, 2 double-width, 1 half-width, 2 double-width.
|
||||
$out = $renderer->row( array( '1あいうえおか2きくカけこ' ) );
|
||||
$result = $strip_borders( explode( "\n", $out ) );
|
||||
|
||||
$this->assertSame( 3, count( $result ) );
|
||||
$this->assertSame( '1あいうえ ', $result[0] ); // 1 single width, 4 double-width, space = 10.
|
||||
$this->assertSame( 'おか2きくカ', $result[1] ); // 2 double-width, 1 single-width, 2 double-width, 1 half-width = 10.
|
||||
$this->assertSame( 'けこ ', $result[2] ); // 2 double-width, 8 spaces = 10.
|
||||
|
||||
// Minimum width 1.
|
||||
|
||||
$renderer->setWidths( array( 1 ) );
|
||||
|
||||
$out = $renderer->row( array( '1あいうえおか2きくカけこ' ) );
|
||||
$result = $strip_borders( explode( "\n", $out ) );
|
||||
|
||||
$this->assertSame( 13, count( $result ) );
|
||||
// Uneven rows.
|
||||
$this->assertSame( '1', $result[0] );
|
||||
$this->assertSame( 'あ', $result[1] );
|
||||
|
||||
// Zero width does no wrapping.
|
||||
|
||||
$renderer->setWidths( array( 0 ) );
|
||||
|
||||
$out = $renderer->row( array( '1あいうえおか2きくカけこ' ) );
|
||||
$result = $strip_borders( explode( "\n", $out ) );
|
||||
|
||||
$this->assertSame( 1, count( $result ) );
|
||||
}
|
||||
|
||||
public function test_column_fullwidth_and_combining() {
|
||||
|
||||
$constraint_width = 80;
|
||||
|
||||
$table = new cli\Table;
|
||||
$renderer = new cli\Table\Ascii;
|
||||
$renderer->setConstraintWidth( $constraint_width );
|
||||
$table->setRenderer( $renderer );
|
||||
$table->setHeaders( array( 'Field', 'Value' ) );
|
||||
$table->addRow( array( 'ID', 2151 ) );
|
||||
$table->addRow( array( 'post_author', 1 ) );
|
||||
$table->addRow( array( 'post_title', 'only-english-lorem-ipsum-dolor-sit-amet-consectetur-adipisicing-elit-sed-do-eiusmod-tempor-incididunt-ut-labore' ) );
|
||||
$table->addRow( array( 'post_content',
|
||||
//'ให้รู้จัก ให้หาหนทางใหม่' .
|
||||
'♫ มีอีกหลายต่อหลายคน เขาอดทนก็เพื่อรัก' . "\n" .
|
||||
'รักผลักดันให้รู้จัก ให้หาหนทางใหม่' . "\r\n" .
|
||||
'ฉันจะล้มตั้งหลายที ดีที่รักมาฉุดไว้' . "\r\n" .
|
||||
'รักสร้างสรรค์สิ่งมากมาย และหลอมละลายทุกหัวใจ' . "\r\n" .
|
||||
'จะมาร้ายดียังไง แต่ใจก็ยังต้องการ' . "\r\n" .
|
||||
'ในทุกๆ วัน โลกหมุนด้วยความรัก ♫' . "\n" .
|
||||
'ขอแสดงความยินดี งานแต่งพี่ Earn & Menn' ."\r\n" .
|
||||
'เที่ยวปายหน้าร้อน ก็เที่ยวได้เหมือนกันน่ะ' . "\r\n" .
|
||||
' ジョバンニはまっ赤になってうなずきました。けれどもいつかジョバンニの眼のなかには涙がいっぱいになりました。そうだ僕は知っていたのだ、もちろんカムパネルラも知っている。' ."\r\n" .
|
||||
'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore' . "\n" .
|
||||
''
|
||||
) );
|
||||
|
||||
$out = $table->getDisplayLines();
|
||||
for ( $i = 0; $i < count( $out ); $i++ ) {
|
||||
$this->assertEquals( $constraint_width, \cli\strwidth( $out[$i] ) );
|
||||
}
|
||||
|
||||
$constraint_width = 81;
|
||||
|
||||
$renderer = new cli\Table\Ascii;
|
||||
$renderer->setConstraintWidth( $constraint_width );
|
||||
$table->setRenderer( $renderer );
|
||||
|
||||
$out = $table->getDisplayLines();
|
||||
for ( $i = 0; $i < count( $out ); $i++ ) {
|
||||
$this->assertEquals( $constraint_width, \cli\strwidth( $out[$i] ) );
|
||||
}
|
||||
|
||||
$constraint_width = 200;
|
||||
|
||||
$renderer = new cli\Table\Ascii;
|
||||
$renderer->setConstraintWidth( $constraint_width );
|
||||
$table->setRenderer( $renderer );
|
||||
|
||||
$out = $table->getDisplayLines();
|
||||
for ( $i = 0; $i < count( $out ); $i++ ) {
|
||||
$this->assertEquals( $constraint_width, \cli\strwidth( $out[$i] ) );
|
||||
}
|
||||
}
|
||||
|
||||
public function test_ascii_pre_colorized_widths() {
|
||||
|
||||
Colors::enable( true );
|
||||
|
||||
$headers = array( 'package', 'version', 'result' );
|
||||
$items = array(
|
||||
array( Colors::colorize( '%ygaa/gaa-kabes%n' ), 'dev-master', Colors::colorize( "%rx%n" ) ),
|
||||
array( Colors::colorize( '%ygaa/gaa-log%n' ), '*', Colors::colorize( "%gok%n" ) ),
|
||||
array( Colors::colorize( '%ygaa/gaa-nonsense%n' ), 'v3.0.11', Colors::colorize( "%rx%n" ) ),
|
||||
array( Colors::colorize( '%ygaa/gaa-100%%new%n' ), 'v100%new', Colors::colorize( "%gok%n" ) ),
|
||||
);
|
||||
|
||||
// Disable colorization, as `\WP_CLI\Formatter::show_table()` does for Ascii tables.
|
||||
Colors::disable( true );
|
||||
$this->assertFalse( Colors::shouldColorize() );
|
||||
|
||||
// Account for colorization of columns 0 & 2.
|
||||
|
||||
$table = new Table;
|
||||
$renderer = new Ascii;
|
||||
$table->setRenderer( $renderer );
|
||||
$table->setAsciiPreColorized( array( true, false, true ) );
|
||||
$table->setHeaders( $headers );
|
||||
$table->setRows( $items );
|
||||
|
||||
$out = $table->getDisplayLines();
|
||||
|
||||
// "+ 4" accommodates 3 borders and header.
|
||||
$this->assertSame( 4 + 4, count( $out ) );
|
||||
|
||||
// Borders & header.
|
||||
$this->assertSame( 42, strlen( $out[0] ) );
|
||||
$this->assertSame( 42, strlen( $out[1] ) );
|
||||
$this->assertSame( 42, strlen( $out[2] ) );
|
||||
$this->assertSame( 42, strlen( $out[7] ) );
|
||||
|
||||
// Data.
|
||||
$this->assertSame( 60, strlen( $out[3] ) );
|
||||
$this->assertSame( 60, strlen( $out[4] ) );
|
||||
$this->assertSame( 60, strlen( $out[5] ) );
|
||||
$this->assertSame( 60, strlen( $out[6] ) );
|
||||
|
||||
// Don't account for colorization of columns 0 & 2.
|
||||
|
||||
$table = new Table;
|
||||
$renderer = new Ascii;
|
||||
$table->setRenderer( $renderer );
|
||||
$table->setHeaders( $headers );
|
||||
$table->setRows( $items );
|
||||
|
||||
$out = $table->getDisplayLines();
|
||||
|
||||
// "+ 4" accommodates 3 borders and header.
|
||||
$this->assertSame( 4 + 4, count( $out ) );
|
||||
|
||||
// Borders & header.
|
||||
$this->assertSame( 56, strlen( $out[0] ) );
|
||||
$this->assertSame( 56, strlen( $out[1] ) );
|
||||
$this->assertSame( 56, strlen( $out[2] ) );
|
||||
$this->assertSame( 56, strlen( $out[7] ) );
|
||||
|
||||
// Data.
|
||||
$this->assertSame( 56, strlen( $out[3] ) );
|
||||
$this->assertSame( 56, strlen( $out[4] ) );
|
||||
$this->assertSame( 56, strlen( $out[5] ) );
|
||||
$this->assertSame( 56, strlen( $out[6] ) );
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user