initial commit; version 22.5.12042
This commit is contained in:
488
libs/cli/Arguments.php
Normal file
488
libs/cli/Arguments.php
Normal file
@ -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
libs/cli/Colors.php
Normal file
279
libs/cli/Colors.php
Normal file
@ -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();
|
||||
}
|
||||
}
|
19
libs/cli/LICENSE
Normal file
19
libs/cli/LICENSE
Normal file
@ -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.
|
44
libs/cli/Memoize.php
Normal file
44
libs/cli/Memoize.php
Normal file
@ -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
libs/cli/Notify.php
Normal file
185
libs/cli/Notify.php
Normal file
@ -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
libs/cli/Progress.php
Normal file
134
libs/cli/Progress.php
Normal file
@ -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);
|
||||
}
|
||||
}
|
121
libs/cli/Shell.php
Executable file
121
libs/cli/Shell.php
Executable file
@ -0,0 +1,121 @@
|
||||
<?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 {
|
||||
if ( function_exists('stream_isatty') ) {
|
||||
return !stream_isatty(STDOUT);
|
||||
} 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';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
283
libs/cli/Streams.php
Executable file
283
libs/cli/Streams.php
Executable file
@ -0,0 +1,283 @@
|
||||
<?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() {
|
||||
if ( function_exists('stream_isatty') ) {
|
||||
return !stream_isatty(static::$out);
|
||||
} else {
|
||||
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
libs/cli/Table.php
Normal file
257
libs/cli/Table.php
Normal file
@ -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
libs/cli/Tree.php
Normal file
69
libs/cli/Tree.php
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
137
libs/cli/arguments/Argument.php
Normal file
137
libs/cli/arguments/Argument.php
Normal file
@ -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;
|
||||
}
|
||||
}
|
126
libs/cli/arguments/HelpScreen.php
Normal file
126
libs/cli/arguments/HelpScreen.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?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("\n\n", $help);
|
||||
}
|
||||
|
||||
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("\n", $help);
|
||||
}
|
||||
|
||||
private function _consume($options) {
|
||||
$max = 0;
|
||||
$out = array();
|
||||
|
||||
foreach ($options as $option => $settings) {
|
||||
if ($option[0] === '-') {
|
||||
$names = array($option);
|
||||
} else {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
43
libs/cli/arguments/InvalidArguments.php
Normal file
43
libs/cli/arguments/InvalidArguments.php
Normal file
@ -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);
|
||||
}
|
||||
}
|
123
libs/cli/arguments/Lexer.php
Normal file
123
libs/cli/arguments/Lexer.php
Normal file
@ -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
libs/cli/cli.php
Executable file
415
libs/cli/cli.php
Executable file
@ -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, );
|
||||
}
|
66
libs/cli/notify/Dots.php
Normal file
66
libs/cli/notify/Dots.php
Normal file
@ -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'));
|
||||
}
|
||||
}
|
45
libs/cli/notify/Spinner.php
Normal file
45
libs/cli/notify/Spinner.php
Normal file
@ -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'));
|
||||
}
|
||||
}
|
85
libs/cli/progress/Bar.php
Normal file
85
libs/cli/progress/Bar.php
Normal file
@ -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
libs/cli/table/Ascii.php
Normal file
227
libs/cli/table/Ascii.php
Normal file
@ -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;
|
||||
}
|
||||
}
|
57
libs/cli/table/Renderer.php
Normal file
57
libs/cli/table/Renderer.php
Normal file
@ -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);
|
||||
}
|
28
libs/cli/table/Tabular.php
Normal file
28
libs/cli/table/Tabular.php
Normal file
@ -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));
|
||||
}
|
||||
}
|
41
libs/cli/tree/Ascii.php
Normal file
41
libs/cli/tree/Ascii.php
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
70
libs/cli/tree/Markdown.php
Normal file
70
libs/cli/tree/Markdown.php
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
26
libs/cli/tree/Renderer.php
Normal file
26
libs/cli/tree/Renderer.php
Normal file
@ -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);
|
||||
|
||||
}
|
6
libs/cli/unicode/regex.php
Normal file
6
libs/cli/unicode/regex.php
Normal file
@ -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.
|
Reference in New Issue
Block a user