Commit f46cffb4 by Carsten Brandt

Implement console color ansi2html

issue #746
parent 0d03f720
...@@ -48,6 +48,7 @@ class BaseConsole ...@@ -48,6 +48,7 @@ class BaseConsole
const ENCIRCLED = 52; const ENCIRCLED = 52;
const OVERLINED = 53; const OVERLINED = 53;
/** /**
* Moves the terminal cursor up by sending ANSI control code CUU to the terminal. * Moves the terminal cursor up by sending ANSI control code CUU to the terminal.
* If the cursor is already at the edge of the screen, this has no effect. * If the cursor is already at the edge of the screen, this has no effect.
...@@ -328,117 +329,117 @@ class BaseConsole ...@@ -328,117 +329,117 @@ class BaseConsole
/** /**
* Converts an ANSI formatted string to HTML * Converts an ANSI formatted string to HTML
* @param $string *
* @return mixed * Note: xTerm 256 bit colors are currently not supported.
*/ *
// TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 * @param string $string the string to convert.
public static function ansiToHtml($string) * @param array $styleMap an optional mapping of ANSI control codes such as
{ * [[FG_COLOR]] or [[BOLD]] to a set of css style definitions.
$tags = 0; * The CSS style definitions are represented as an array where the array keys correspond
* to the css style attribute names and the values are the css values.
* values may be arrays that will be merged and imploded with `' '` when rendered.
* @return string HTML representation of the ANSI formatted string
*/
public static function ansiToHtml($string, $styleMap = [])
{
$styleMap = [
// http://www.w3.org/TR/CSS2/syndata.html#value-def-color
self::FG_BLACK => ['color' => 'black'],
self::FG_BLUE => ['color' => 'blue'],
self::FG_CYAN => ['color' => 'aqua'],
self::FG_GREEN => ['color' => 'lime'],
self::FG_GREY => ['color' => 'silver'],
// http://meyerweb.com/eric/thoughts/2014/06/19/rebeccapurple/
// http://dev.w3.org/csswg/css-color/#valuedef-rebeccapurple
self::FG_PURPLE => ['color' => 'rebeccapurple'],
self::FG_RED => ['color' => 'red'],
self::FG_YELLOW => ['color' => 'yellow'],
self::BG_BLACK => ['background-color' => 'black'],
self::BG_BLUE => ['background-color' => 'blue'],
self::BG_CYAN => ['background-color' => 'aqua'],
self::BG_GREEN => ['background-color' => 'lime'],
self::BG_GREY => ['background-color' => 'silver'],
self::BG_PURPLE => ['background-color' => 'rebeccapurple'],
self::BG_RED => ['background-color' => 'red'],
self::BG_YELLOW => ['background-color' => 'yellow'],
self::BOLD => ['font-weight' => 'bold'],
self::ITALIC => ['font-style' => 'italic'],
self::UNDERLINE => ['text-decoration' => ['underline']],
self::OVERLINED => ['text-decoration' => ['overline']],
self::CROSSED_OUT => ['text-decoration' => ['line-through']],
self::BLINK => ['text-decoration' => ['blink']],
self::CONCEALED => ['visibility' => 'hidden'],
// self::ENCIRCLED:
// self::FRAMED:
] + $styleMap;
return preg_replace_callback( $tags = 0;
'/\033\[[\d;]+m/', $result = preg_replace_callback(
function ($ansi) use (&$tags) { '/\033\[([\d;]+)m/',
$styleA = []; function ($ansi) use (&$tags, $styleMap) {
foreach (explode(';', $ansi) as $controlCode) { $style = [];
$style = []; $reset = false;
switch ($controlCode) { $negative = false;
case self::FG_BLACK: foreach (explode(';', $ansi[1]) as $controlCode) {
$style = ['color' => '#000000']; if ($controlCode == 0) {
break; $style = [];
case self::FG_BLUE: $reset = true;
$style = ['color' => '#000078']; } elseif ($controlCode == self::NEGATIVE) {
break; $negative = true;
case self::FG_CYAN: } elseif (isset($styleMap[$controlCode])) {
$style = ['color' => '#007878']; $style[] = $styleMap[$controlCode];
break;
case self::FG_GREEN:
$style = ['color' => '#007800'];
break;
case self::FG_GREY:
$style = ['color' => '#787878'];
break;
case self::FG_PURPLE:
$style = ['color' => '#780078'];
break;
case self::FG_RED:
$style = ['color' => '#780000'];
break;
case self::FG_YELLOW:
$style = ['color' => '#787800'];
break;
case self::BG_BLACK:
$style = ['background-color' => '#000000'];
break;
case self::BG_BLUE:
$style = ['background-color' => '#000078'];
break;
case self::BG_CYAN:
$style = ['background-color' => '#007878'];
break;
case self::BG_GREEN:
$style = ['background-color' => '#007800'];
break;
case self::BG_GREY:
$style = ['background-color' => '#787878'];
break;
case self::BG_PURPLE:
$style = ['background-color' => '#780078'];
break;
case self::BG_RED:
$style = ['background-color' => '#780000'];
break;
case self::BG_YELLOW:
$style = ['background-color' => '#787800'];
break;
case self::BOLD:
$style = ['font-weight' => 'bold'];
break;
case self::ITALIC:
$style = ['font-style' => 'italic'];
break;
case self::UNDERLINE:
$style = ['text-decoration' => ['underline']];
break;
case self::OVERLINED:
$style = ['text-decoration' => ['overline']];
break;
case self::CROSSED_OUT:
$style = ['text-decoration' => ['line-through']];
break;
case self::BLINK:
$style = ['text-decoration' => ['blink']];
break;
case self::NEGATIVE: // ???
case self::CONCEALED:
case self::ENCIRCLED:
case self::FRAMED:
// TODO allow resetting codes
break;
case 0: // ansi reset
$return = '';
for (; $tags > 0; $tags--) {
$return .= '</span>';
}
return $return;
} }
}
$return = '';
while($reset && $tags > 0) {
$return .= '</span>';
$tags--;
}
if (empty($style)) {
return $return;
}
$currentStyle = [];
foreach ($style as $content) {
$currentStyle = ArrayHelper::merge($currentStyle, $content);
}
$styleA = ArrayHelper::merge($styleA, $style); // if negative is set, invert background and foreground
if ($negative) {
if (isset($currentStyle['color'])) {
$fgColor = $currentStyle['color'];
unset($currentStyle['color']);
}
if (isset($currentStyle['background-color'])) {
$bgColor = $currentStyle['background-color'];
unset($currentStyle['background-color']);
}
if (isset($fgColor)) {
$currentStyle['background-color'] = $fgColor;
}
if (isset($bgColor)) {
$currentStyle['color'] = $bgColor;
}
} }
$styleString = [];
foreach ($styleA as $name => $content) { $styleString = '';
if ($name === 'text-decoration') { foreach($currentStyle as $name => $value) {
$content = implode(' ', $content); if (is_array($value)) {
$value = implode(' ', $value);
} }
$styleString[] = $name . ':' . $content; $styleString .= "$name: $value;";
} }
$tags++; $tags++;
return "$return<span style=\"$styleString\">";
return '<span' . (!empty($styleString) ? 'style="' . implode(';', $styleString) : '') . '>';
}, },
$string $string
); );
while($tags > 0) {
$result .= '</span>';
$tags--;
}
return $result;
} }
// TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746
......
...@@ -79,4 +79,54 @@ class ConsoleTest extends TestCase ...@@ -79,4 +79,54 @@ class ConsoleTest extends TestCase
sleep(1); sleep(1);
} }
}*/ }*/
public function ansiFormats()
{
return [
['test', 'test'],
[Console::ansiFormat('test', [Console::FG_RED]), '<span style="color: red;">test</span>'],
['abc' . Console::ansiFormat('def', [Console::FG_RED]) . 'ghj', 'abc<span style="color: red;">def</span>ghj'],
['abc' . Console::ansiFormat('def', [Console::FG_RED, Console::BG_GREEN]) . 'ghj', 'abc<span style="color: red;background-color: lime;">def</span>ghj'],
['abc' . Console::ansiFormat('def', [Console::FG_GREEN, Console::FG_RED, Console::BG_GREEN]) . 'ghj', 'abc<span style="color: red;background-color: lime;">def</span>ghj'],
['abc' . Console::ansiFormat('def', [Console::BOLD, Console::BG_GREEN]) . 'ghj', 'abc<span style="font-weight: bold;background-color: lime;">def</span>ghj'],
[
Console::ansiFormat('test', [Console::UNDERLINE, Console::OVERLINED, Console::CROSSED_OUT, Console::FG_GREEN]),
'<span style="text-decoration: underline overline line-through;color: lime;">test</span>'
],
[Console::ansiFormatCode([Console::RESET]) . Console::ansiFormatCode([Console::RESET]), ''],
[Console::ansiFormatCode([Console::RESET]) . Console::ansiFormatCode([Console::RESET]) . 'test', 'test'],
[Console::ansiFormatCode([Console::RESET]) . 'test' . Console::ansiFormatCode([Console::RESET]), 'test'],
[
Console::ansiFormatCode([Console::BOLD]) . 'abc' . Console::ansiFormatCode([Console::RESET, Console::FG_GREEN]) . 'ghj' . Console::ansiFormatCode([Console::RESET]),
'<span style="font-weight: bold;">abc</span><span style="color: lime;">ghj</span>'
],
[
Console::ansiFormatCode([Console::FG_GREEN]) . ' a ' . Console::ansiFormatCode([Console::BOLD]) . 'abc' . Console::ansiFormatCode([Console::RESET]) . 'ghj',
'<span style="color: lime;"> a <span style="font-weight: bold;">abc</span></span>ghj'
],
[
Console::ansiFormat('test', [Console::FG_GREEN, Console::BG_BLUE, Console::NEGATIVE]),
'<span style="background-color: lime;color: blue;">test</span>'
],
[
Console::ansiFormat('test', [Console::NEGATIVE]),
'test'
],
[
Console::ansiFormat('test', [Console::CONCEALED]),
'<span style="visibility: hidden;">test</span>'
],
];
}
/**
* @dataProvider ansiFormats
*/
public function testAnsi2Html($ansi, $html)
{
$this->assertEquals($html, Console::ansiToHtml($ansi));
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment