Skip to content

Commit c3a01e7

Browse files
Merge pull request #60 from tollmanz/colorized-string-length
Colors::length returns incorrect lengths for colorized strings
2 parents 4a1f4cd + 402ff3f commit c3a01e7

2 files changed

Lines changed: 164 additions & 25 deletions

File tree

lib/cli/Colors.php

Lines changed: 100 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class Colors {
5050
);
5151
static protected $_enabled = null;
5252

53+
static protected $_string_cache = array();
54+
5355
static public function enable($force = true) {
5456
self::$_enabled = $force === true ? true : null;
5557
}
@@ -111,7 +113,97 @@ static public function color($color) {
111113
* @return string
112114
*/
113115
static public function colorize($string, $colored = null) {
114-
static $conversions = array(
116+
$passed = $string;
117+
118+
if (isset(self::$_string_cache[md5($passed)]['colorized'])) {
119+
return self::$_string_cache[md5($passed)]['colorized'];
120+
}
121+
122+
if (!self::shouldColorize($colored)) {
123+
$return = preg_replace('/%((%)|.)/', '$2', $string);
124+
self::cacheString($passed, $return, $colored);
125+
return $return;
126+
}
127+
128+
$string = str_replace('%%', '% ', $string);
129+
130+
foreach (self::getColors() as $key => $value) {
131+
$string = str_replace($key, self::color($value), $string);
132+
}
133+
134+
$string = str_replace('% ', '%', $string);
135+
self::cacheString($passed, $string, $colored);
136+
137+
return $string;
138+
}
139+
140+
/**
141+
* Remove color information from a string.
142+
*
143+
* @param string $string A string with color information.
144+
* @return string A string with color information removed.
145+
*/
146+
static public function decolorize($string) {
147+
// Get rid of color tokens if they exist
148+
$string = str_replace(array_keys(self::getColors()), '', $string);
149+
150+
// Remove color encoding if it exists
151+
foreach (self::getColors() as $key => $value) {
152+
$string = str_replace(self::color($value), '', $string);
153+
}
154+
155+
return $string;
156+
}
157+
158+
/**
159+
* Cache the original, colorized, and decolorized versions of a string.
160+
*
161+
* @param string $passed The original string before colorization.
162+
* @param string $colorized The string after running through self::colorize.
163+
* @param string $colored The string without any color information.
164+
*/
165+
static public function cacheString($passed, $colorized, $colored) {
166+
self::$_string_cache[md5($passed)] = array(
167+
'passed' => $passed,
168+
'colorized' => $colorized,
169+
'decolorized' => self::decolorize($passed)
170+
);
171+
}
172+
173+
/**
174+
* Return the length of the string without color codes.
175+
*
176+
* @param string $string the string to measure
177+
* @return string
178+
*/
179+
static public function length($string) {
180+
if (isset(self::$_string_cache[md5($string)]['decolorized'])) {
181+
$test_string = self::$_string_cache[md5($string)]['decolorized'];
182+
} else {
183+
$test_string = self::decolorize($string);
184+
}
185+
186+
return safe_strlen($test_string);
187+
}
188+
189+
/**
190+
* Pad the string to a certain display length.
191+
*
192+
* @param string $string the string to pad
193+
* @param integer $length the display length
194+
* @return string
195+
*/
196+
static public function pad($string, $length) {
197+
return safe_str_pad( $string, $length );
198+
}
199+
200+
/**
201+
* Get the color mapping array.
202+
*
203+
* @return array Array of color tokens mapped to colors and styles.
204+
*/
205+
static public function getColors() {
206+
return array(
115207
'%y' => array('color' => 'yellow'),
116208
'%g' => array('color' => 'green'),
117209
'%b' => array('color' => 'blue'),
@@ -146,38 +238,21 @@ static public function colorize($string, $colored = null) {
146238
'%9' => array('style' => 'bright'),
147239
'%_' => array('style' => 'bright')
148240
);
149-
150-
if (!self::shouldColorize($colored)) {
151-
return preg_replace('/%((%)|.)/', '$2', $string);
152-
}
153-
154-
$string = str_replace('%%', '% ', $string);
155-
156-
foreach ($conversions as $key => $value) {
157-
$string = str_replace($key, self::color($value), $string);
158-
}
159-
160-
return str_replace('% ', '%', $string);
161241
}
162242

163243
/**
164-
* Return the length of the string without color codes.
244+
* Get the cached string values.
165245
*
166-
* @param string $string the string to measure
167-
* @return string
246+
* @return array The cached string values.
168247
*/
169-
static public function length($string) {
170-
return safe_strlen(self::colorize($string, false));
248+
static public function getStringCache() {
249+
return self::$_string_cache;
171250
}
172251

173252
/**
174-
* Pad the string to a certain display length.
175-
*
176-
* @param string $string the string to pad
177-
* @param integer $length the display length
178-
* @return string
253+
* Clear the string cache.
179254
*/
180-
static public function pad($string, $length) {
181-
return safe_str_pad( $string, $length );
255+
static public function clearStringCache() {
256+
self::$_string_cache = array();
182257
}
183258
}

tests/test-cli.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
class testsCli extends PHPUnit_Framework_TestCase {
44

5+
function setUp() {
6+
// Reset enable state
7+
\cli\Colors::enable( null );
8+
9+
// Empty the cache
10+
\cli\Colors::clearStringCache();
11+
}
12+
13+
function test_string_length() {
14+
$this->assertEquals( \cli\Colors::length( 'x' ), 1 );
15+
}
16+
517
function test_encoded_string_length() {
618

719
$this->assertEquals( \cli\Colors::length( 'hello' ), 5 );
@@ -12,4 +24,56 @@ function test_encoded_string_length() {
1224

1325
}
1426

27+
function test_colorized_string_length() {
28+
$this->assertEquals( \cli\Colors::length( \cli\Colors::colorize( '%Gx%n', true ) ), 1 );
29+
}
30+
31+
function test_colorize_string_is_colored() {
32+
$original = '%Gx';
33+
$colorized = "\033[32;1mx";
34+
35+
$this->assertEquals( \cli\Colors::colorize( $original, true ), $colorized );
36+
}
37+
38+
function test_colorize_when_colorize_is_forced() {
39+
$original = '%gx%n';
40+
41+
$this->assertEquals( \cli\Colors::colorize( $original, false ), 'x' );
42+
}
43+
44+
function test_binary_string_is_converted_back_to_original_string() {
45+
$string = 'x';
46+
$string_with_color = '%b' . $string;
47+
$colorized_string = "\033[34m$string";
48+
49+
// Ensure colorization is applied correctly
50+
$this->assertEquals( \cli\Colors::colorize( $string_with_color, true ), $colorized_string );
51+
52+
// Ensure that the colorization is reverted
53+
$this->assertEquals( \cli\Colors::decolorize( $colorized_string ), $string );
54+
}
55+
56+
function test_string_cache() {
57+
$string = 'x';
58+
$string_with_color = '%k' . $string;
59+
$colorized_string = "\033[30m$string";
60+
61+
// Ensure colorization works
62+
$this->assertEquals( \cli\Colors::colorize( $string_with_color, true ), $colorized_string );
63+
64+
// Test that the value was cached appropriately
65+
$test_cache = array(
66+
'passed' => $string_with_color,
67+
'colorized' => $colorized_string,
68+
'decolorized' => $string,
69+
);
70+
71+
$real_cache = \cli\Colors::getStringCache();
72+
73+
// Test that the cache value exists
74+
$this->assertTrue( isset( $real_cache[ md5( $string_with_color ) ] ) );
75+
76+
// Test that the cache value is correctly set
77+
$this->assertEquals( $test_cache, $real_cache[ md5( $string_with_color ) ] );
78+
}
1579
}

0 commit comments

Comments
 (0)