diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -130,6 +130,7 @@ 'PhutilConsoleBlock' => 'console/view/PhutilConsoleBlock.php', 'PhutilConsoleConcatenatedView' => 'console/view/PhutilConsoleConcatenatedView.php', 'PhutilConsoleFormatter' => 'console/PhutilConsoleFormatter.php', + 'PhutilConsoleFormatterTestCase' => 'console/__tests__/PhutilConsoleFormatterTestCase.php', 'PhutilConsoleList' => 'console/view/PhutilConsoleList.php', 'PhutilConsoleMessage' => 'console/PhutilConsoleMessage.php', 'PhutilConsoleProgressBar' => 'console/PhutilConsoleProgressBar.php', @@ -653,6 +654,7 @@ 'PhutilConsoleBlock' => 'PhutilConsoleView', 'PhutilConsoleConcatenatedView' => 'PhutilConsoleView', 'PhutilConsoleFormatter' => 'Phobject', + 'PhutilConsoleFormatterTestCase' => 'PhutilTestCase', 'PhutilConsoleList' => 'PhutilConsoleView', 'PhutilConsoleMessage' => 'Phobject', 'PhutilConsoleProgressBar' => 'Phobject', diff --git a/src/console/PhutilConsoleFormatter.php b/src/console/PhutilConsoleFormatter.php --- a/src/console/PhutilConsoleFormatter.php +++ b/src/console/PhutilConsoleFormatter.php @@ -47,6 +47,13 @@ return call_user_func_array('sprintf', $args); } + public static function renderLiterally($matches) { + return preg_replace( + '/\*\*(.*\*\*)?+|__(.*__)?+|##(.*##)?+/sU', + '\\\\\0', + $matches[1]); + } + public static function replaceColorCode($matches) { $codes = self::$colorCodes; $offset = 30 + $codes[$matches[2]]; @@ -64,10 +71,16 @@ // Sequence should be preceded by start-of-string or non-backslash // escaping. + $backtick_re = '/(?<![\\\\])`(.*)`/sU'; $bold_re = '/(?<![\\\\])\*\*(.*)\*\*/sU'; $underline_re = '/(?<![\\\\])__(.*)__/sU'; $invert_re = '/(?<![\\\\])##(.*)##/sU'; + $format = preg_replace_callback( + $backtick_re, + array(__CLASS__, 'renderLiterally'), + $format); + if (self::getDisableANSI()) { $format = preg_replace($bold_re, '\1', $format); $format = preg_replace($underline_re, '\1', $format); @@ -92,7 +105,7 @@ } // Remove backslash escaping - return preg_replace('/\\\\(\*\*.*\*\*|__.*__|##.*##)/sU', '\1', $format); + return preg_replace('/\\\\(\*\*.*\*\*|__.*__|##.*##|`)/sU', '\1', $format); } } diff --git a/src/console/__tests__/PhutilConsoleFormatterTestCase.php b/src/console/__tests__/PhutilConsoleFormatterTestCase.php new file mode 100644 --- /dev/null +++ b/src/console/__tests__/PhutilConsoleFormatterTestCase.php @@ -0,0 +1,77 @@ +<?php + +final class PhutilConsoleFormatterTestCase extends PhutilTestCase { + + public function testformatString() { + $esc = chr(27); + + $data = array( + '' => array( + '', + '', + ), + '<bg:red>red background</bg>' => array( + 'red background', + "{$esc}[41mred background{$esc}[49m", + ), + '<fg:blue>blue foreground</fg>' => array( + 'blue foreground', + "{$esc}[34mblue foreground{$esc}[39m", + ), + '**bold**' => array( + 'bold', + "{$esc}[1mbold{$esc}[m", + ), + '\\**bold**' => array( + '**bold**', + '**bold**', + ), + '__underline__' => array( + 'underline', + "{$esc}[4munderline{$esc}[m", + ), + '\\__underline__' => array( + '__underline__', + '__underline__', + ), + '##invert##' => array( + 'invert', + "{$esc}[7minvert{$esc}[m", + ), + '\\##invert##' => array( + '##invert##', + '##invert##', + ), + '`backtick`' => array( + 'backtick', + 'backtick', + ), + '\\`backtick`' => array( + '`backtick`', + '`backtick`', + ), + '`**literal**__text__`' => array( + '**literal**__text__', + '**literal**__text__', + ), + ); + + foreach ($data as $format => $expected) { + list($nonansi_expected, $ansi_expected) = $expected; + + $is_ansi = PhutilConsoleFormatter::getDisableANSI(); + + PhutilConsoleFormatter::disableANSI(true); + $nonansi_value = PhutilConsoleFormatter::formatString($format); + + PhutilConsoleFormatter::disableANSI(false); + $ansi_value = PhutilConsoleFormatter::formatString($format); + + $this->assertEqual($nonansi_expected, $nonansi_value); + $this->assertEqual($ansi_expected, $ansi_value); + } + + PhutilConsoleFormatter::disableANSI($is_ansi); + } + +}