Skip to content

Commit

Permalink
Dumper: improved encoding of strings, added colors
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Mar 24, 2022
1 parent b54326b commit 2768330
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 38 deletions.
102 changes: 71 additions & 31 deletions src/Framework/Dumper.php
Expand Up @@ -33,17 +33,6 @@ class Dumper
*/
public static function toLine($var): string
{
static $table;
if ($table === null) {
foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) {
$table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
}
$table['\\'] = '\\\\';
$table["\r"] = '\r';
$table["\n"] = '\n';
$table["\t"] = '\t';
}

if (is_bool($var)) {
return $var ? 'TRUE' : 'FALSE';

Expand All @@ -62,9 +51,7 @@ public static function toLine($var): string
} elseif (strlen($var) > self::$maxLength) {
$var = substr($var, 0, self::$maxLength) . '...';
}
return preg_match('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error()
? '"' . strtr($var, $table) . '"'
: "'$var'";
return self::encodeStringLine($var);

} elseif (is_array($var)) {
$out = '';
Expand Down Expand Up @@ -146,20 +133,10 @@ private static function _toPhp(&$var, array &$list = [], int $level = 0, int &$l
} elseif ($var === null) {
return 'null';

} elseif (is_string($var) && (preg_match('#[^\x09\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error())) {
static $table;
if ($table === null) {
foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) {
$table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
}
$table['\\'] = '\\\\';
$table["\r"] = '\r';
$table["\n"] = '\n';
$table["\t"] = '\t';
$table['$'] = '\$';
$table['"'] = '\"';
}
return '"' . strtr($var, $table) . '"';
} elseif (is_string($var)) {
$res = self::encodeStringPhp($var);
$line += substr_count($res, "\n");
return $res;

} elseif (is_array($var)) {
$space = str_repeat("\t", $level);
Expand Down Expand Up @@ -242,9 +219,72 @@ private static function _toPhp(&$var, array &$list = [], int $level = 0, int &$l
return '/* resource ' . get_resource_type($var) . ' */';

} else {
$res = var_export($var, true);
$line += substr_count($res, "\n");
return $res;
return var_export($var, true);
}
}


private static function encodeStringPhp(string $s): string
{
$special = [
"\r" => '\r',
"\n" => '\n',
"\t" => "\t",
"\e" => '\e',
'\\' => '\\\\',
];
$utf8 = preg_match('##u', $s);
$escaped = preg_replace_callback(
$utf8 ? '#[\p{C}\\\\]#u' : '#[\x00-\x1F\x7F-\xFF\\\\]#',
function ($m) use ($special) {
return $special[$m[0]] ?? (strlen($m[0]) === 1
? '\x' . str_pad(strtoupper(dechex(ord($m[0]))), 2, '0', STR_PAD_LEFT) . ''
: '\u{' . strtoupper(ltrim(dechex(self::utf8Ord($m[0])), '0')) . '}');
},
$s
);
return $s === str_replace('\\\\', '\\', $escaped)
? "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $s) . "'"
: '"' . addcslashes($escaped, '"$') . '"';
}


private static function encodeStringLine(string $s): string
{
$special = [
"\r" => "\\r\r",
"\n" => "\\n\n",
"\t" => "\\t\t",
"\e" => '\\e',
"'" => "'",
];
$utf8 = preg_match('##u', $s);
$escaped = preg_replace_callback(
$utf8 ? '#[\p{C}\']#u' : '#[\x00-\x1F\x7F-\xFF\']#',
function ($m) use ($special) {
return "\e[22m"
. ($special[$m[0]] ?? (strlen($m[0]) === 1
? '\x' . str_pad(strtoupper(dechex(ord($m[0]))), 2, '0', STR_PAD_LEFT)
: '\u{' . strtoupper(ltrim(dechex(self::utf8Ord($m[0])), '0')) . '}'))
. "\e[1m";
},
$s
);
return "'" . $escaped . "'";
}


private static function utf8Ord(string $c): int
{
$ord0 = ord($c[0]);
if ($ord0 < 0x80) {
return $ord0;
} elseif ($ord0 < 0xE0) {
return ($ord0 << 6) + ord($c[1]) - 0x3080;
} elseif ($ord0 < 0xF0) {
return ($ord0 << 12) + (ord($c[1]) << 6) + ord($c[2]) - 0xE2080;
} else {
return ($ord0 << 18) + (ord($c[1]) << 12) + (ord($c[2]) << 6) + ord($c[3]) - 0x3C82080;
}
}

Expand Down
3 changes: 2 additions & 1 deletion tests/Framework/Assert.match.phpt
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);

use Tester\Assert;
use Tester\Dumper;

require __DIR__ . '/../bootstrap.php';

Expand Down Expand Up @@ -92,7 +93,7 @@ foreach ($notMatches as [$expected, $actual, $expected2, $actual2]) {

$ex = Assert::exception(function () use ($expected, $actual) {
Assert::match($expected, $actual);
}, Tester\AssertException::class, "'$actual3' should match '$expected3'");
}, Tester\AssertException::class, Dumper::toLine($actual3) . " should match " . Dumper::toLine($expected3));

Assert::same($expected2, $ex->expected);
Assert::same($actual2, $ex->actual);
Expand Down
2 changes: 1 addition & 1 deletion tests/Framework/Dumper.dumpException.phpt
Expand Up @@ -21,7 +21,7 @@ $cases = [
'Failed: NULL should not be NULL' => function () { Assert::notSame(null, null); },
'Failed: boolean should be instance of x' => function () { Assert::type('x', true); },
'Failed: resource should be int' => function () { Assert::type('int', fopen(__FILE__, 'r')); },
"Failed: 'Hello\nWorld' should match\n ... 'Hello'" => function () { Assert::match('%a%', "Hello\nWorld"); },
"Failed: 'Hello\\n\nWorld' should match\n ... 'Hello'" => function () { Assert::match('%a%', "Hello\nWorld"); },
"Failed: '...xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' should be \n ... '...xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'" => function () { Assert::same(str_repeat('x', 100), str_repeat('x', 120)); },
"Failed: '...xxxxxxxxxxxxxxxxxxxxxxxxxxx****************************************' should be \n ... '...xxxxxxxxxxxxxxxxxxxxxxxxxxx'" => function () { Assert::same(str_repeat('x', 30), str_repeat('x', 30) . str_repeat('*', 40)); },
"Failed: 'xxxxx*****************************************************************...' should be \n ... 'xxxxx'" => function () { Assert::same(str_repeat('x', 5), str_repeat('x', 5) . str_repeat('*', 90)); },
Expand Down
9 changes: 5 additions & 4 deletions tests/Framework/Dumper.toLine.phpt
Expand Up @@ -21,10 +21,11 @@ Assert::match('NAN', Dumper::toLine(NAN));
Assert::match("''", Dumper::toLine(''));
Assert::match("' '", Dumper::toLine(' '));
Assert::match("'0'", Dumper::toLine('0'));
Assert::match('"\\x00"', Dumper::toLine("\x00"));
Assert::match("' '", Dumper::toLine("\t"));
Assert::match('"\\xff"', Dumper::toLine("\xFF"));
Assert::match("'multi\nline'", Dumper::toLine("multi\nline"));
Assert::match("'\e[22m\\x00\e[1m'", Dumper::toLine("\x00"));
Assert::match("'\e[22m\\u{FEFF}\e[1m'", Dumper::toLine("\xEF\xBB\xBF")); // BOM
Assert::match("'\e[22m\\t\t\e[1m'", Dumper::toLine("\t"));
Assert::match("'\e[22m\\xFF\e[1m'", Dumper::toLine("\xFF"));
Assert::match("'multi\e[22m\\n\n\e[1mline'", Dumper::toLine("multi\nline"));
Assert::match("'I帽t毛rn芒ti么n脿liz忙ti酶n'", Dumper::toLine("I\xc3\xb1t\xc3\xabrn\xc3\xa2ti\xc3\xb4n\xc3\xa0liz\xc3\xa6ti\xc3\xb8n"));
Assert::match('resource(stream)', Dumper::toLine(fopen(__FILE__, 'r')));
Assert::match('stdClass(#%a%)', Dumper::toLine((object) [1, 2]));
Expand Down
7 changes: 6 additions & 1 deletion tests/Framework/Dumper.toPhp.phpt
Expand Up @@ -29,8 +29,9 @@ Assert::match("''", Dumper::toPhp(''));
Assert::match("' '", Dumper::toPhp(' '));
Assert::match("'0'", Dumper::toPhp('0'));
Assert::match('"\\x00"', Dumper::toPhp("\x00"));
Assert::match('"\u{FEFF}"', Dumper::toPhp("\xEF\xBB\xBF")); // BOM
Assert::match("' '", Dumper::toPhp("\t"));
Assert::match('"\\xff"', Dumper::toPhp("\xFF"));
Assert::match('"\\xFF"', Dumper::toPhp("\xFF"));
Assert::match('"multi\nline"', Dumper::toPhp("multi\nline"));
Assert::match("'I帽t毛rn芒ti么n脿liz忙ti酶n'", Dumper::toPhp("I\xc3\xb1t\xc3\xabrn\xc3\xa2ti\xc3\xb4n\xc3\xa0liz\xc3\xa6ti\xc3\xb8n"));
Assert::match('[
Expand All @@ -41,6 +42,10 @@ Assert::match('[
[1 => 1, 2, 3, 4, 5, 6, 7, \'abcdefgh\'],
]', Dumper::toPhp([1, 'hello', "\r" => [], [1, 2], [1 => 1, 2, 3, 4, 5, 6, 7, 'abcdefgh']]));

Assert::match('\'$"\\\\\'', Dumper::toPhp('$"\\'));
Assert::match('\'$"\\ \x00\'', Dumper::toPhp('$"\\ \x00'));
Assert::match('"\\$\\"\\\\ \x00"', Dumper::toPhp("$\"\\ \x00"));

Assert::match('/* resource stream */', Dumper::toPhp(fopen(__FILE__, 'r')));
Assert::match('(object) /* #%a% */ []', Dumper::toPhp((object) null));
Assert::match("(object) /* #%a% */ [
Expand Down

0 comments on commit 2768330

Please sign in to comment.