Skip to content

Commit 82462ec

Browse files
committed
Various half-worked fixes relating to #106 and PR #109.
1 parent e02568e commit 82462ec

6 files changed

Lines changed: 245 additions & 47 deletions

File tree

lib/cli/Colors.php

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ static public function color($color) {
9191

9292
$colors = array();
9393
foreach (array('color', 'style', 'background') as $type) {
94-
$code = @$color[$type];
94+
$code = $color[$type];
9595
if (isset(self::$_colors[$type][$code])) {
9696
$colors[] = self::$_colors[$type][$code];
9797
}
@@ -143,11 +143,16 @@ static public function colorize($string, $colored = null) {
143143
* Remove color information from a string.
144144
*
145145
* @param string $string A string with color information.
146+
* @param bool $keep_tokens Optional. If set, color tokens (eg "%n") won't be stripped. Default false.
146147
* @return string A string with color information removed.
147148
*/
148-
static public function decolorize($string) {
149-
// Get rid of color tokens if they exist
150-
$string = str_replace(array_keys(self::getColors()), '', $string);
149+
static public function decolorize( $string, bool $keep_tokens = false ) {
150+
if ( ! $keep_tokens ) {
151+
// Get rid of color tokens if they exist
152+
$string = str_replace('%%', '', $string);
153+
$string = str_replace(array_keys(self::getColors()), '', $string);
154+
$string = str_replace('', '%', $string);
155+
}
151156

152157
// Remove color encoding if it exists
153158
foreach (self::getColors() as $key => $value) {
@@ -179,41 +184,34 @@ static public function cacheString($passed, $colorized, $colored) {
179184
* @return int
180185
*/
181186
static public function length($string) {
182-
if (isset(self::$_string_cache[md5($string)]['decolorized'])) {
183-
$test_string = self::$_string_cache[md5($string)]['decolorized'];
184-
} else {
185-
$test_string = self::decolorize($string);
186-
}
187-
188-
return safe_strlen($test_string);
187+
return safe_strlen( self::decolorize( $string ) );
189188
}
190189

191190
/**
192-
* Return the width (length in characters) of the string without color codes.
191+
* Return the width (length in characters) of the string without color codes if enabled.
193192
*
194-
* @param string $string the string to measure
193+
* @param string $string The string to measure.
194+
* @param bool $pre_colorized Optional. Set if the string is pre-colorized. Default false.
195195
* @return int
196196
*/
197-
static public function width($string) {
198-
$md5 = md5($string);
199-
if (isset(self::$_string_cache[$md5]['decolorized'])) {
200-
$test_string = self::$_string_cache[$md5]['decolorized'];
201-
} else {
202-
$test_string = self::decolorize($string);
203-
}
204-
205-
return strwidth($test_string);
197+
static public function width( $string, bool $pre_colorized = false ) {
198+
return strwidth( $pre_colorized || self::shouldColorize() ? self::decolorize( $string, $pre_colorized /*keep_tokens*/ ) : $string );
206199
}
207200

208201
/**
209202
* Pad the string to a certain display length.
210203
*
211-
* @param string $string the string to pad
212-
* @param integer $length the display length
204+
* @param string $string The string to pad.
205+
* @param int $length The display length.
206+
* @param bool $pre_colorized Optional. Set if the string is pre-colorized. Default false.
213207
* @return string
214208
*/
215-
static public function pad($string, $length) {
216-
return safe_str_pad( $string, $length );
209+
static public function pad( $string, $length, bool $pre_colorized = false ) {
210+
$real_length = self::width( $string, $pre_colorized );
211+
$diff = strlen( $string ) - $real_length;
212+
$length += $diff;
213+
214+
return str_pad( $string, $length );
217215
}
218216

219217
/**

lib/cli/Table.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public function setRenderer(Renderer $renderer) {
102102
*/
103103
protected function checkRow(array $row) {
104104
foreach ($row as $column => $str) {
105-
$width = Colors::shouldColorize() ? Colors::width($str) : strwidth($str);
105+
$width = Colors::width( $str, $this->isAsciiPreColorized( $column ) );
106106
if (!isset($this->_width[$column]) || $width > $this->_width[$column]) {
107107
$this->_width[$column] = $width;
108108
}
@@ -228,4 +228,30 @@ public function setRows(array $rows) {
228228
public function countRows() {
229229
return count($this->_rows);
230230
}
231+
232+
/**
233+
* Set whether items in an Ascii table are pre-colorized.
234+
*
235+
* @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.
236+
* @see cli\Ascii::setPreColorized()
237+
*/
238+
public function setAsciiPreColorized( $pre_colorized ) {
239+
if ( $this->_renderer instanceof Ascii ) {
240+
$this->_renderer->setPreColorized( $pre_colorized );
241+
}
242+
}
243+
244+
/**
245+
* Is a column in an Ascii table pre-colorized?
246+
*
247+
* @param int $column Column index to check.
248+
* @return bool True if whole Ascii table is marked as pre-colorized, or if the individual column is pre-colorized; else false.
249+
* @see cli\Ascii::isPreColorized()
250+
*/
251+
private function isAsciiPreColorized( int $column ) {
252+
if ( $this->_renderer instanceof Ascii ) {
253+
return $this->_renderer->isPreColorized( $column );
254+
}
255+
return false;
256+
}
231257
}

lib/cli/cli.php

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,29 @@ function safe_strlen( $str ) {
176176
* Attempts an encoding-safe way of getting a substring. If mb_string extensions aren't
177177
* installed, falls back to ascii substring if no encoding is present
178178
*
179-
* @param string $str The input string
180-
* @param int $start The starting position of the substring
181-
* @param boolean $length Maximum length of the substring
182-
* @return string Substring of string specified by start and length parameters
179+
* @param string $str The input string.
180+
* @param int $start The starting position of the substring.
181+
* @param int|boolean $length Optional. Maximum length of the substring. Default false but should set to null for `substr()` compat behavior.
182+
* @param boolean $width Optional. If set and encoding is UTF-8, $length is interpreted as spacing width. Default false.
183+
* @return string Substring of string specified by start and length parameters
183184
*/
184-
function safe_substr( $str, $start, $length = false ) {
185+
function safe_substr( $str, $start, $length = false, $width = false ) {
185186
if ( function_exists( 'mb_substr' ) && function_exists( 'mb_detect_encoding' ) ) {
186-
$substr = mb_substr( $str, $start, $length, mb_detect_encoding( $str ) );
187+
$encoding = mb_detect_encoding( $str );
188+
if ( false !== $width && 'UTF-8' === $encoding ) {
189+
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
190+
if ( null === $eaw_regex ) {
191+
// Load both regexs generated from Unicode data.
192+
require __DIR__ . '/unicode/regex.php';
193+
}
194+
$cnt = preg_match_all( '/[\x00-\x7f\xc2-\xf4][^\x00-\x7f\xc2-\xf4]*/', $str, $matches );
195+
$width = $length;
196+
197+
for ( $length = 0; $length < $cnt && $width > 0; $length++ ) {
198+
$width -= preg_match( $eaw_regex, $matches[0][ $length ] ) ? 2 : 1;
199+
}
200+
}
201+
$substr = mb_substr( $str, $start, $length, $encoding );
187202
} else {
188203
// iconv will return PHP notice if non-ascii characters are present in input string
189204
$str = iconv( 'ASCII' , 'ASCII', $str );
@@ -202,8 +217,7 @@ function safe_substr( $str, $start, $length = false ) {
202217
* @return string
203218
*/
204219
function safe_str_pad( $string, $length ) {
205-
$cleaned_string = Colors::shouldColorize() ? Colors::decolorize( $string ) : $string;
206-
$real_length = strwidth( $cleaned_string );
220+
$real_length = strwidth( $string );
207221
$diff = strlen( $string ) - $real_length;
208222
$length += $diff;
209223

lib/cli/table/Ascii.php

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class Ascii extends Renderer {
2727
);
2828
protected $_border = null;
2929
protected $_constraintWidth = null;
30+
protected $_pre_colorized = false;
3031

3132
/**
3233
* Set the widths of each column in the table.
@@ -133,17 +134,17 @@ public function row( array $row ) {
133134
$value = str_replace( PHP_EOL, ' ', $value );
134135

135136
$col_width = $this->_widths[ $col ];
136-
$original_val_width = Colors::shouldColorize() ? Colors::width( $value ) : \cli\strwidth( $value );
137+
$original_val_width = Colors::width( $value, self::isPreColorized( $col ) );
137138
if ( $original_val_width > $col_width ) {
138-
$row[ $col ] = \cli\safe_substr( $value, 0, $col_width );
139-
$value = \cli\safe_substr( $value, $col_width, $original_val_width );
139+
$row[ $col ] = \cli\safe_substr( $value, 0, $col_width, true /*width*/ );
140+
$value = \cli\safe_substr( $value, \cli\safe_strlen( $row[ $col ] ), null );
140141
$i = 0;
141142
do {
142-
$extra_value = \cli\safe_substr( $value, 0, $col_width );
143-
$val_width = \cli\strwidth( $extra_value );
143+
$extra_value = \cli\safe_substr( $value, 0, $col_width, true /*width*/ );
144+
$val_width = Colors::width( $extra_value, self::isPreColorized( $col ) );
144145
if ( $val_width ) {
145146
$extra_rows[ $col ][] = $extra_value;
146-
$value = \cli\safe_substr( $value, $col_width, $original_val_width );
147+
$value = \cli\safe_substr( $value, \cli\safe_strlen( $extra_value ), null );
147148
$i++;
148149
if ( $i > $extra_row_count ) {
149150
$extra_row_count = $i;
@@ -188,6 +189,31 @@ public function row( array $row ) {
188189
}
189190

190191
private function padColumn($content, $column) {
191-
return $this->_characters['padding'] . Colors::pad($content, $this->_widths[$column]) . $this->_characters['padding'];
192+
return $this->_characters['padding'] . Colors::pad( $content, $this->_widths[ $column ], $this->isPreColorized( $column ) ) . $this->_characters['padding'];
193+
}
194+
195+
/**
196+
* Set whether items are pre-colorized.
197+
*
198+
* @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.
199+
*/
200+
public function setPreColorized( $pre_colorized ) {
201+
$this->_pre_colorized = $pre_colorized;
202+
}
203+
204+
/**
205+
* Is a column pre-colorized?
206+
*
207+
* @param int $column Column index to check.
208+
* @return bool True if whole table is marked as pre-colorized, or if the individual column is pre-colorized; else false.
209+
*/
210+
public function isPreColorized( int $column ) {
211+
if ( is_bool( $this->_pre_colorized ) ) {
212+
return $this->_pre_colorized;
213+
}
214+
if ( is_array( $this->_pre_colorized ) && isset( $this->_pre_colorized[ $column ] ) ) {
215+
return $this->_pre_colorized[ $column ];
216+
}
217+
return false;
192218
}
193219
}

tests/test-cli.php

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,30 @@ function test_encoded_string_pad() {
4545
}
4646

4747
function test_colorized_string_pad() {
48-
$this->assertEquals( 22, strlen( \cli\Colors::pad( \cli\Colors::colorize( "%Gx%n", true ), 11 ))); // colorized `x` string
49-
$this->assertEquals( 23, strlen( \cli\Colors::pad( \cli\Colors::colorize( "%Góra%n", true ), 11 ))); // colorized `óra` string
48+
// Colors enabled.
49+
50+
$colorized = \cli\Colors::colorize( '%Gx%n', true ); // colorized `x` string
51+
$this->assertSame( 22, strlen( \cli\Colors::pad( $colorized, 11 ) ) );
52+
$this->assertSame( 22, strlen( \cli\Colors::pad( $colorized, 11, false /*pre_colorized*/ ) ) );
53+
$this->assertSame( 22, strlen( \cli\Colors::pad( $colorized, 11, true /*pre_colorized*/ ) ) );
54+
55+
$colorized = \cli\Colors::colorize( "%Góra%n", true ); // colorized `óra` string
56+
$this->assertSame( 23, strlen( \cli\Colors::pad( $colorized, 11 ) ) );
57+
$this->assertSame( 23, strlen( \cli\Colors::pad( $colorized, 11, false /*pre_colorized*/ ) ) );
58+
$this->assertSame( 23, strlen( \cli\Colors::pad( $colorized, 11, true /*pre_colorized*/ ) ) );
59+
60+
// Colors disabled.
61+
\cli\Colors::disable( true );
62+
63+
$colorized = \cli\Colors::colorize( '%Gx%n', true ); // colorized `x` string
64+
$this->assertSame( 12, strlen( \cli\Colors::pad( $colorized, 12 ) ) );
65+
$this->assertSame( 12, strlen( \cli\Colors::pad( $colorized, 12, false /*pre_colorized*/ ) ) );
66+
$this->assertSame( 23, strlen( \cli\Colors::pad( $colorized, 12, true /*pre_colorized*/ ) ) );
67+
68+
$colorized = \cli\Colors::colorize( "%Góra%n", true ); // colorized `óra` string
69+
$this->assertSame( 16, strlen( \cli\Colors::pad( $colorized, 15 ) ) );
70+
$this->assertSame( 16, strlen( \cli\Colors::pad( $colorized, 15, false /*pre_colorized*/ ) ) );
71+
$this->assertSame( 27, strlen( \cli\Colors::pad( $colorized, 15, true /*pre_colorized*/ ) ) );
5072
}
5173

5274
function test_encoded_substr() {
@@ -63,8 +85,30 @@ function test_colorized_string_length() {
6385
}
6486

6587
function test_colorized_string_width() {
66-
$this->assertEquals( \cli\Colors::width( \cli\Colors::colorize( '%Gx%n', true ) ), 1 );
67-
$this->assertEquals( \cli\Colors::width( \cli\Colors::colorize( '%G日%n', true ) ), 2 ); // Double-width char.
88+
// Colors enabled.
89+
90+
$colorized = \cli\Colors::colorize( '%Gx%n', true );
91+
$this->assertSame( 1, \cli\Colors::width( $colorized ) );
92+
$this->assertSame( 1, \cli\Colors::width( $colorized, false /*pre_colorized*/ ) );
93+
$this->assertSame( 1, \cli\Colors::width( $colorized, true /*pre_colorized*/ ) );
94+
95+
$colorized = \cli\Colors::colorize( '%G日%n', true ); // Double-width char.
96+
$this->assertSame( 2, \cli\Colors::width( $colorized ) );
97+
$this->assertSame( 2, \cli\Colors::width( $colorized, false /*pre_colorized*/ ) );
98+
$this->assertSame( 2, \cli\Colors::width( $colorized, true /*pre_colorized*/ ) );
99+
100+
// Colors disabled.
101+
\cli\Colors::disable( true );
102+
103+
$colorized = \cli\Colors::colorize( '%Gx%n', true );
104+
$this->assertSame( 12, \cli\Colors::width( $colorized ) );
105+
$this->assertSame( 12, \cli\Colors::width( $colorized, false /*pre_colorized*/ ) );
106+
$this->assertSame( 1, \cli\Colors::width( $colorized, true /*pre_colorized*/ ) );
107+
108+
$colorized = \cli\Colors::colorize( '%G日%n', true ); // Double-width char.
109+
$this->assertSame( 13, \cli\Colors::width( $colorized ) );
110+
$this->assertSame( 13, \cli\Colors::width( $colorized, false /*pre_colorized*/ ) );
111+
$this->assertSame( 2, \cli\Colors::width( $colorized, true /*pre_colorized*/ ) );
68112
}
69113

70114
function test_colorize_string_is_colored() {

0 commit comments

Comments
 (0)