Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a52f41d

Browse filesBrowse files
denkiryokuhatsudenfabpot
authored andcommitted
[Console]Improve formatter for double-width character
1 parent c2e134f commit a52f41d
Copy full SHA for a52f41d

7 files changed

+143
-30
lines changed

‎src/Symfony/Component/Console/Application.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Application.php
+64-27Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public function setDispatcher(EventDispatcherInterface $dispatcher)
9999
* @param InputInterface $input An Input instance
100100
* @param OutputInterface $output An Output instance
101101
*
102-
* @return integer 0 if everything went fine, or an error code
102+
* @return int 0 if everything went fine, or an error code
103103
*
104104
* @throws \Exception When doRun returns Exception
105105
*
@@ -159,7 +159,7 @@ public function run(InputInterface $input = null, OutputInterface $output = null
159159
* @param InputInterface $input An Input instance
160160
* @param OutputInterface $output An Output instance
161161
*
162-
* @return integer 0 if everything went fine, or an error code
162+
* @return int 0 if everything went fine, or an error code
163163
*/
164164
public function doRun(InputInterface $input, OutputInterface $output)
165165
{
@@ -270,7 +270,7 @@ public function getHelp()
270270
/**
271271
* Sets whether to catch exceptions or not during commands execution.
272272
*
273-
* @param bool $boolean Whether to catch exceptions or not during commands execution
273+
* @param bool $boolean Whether to catch exceptions or not during commands execution
274274
*
275275
* @api
276276
*/
@@ -282,7 +282,7 @@ public function setCatchExceptions($boolean)
282282
/**
283283
* Sets whether to automatically exit after a command execution or not.
284284
*
285-
* @param bool $boolean Whether to automatically exit after a command execution or not
285+
* @param bool $boolean Whether to automatically exit after a command execution or not
286286
*
287287
* @api
288288
*/
@@ -449,7 +449,7 @@ public function get($name)
449449
*
450450
* @param string $name The command name or alias
451451
*
452-
* @return Boolean true if the command exists, false otherwise
452+
* @return bool true if the command exists, false otherwise
453453
*
454454
* @api
455455
*/
@@ -674,8 +674,8 @@ public static function getAbbreviations($names)
674674
/**
675675
* Returns a text representation of the Application.
676676
*
677-
* @param string $namespace An optional namespace name
678-
* @param bool $raw Whether to return raw command list
677+
* @param string $namespace An optional namespace name
678+
* @param bool $raw Whether to return raw command list
679679
*
680680
* @return string A string representing the Application
681681
*
@@ -691,8 +691,8 @@ public function asText($namespace = null, $raw = false)
691691
/**
692692
* Returns an XML representation of the Application.
693693
*
694-
* @param string $namespace An optional namespace name
695-
* @param bool $asDom Whether to return a DOM or an XML string
694+
* @param string $namespace An optional namespace name
695+
* @param bool $asDom Whether to return a DOM or an XML string
696696
*
697697
* @return string|\DOMDocument An XML string representing the Application
698698
*
@@ -708,34 +708,22 @@ public function asXml($namespace = null, $asDom = false)
708708
/**
709709
* Renders a caught exception.
710710
*
711-
* @param \Exception $e An exception instance
711+
* @param \Exception $e An exception instance
712712
* @param OutputInterface $output An OutputInterface instance
713713
*/
714714
public function renderException($e, $output)
715715
{
716-
$strlen = function ($string) {
717-
if (!function_exists('mb_strlen')) {
718-
return strlen($string);
719-
}
720-
721-
if (false === $encoding = mb_detect_encoding($string)) {
722-
return strlen($string);
723-
}
724-
725-
return mb_strlen($string, $encoding);
726-
};
727-
728716
do {
729717
$title = sprintf(' [%s] ', get_class($e));
730-
$len = $strlen($title);
718+
$len = $this->stringWidth($title);
731719
// HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327
732720
$width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : (defined('HHVM_VERSION') ? 1 << 31 : PHP_INT_MAX);
733721
$formatter = $output->getFormatter();
734722
$lines = array();
735723
foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) {
736-
foreach (str_split($line, $width - 4) as $line) {
724+
foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
737725
// pre-format lines to get the right string length
738-
$lineLength = $strlen(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4;
726+
$lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4;
739727
$lines[] = array($line, $lineLength);
740728

741729
$len = max($lineLength, $len);
@@ -744,7 +732,7 @@ public function renderException($e, $output)
744732

745733
$messages = array('', '');
746734
$messages[] = $emptyLine = $formatter->format(sprintf('<error>%s</error>', str_repeat(' ', $len)));
747-
$messages[] = $formatter->format(sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $strlen($title)))));
735+
$messages[] = $formatter->format(sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))));
748736
foreach ($lines as $line) {
749737
$messages[] = $formatter->format(sprintf('<error> %s %s</error>', $line[0], str_repeat(' ', $len - $line[1])));
750738
}
@@ -890,7 +878,7 @@ protected function configureIO(InputInterface $input, OutputInterface $output)
890878
* @param InputInterface $input An Input instance
891879
* @param OutputInterface $output An Output instance
892880
*
893-
* @return integer 0 if everything went fine, or an error code
881+
* @return int 0 if everything went fine, or an error code
894882
*/
895883
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
896884
{
@@ -1125,4 +1113,53 @@ private function findAlternatives($name, $collection, $abbrevs, $callback = null
11251113

11261114
return array_keys($alternatives);
11271115
}
1116+
1117+
private function stringWidth($string)
1118+
{
1119+
if (!function_exists('mb_strwidth')) {
1120+
return strlen($string);
1121+
}
1122+
1123+
if (false === $encoding = mb_detect_encoding($string)) {
1124+
return strlen($string);
1125+
}
1126+
1127+
return mb_strwidth($string, $encoding);
1128+
}
1129+
1130+
private function splitStringByWidth($string, $width)
1131+
{
1132+
// str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
1133+
// additionally, array_slice() is not enough as some character has doubled width.
1134+
// we need a function to split string not by character count but by string width
1135+
1136+
if (!function_exists('mb_strwidth')) {
1137+
return str_split($string, $width);
1138+
}
1139+
1140+
if (false === $encoding = mb_detect_encoding($string)) {
1141+
return str_split($string, $width);
1142+
}
1143+
1144+
$utf8String = mb_convert_encoding($string, 'utf8', $encoding);
1145+
$lines = array();
1146+
$line = '';
1147+
foreach (preg_split('//u', $utf8String) as $char) {
1148+
// test if $char could be appended to current line
1149+
if (mb_strwidth($line.$char) <= $width) {
1150+
$line .= $char;
1151+
continue;
1152+
}
1153+
// if not, push current line to array and make new line
1154+
$lines[] = str_pad($line, $width);
1155+
$line = $char;
1156+
}
1157+
if (strlen($line)) {
1158+
$lines[] = count($lines) ? str_pad($line, $width) : $line;
1159+
}
1160+
1161+
mb_convert_variables($encoding, 'utf8', $lines);
1162+
1163+
return $lines;
1164+
}
11281165
}

‎src/Symfony/Component/Console/Helper/Helper.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Helper/Helper.php
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,18 @@ public function getHelperSet()
4545
*
4646
* @param string $string The string to check its length
4747
*
48-
* @return integer The length of the string
48+
* @return int The length of the string
4949
*/
5050
protected function strlen($string)
5151
{
52-
if (!function_exists('mb_strlen')) {
52+
if (!function_exists('mb_strwidth')) {
5353
return strlen($string);
5454
}
5555

5656
if (false === $encoding = mb_detect_encoding($string)) {
5757
return strlen($string);
5858
}
5959

60-
return mb_strlen($string, $encoding);
60+
return mb_strwidth($string, $encoding);
6161
}
6262
}

‎src/Symfony/Component/Console/Tests/ApplicationTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/ApplicationTest.php
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,33 @@ public function testRenderException()
469469
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception4.txt', $tester->getDisplay(true), '->renderException() wraps messages when they are bigger than the terminal');
470470
}
471471

472+
public function testRenderExceptionWithDoubleWidthCharacters()
473+
{
474+
$application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth'));
475+
$application->setAutoExit(false);
476+
$application->expects($this->any())
477+
->method('getTerminalWidth')
478+
->will($this->returnValue(120));
479+
$application->register('foo')->setCode(function () {throw new \Exception('エラーメッセージ');});
480+
$tester = new ApplicationTester($application);
481+
482+
$tester->run(array('command' => 'foo'), array('decorated' => false));
483+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1.txt', $tester->getDisplay(true), '->renderException() renderes a pretty exceptions with previous exceptions');
484+
485+
$tester->run(array('command' => 'foo'), array('decorated' => true));
486+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1decorated.txt', $tester->getDisplay(true), '->renderException() renderes a pretty exceptions with previous exceptions');
487+
488+
$application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth'));
489+
$application->setAutoExit(false);
490+
$application->expects($this->any())
491+
->method('getTerminalWidth')
492+
->will($this->returnValue(32));
493+
$application->register('foo')->setCode(function () {throw new \Exception('コマンドの実行中にエラーが発生しました。');});
494+
$tester = new ApplicationTester($application);
495+
$tester->run(array('command' => 'foo'), array('decorated' => false));
496+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth2.txt', $tester->getDisplay(true), '->renderException() wraps messages when they are bigger than the terminal');
497+
}
498+
472499
public function testRun()
473500
{
474501
$application = new Application();
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
3+
4+
[Exception]
5+
エラーメッセージ
6+
7+
8+
9+
foo
10+
11+
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
3+
 
4+
 [Exception] 
5+
 エラーメッセージ 
6+
 
7+
8+
9+
foo
10+
11+
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
3+
4+
[Exception]
5+
コマンドの実行中にエラーが
6+
発生しました。
7+
8+
9+
10+
foo
11+
12+

‎src/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php
+15Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,21 @@ public function testFormatBlockWithDiacriticLetters()
6969
);
7070
}
7171

72+
public function testFormatBlockWithDoubleWidthDiacriticLetters()
73+
{
74+
if (!extension_loaded('mbstring')) {
75+
$this->markTestSkipped('This test requires mbstring to work.');
76+
}
77+
$formatter = new FormatterHelper();
78+
$this->assertEquals(
79+
'<error> </error>'."\n" .
80+
'<error> 表示するテキスト </error>'."\n" .
81+
'<error> </error>',
82+
$formatter->formatBlock('表示するテキスト', 'error', true),
83+
'::formatBlock() formats a message in a block'
84+
);
85+
}
86+
7287
public function testFormatBlockLGEscaping()
7388
{
7489
$formatter = new FormatterHelper();

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.