initial commit; version 22.5.12042

This commit is contained in:
2022-12-12 23:28:25 -05:00
commit af1b03d79f
17653 changed files with 22692970 additions and 0 deletions

488
libs/cli/Arguments.php Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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();
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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
View 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
View 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'));
}
}

View 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
View 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
View 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;
}
}

View 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);
}

View 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
View 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;
}
}

View 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;
}
}

View 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);
}

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