Skip to content

Commit e02568e

Browse files
authored
Merge pull request #107 from gitlost/issue_102
Add strwidth(), called by safe_str_pad(). Add unicode/regex.php.
2 parents 09075e5 + cb5c203 commit e02568e

File tree

9 files changed

+210
-16
lines changed

9 files changed

+210
-16
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.idea
22
vendor
3+
.*.swp

lib/cli/Colors.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ static public function cacheString($passed, $colorized, $colored) {
176176
* Return the length of the string without color codes.
177177
*
178178
* @param string $string the string to measure
179-
* @return string
179+
* @return int
180180
*/
181181
static public function length($string) {
182182
if (isset(self::$_string_cache[md5($string)]['decolorized'])) {
@@ -188,6 +188,23 @@ static public function length($string) {
188188
return safe_strlen($test_string);
189189
}
190190

191+
/**
192+
* Return the width (length in characters) of the string without color codes.
193+
*
194+
* @param string $string the string to measure
195+
* @return int
196+
*/
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);
206+
}
207+
191208
/**
192209
* Pad the string to a certain display length.
193210
*

lib/cli/Streams.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ public static function render( $msg ) {
3131
$args = func_get_args();
3232

3333
// No string replacement is needed
34-
if( count( $args ) == 1 ) {
35-
return Colors::colorize( $msg );
34+
if( count( $args ) == 1 || ( is_string( $args[1] ) && '' === $args[1] ) ) {
35+
return Colors::shouldColorize() ? Colors::colorize( $msg ) : $msg;
3636
}
3737

3838
// If the first argument is not an array just pass to sprintf
3939
if( !is_array( $args[1] ) ) {
4040
// Colorize the message first so sprintf doesn't bitch at us
41-
$args[0] = Colors::colorize( $args[0] );
41+
if ( Colors::shouldColorize() ) {
42+
$args[0] = Colors::colorize( $args[0] );
43+
}
4244

4345
// Escape percent characters for sprintf
4446
$args[0] = preg_replace('/(%([^\w]|$))/', "%$1", $args[0]);
@@ -50,7 +52,7 @@ public static function render( $msg ) {
5052
foreach( $args[1] as $key => $value ) {
5153
$msg = str_replace( '{:' . $key . '}', $value, $msg );
5254
}
53-
return Colors::colorize( $msg );
55+
return Colors::shouldColorize() ? Colors::colorize( $msg ) : $msg;
5456
}
5557

5658
/**

lib/cli/Table.php

Lines changed: 1 addition & 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::length($str);
105+
$width = Colors::shouldColorize() ? Colors::width($str) : strwidth($str);
106106
if (!isset($this->_width[$column]) || $width > $this->_width[$column]) {
107107
$this->_width[$column] = $width;
108108
}

lib/cli/cli.php

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,15 +203,52 @@ function safe_substr( $str, $start, $length = false ) {
203203
*/
204204
function safe_str_pad( $string, $length ) {
205205
$cleaned_string = Colors::shouldColorize() ? Colors::decolorize( $string ) : $string;
206-
// Hebrew vowel characters
207-
$cleaned_string = preg_replace( '#[\x{591}-\x{5C7}]+#u', '', $cleaned_string );
208-
if ( function_exists( 'mb_strwidth' ) && function_exists( 'mb_detect_encoding' ) ) {
209-
$real_length = mb_strwidth( $cleaned_string, mb_detect_encoding( $string ) );
210-
} else {
211-
$real_length = safe_strlen( $cleaned_string );
212-
}
206+
$real_length = strwidth( $cleaned_string );
213207
$diff = strlen( $string ) - $real_length;
214208
$length += $diff;
215209

216210
return str_pad( $string, $length );
217211
}
212+
213+
/**
214+
* 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.
215+
*
216+
* @param string The string to check
217+
* @return int The string's width.
218+
*/
219+
function strwidth( $string ) {
220+
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
221+
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.
222+
if ( null === $eaw_regex ) {
223+
// Load both regexs generated from Unicode data.
224+
require __DIR__ . '/unicode/regex.php';
225+
}
226+
227+
// Allow for selective testings - "1" bit set tests grapheme_strlen(), "2" preg_match_all( '/\X/u' ), "4" mb_strwidth(), "other" safe_strlen().
228+
$test_strwidth = getenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH' );
229+
230+
// Assume UTF-8 - `grapheme_strlen()` will return null if given non-UTF-8 string.
231+
if ( function_exists( 'grapheme_strlen' ) && null !== ( $width = grapheme_strlen( $string ) ) ) {
232+
if ( ! $test_strwidth || ( $test_strwidth & 1 ) ) {
233+
return $width + preg_match_all( $eaw_regex, $string, $dummy /*needed for PHP 5.3*/ );
234+
}
235+
}
236+
// Assume UTF-8 - `preg_match_all()` will return false if given non-UTF-8 string (or if PCRE UTF-8 mode is unavailable).
237+
if ( false !== ( $width = preg_match_all( '/\X/u', $string, $dummy /*needed for PHP 5.3*/ ) ) ) {
238+
if ( ! $test_strwidth || ( $test_strwidth & 2 ) ) {
239+
return $width + preg_match_all( $eaw_regex, $string, $dummy /*needed for PHP 5.3*/ );
240+
}
241+
}
242+
if ( function_exists( 'mb_strwidth' ) && function_exists( 'mb_detect_encoding' ) ) {
243+
$encoding = mb_detect_encoding( $string, null, true /*strict*/ );
244+
$width = mb_strwidth( $string, $encoding );
245+
if ( 'UTF-8' === $encoding ) {
246+
// Subtract combining characters.
247+
$width -= preg_match_all( $m_regex, $string, $dummy /*needed for PHP 5.3*/ );
248+
}
249+
if ( ! $test_strwidth || ( $test_strwidth & 4 ) ) {
250+
return $width;
251+
}
252+
}
253+
return safe_strlen( $string );
254+
}

lib/cli/table/Ascii.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,14 @@ public function row( array $row ) {
133133
$value = str_replace( PHP_EOL, ' ', $value );
134134

135135
$col_width = $this->_widths[ $col ];
136-
$original_val_width = Colors::length( $value );
136+
$original_val_width = Colors::shouldColorize() ? Colors::width( $value ) : \cli\strwidth( $value );
137137
if ( $original_val_width > $col_width ) {
138138
$row[ $col ] = \cli\safe_substr( $value, 0, $col_width );
139139
$value = \cli\safe_substr( $value, $col_width, $original_val_width );
140140
$i = 0;
141141
do {
142142
$extra_value = \cli\safe_substr( $value, 0, $col_width );
143-
$val_width = \cli\safe_strlen( $extra_value );
143+
$val_width = \cli\strwidth( $extra_value );
144144
if ( $val_width ) {
145145
$extra_rows[ $col ][] = $extra_value;
146146
$value = \cli\safe_substr( $value, $col_width, $original_val_width );

lib/cli/unicode/regex.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
// Generated by "gen_east_asian_width.php" from "http://www.unicode.org/Public/10.0.0/ucd/EastAsianWidth.txt".
3+
$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.
4+
5+
// Generated by "gen_cat_regex_alts.php" from "http://www.unicode.org/Public/10.0.0/ucd/UnicodeData.txt".
6+
$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.

tests/test-cli.php

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ function setUp() {
1212

1313
function test_string_length() {
1414
$this->assertEquals( \cli\Colors::length( 'x' ), 1 );
15+
$this->assertEquals( \cli\Colors::length( '' ), 1 );
16+
}
17+
18+
function test_string_width() {
19+
$this->assertEquals( \cli\Colors::width( 'x' ), 1 );
20+
$this->assertEquals( \cli\Colors::width( '' ), 2 ); // Double-width char.
1521
}
1622

1723
function test_encoded_string_length() {
@@ -22,6 +28,14 @@ function test_encoded_string_length() {
2228

2329
}
2430

31+
function test_encoded_string_width() {
32+
33+
$this->assertEquals( \cli\Colors::width( 'hello' ), 5 );
34+
$this->assertEquals( \cli\Colors::width( 'óra' ), 3 );
35+
$this->assertEquals( \cli\Colors::width( '日本語' ), 6 ); // 3 double-width chars.
36+
37+
}
38+
2539
function test_encoded_string_pad() {
2640

2741
$this->assertEquals( 6, strlen( \cli\Colors::pad( 'hello', 6 ) ) );
@@ -45,6 +59,12 @@ function test_encoded_substr() {
4559

4660
function test_colorized_string_length() {
4761
$this->assertEquals( \cli\Colors::length( \cli\Colors::colorize( '%Gx%n', true ) ), 1 );
62+
$this->assertEquals( \cli\Colors::length( \cli\Colors::colorize( '%G日%n', true ) ), 1 );
63+
}
64+
65+
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.
4868
}
4969

5070
function test_colorize_string_is_colored() {
@@ -95,4 +115,94 @@ function test_string_cache() {
95115
// Test that the cache value is correctly set
96116
$this->assertEquals( $test_cache, $real_cache[ md5( $string_with_color ) ] );
97117
}
98-
}
118+
119+
function test_strwidth() {
120+
// Save.
121+
$test_strwidth = getenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH' );
122+
if ( function_exists( 'mb_detect_order' ) ) {
123+
$mb_detect_order = mb_detect_order();
124+
}
125+
126+
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH' );
127+
128+
// UTF-8.
129+
130+
// 4 characters, one a double-width Han = 5 spacing chars, with 2 combining chars. Adapted from http://unicode.org/faq/char_combmark.html#7 (combining acute accent added after "a").
131+
$str = "a\xCC\x81\xE0\xA4\xA8\xE0\xA4\xBF\xE4\xBA\x9C\xF0\x90\x82\x83";
132+
133+
if ( function_exists( 'grapheme_strlen' ) ) {
134+
$this->assertSame( 5, \cli\strwidth( $str ) ); // Tests grapheme_strlen().
135+
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH=2' ); // Test preg_match_all( '/\X/u' ).
136+
$this->assertSame( 5, \cli\strwidth( $str ) );
137+
} else {
138+
$this->assertSame( 5, \cli\strwidth( $str ) ); // Tests preg_match_all( '/\X/u' ).
139+
}
140+
141+
if ( function_exists( 'mb_strwidth' ) && function_exists( 'mb_detect_order' ) ) {
142+
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH=4' ); // Test mb_strwidth().
143+
mb_detect_order( array( 'UTF-8', 'ISO-8859-1' ) );
144+
$this->assertSame( 5, \cli\strwidth( $str ) );
145+
}
146+
147+
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH=8' ); // Test safe_strlen().
148+
if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_detect_order' ) ) {
149+
$this->assertSame( 6, \cli\strwidth( $str ) ); // mb_strlen() - counts the 2 combining chars but not the double-width Han so out by 1.
150+
$this->assertSame( 6, mb_strlen( $str, 'UTF-8' ) );
151+
} else {
152+
$this->assertSame( 16, \cli\strwidth( $str ) ); // strlen() - no. of bytes.
153+
$this->assertSame( 16, strlen( $str ) );
154+
}
155+
156+
// Nepali जस्ट ट॓स्ट गर्दै - 1st word: 3 spacing + 1 combining, 2nd word: 3 spacing + 2 combining, 3rd word: 3 spacing + 2 combining = 9 spacing chars + 2 spaces = 11 chars.
157+
$str = "\xe0\xa4\x9c\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\x9f \xe0\xa4\x9f\xe0\xa5\x93\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\x9f \xe0\xa4\x97\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa6\xe0\xa5\x88";
158+
159+
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH' );
160+
161+
if ( function_exists( 'grapheme_strlen' ) ) {
162+
$this->assertSame( 11, \cli\strwidth( $str ) ); // Tests grapheme_strlen().
163+
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH=2' ); // Test preg_match_all( '/\X/u' ).
164+
$this->assertSame( 11, \cli\strwidth( $str ) );
165+
} else {
166+
$this->assertSame( 11, \cli\strwidth( $str ) ); // Tests preg_match_all( '/\X/u' ).
167+
}
168+
169+
if ( function_exists( 'mb_strwidth' ) && function_exists( 'mb_detect_order' ) ) {
170+
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH=4' ); // Test mb_strwidth().
171+
mb_detect_order( array( 'UTF-8' ) );
172+
$this->assertSame( 11, \cli\strwidth( $str ) );
173+
}
174+
175+
// Non-UTF-8 - both grapheme_strlen() and preg_match_all( '/\X/u' ) will fail.
176+
177+
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH' );
178+
179+
if ( function_exists( 'mb_strwidth' ) && function_exists( 'mb_detect_order' ) ) {
180+
// Latin-1
181+
mb_detect_order( array( 'UTF-8', 'ISO-8859-1' ) );
182+
$str = "\xe0b\xe7"; // "àbç" in ISO-8859-1
183+
$this->assertSame( 3, \cli\strwidth( $str ) ); // Test mb_strwidth().
184+
$this->assertSame( 3, mb_strwidth( $str, 'ISO-8859-1' ) );
185+
186+
// Shift JIS.
187+
mb_detect_order( array( 'UTF-8', 'SJIS' ) );
188+
$str = "\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd\x90\xa2\x8a\x45!"; // "こャにちは世界!" ("Hello world!") in Shift JIS - 7 double-width chars plus Latin exclamation mark.
189+
$this->assertSame( 15, \cli\strwidth( $str ) ); // Test mb_strwidth().
190+
$this->assertSame( 15, mb_strwidth( $str, 'SJIS' ) );
191+
192+
putenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH=8' ); // Test safe_strlen().
193+
if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_detect_order' ) ) {
194+
$this->assertSame( 8, \cli\strwidth( $str ) ); // mb_strlen() - doesn't allow for double-width.
195+
$this->assertSame( 8, mb_strlen( $str, 'SJIS' ) );
196+
} else {
197+
$this->assertSame( 15, \cli\strwidth( $str ) ); // strlen() - no. of bytes.
198+
$this->assertSame( 15, strlen( $str ) );
199+
}
200+
}
201+
202+
// Restore.
203+
putenv( false == $test_strwidth ? 'PHP_CLI_TOOLS_TEST_STRWIDTH' : "PHP_CLI_TOOLS_TEST_STRWIDTH=$test_strwidth" );
204+
if ( function_exists( 'mb_detect_order' ) ) {
205+
mb_detect_order( $mb_detect_order );
206+
}
207+
}
208+
}

tests/test-table-ascii.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,27 @@ public function testDrawOneColumnColoredTable() {
8888
| $x |
8989
+-------------+
9090
91+
OUT;
92+
$this->assertInOutEquals(array($headers, $rows), $output);
93+
}
94+
95+
/**
96+
* Check it works with colors disabled.
97+
*/
98+
public function testDrawOneColumnColorDisabledTable() {
99+
Colors::disable( true );
100+
$this->assertFalse( Colors::shouldColorize() );
101+
$headers = array('Test Header');
102+
$rows = array(
103+
array('%Gx%n'),
104+
);
105+
$output = <<<OUT
106+
+-------------+
107+
| Test Header |
108+
+-------------+
109+
| %Gx%n |
110+
+-------------+
111+
91112
OUT;
92113
$this->assertInOutEquals(array($headers, $rows), $output);
93114
}

0 commit comments

Comments
 (0)