diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 74ee02d..ce4fb50 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,12 +10,12 @@ jobs: strategy: matrix: - php-versions: ['7.2', '7.3', '7.4', '8.0'] + php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] fail-fast: false steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/src/Base.php b/src/Base.php index d71a96e..a3b6049 100644 --- a/src/Base.php +++ b/src/Base.php @@ -94,7 +94,7 @@ public function __construct($autocatch = true) if ($autocatch) { set_exception_handler(array($this, 'fatal')); } - + $this->setLogLevel($this->logdefault); $this->colors = new Colors(); $this->options = new Options($this->colors); } @@ -229,13 +229,25 @@ protected function execute() public function setLogLevel($level) { if (!isset($this->loglevel[$level])) $this->fatal('Unknown log level'); - $enable = true; + $enable = false; foreach (array_keys($this->loglevel) as $l) { + if ($l == $level) $enable = true; $this->loglevel[$l]['enabled'] = $enable; - if ($l == $level) $enable = false; } } + /** + * Check if a message with the given level should be logged + * + * @param string $level + * @return bool + */ + public function isLogLevelEnabled($level) + { + if (!isset($this->loglevel[$level])) $this->fatal('Unknown log level'); + return $this->loglevel[$level]['enabled']; + } + /** * Exits the program on a fatal error * @@ -283,7 +295,7 @@ protected function logMessage($level, $message, array $context = array()) if (!isset($this->loglevel[$level])) $level = 'error'; $info = $this->loglevel[$level]; - if (!$info['enabled']) return; // no logging for this level + if (!$this->isLogLevelEnabled($level)) return; // no logging for this level $message = $this->interpolate($message, $context); diff --git a/src/Colors.php b/src/Colors.php index 015e422..dd1fd04 100644 --- a/src/Colors.php +++ b/src/Colors.php @@ -31,6 +31,9 @@ class Colors const C_LIGHTGRAY = 'lightgray'; const C_WHITE = 'white'; + // Regex pattern to match color codes + const C_CODE_REGEX = "/(\33\[[0-9;]+m)/"; + /** @var array known color names */ protected $colors = array( self::C_RESET => "\33[0m", @@ -70,6 +73,10 @@ public function __construct() $this->enabled = false; return; } + if (getenv('NO_COLOR')) { // https://no-color.org/ + $this->enabled = false; + return; + } } /** diff --git a/src/TableFormatter.php b/src/TableFormatter.php index 23bb894..20d71c6 100644 --- a/src/TableFormatter.php +++ b/src/TableFormatter.php @@ -293,6 +293,7 @@ protected function substr($string, $start = 0, $length = null) protected function wordwrap($str, $width = 75, $break = "\n", $cut = false) { $lines = explode($break, $str); + $color_reset = $this->colors->getColorCode(Colors::C_RESET); foreach ($lines as &$line) { $line = rtrim($line); if ($this->strlen($line) <= $width) { @@ -301,18 +302,30 @@ protected function wordwrap($str, $width = 75, $break = "\n", $cut = false) $words = explode(' ', $line); $line = ''; $actual = ''; + $color = ''; foreach ($words as $word) { + if (preg_match_all(Colors::C_CODE_REGEX, $word, $color_codes) ) { + # Word contains color codes + foreach ($color_codes[0] as $code) { + if ($code == $color_reset) { + $color = ''; + } else { + # Remember color so we can reapply it after a line break + $color = $code; + } + } + } if ($this->strlen($actual . $word) <= $width) { $actual .= $word . ' '; } else { if ($actual != '') { $line .= rtrim($actual) . $break; } - $actual = $word; + $actual = $color . $word; if ($cut) { while ($this->strlen($actual) > $width) { $line .= $this->substr($actual, 0, $width) . $break; - $actual = $this->substr($actual, $width); + $actual = $color . $this->substr($actual, $width); } } $actual .= ' '; @@ -322,4 +335,4 @@ protected function wordwrap($str, $width = 75, $break = "\n", $cut = false) } return implode($break, $lines); } -} \ No newline at end of file +} diff --git a/tests/LogLevelTest.php b/tests/LogLevelTest.php new file mode 100644 index 0000000..1e5fcc8 --- /dev/null +++ b/tests/LogLevelTest.php @@ -0,0 +1,91 @@ +setLogLevel($level); + foreach ($enabled as $e) { + $this->assertTrue($cli->isLogLevelEnabled($e), "$e is not enabled but should be"); + } + foreach ($disabled as $d) { + $this->assertFalse($cli->isLogLevelEnabled($d), "$d is enabled but should not be"); + } + } + + +} diff --git a/tests/TableFormatterTest.php b/tests/TableFormatterTest.php index 687643a..3b1860a 100644 --- a/tests/TableFormatterTest.php +++ b/tests/TableFormatterTest.php @@ -104,7 +104,7 @@ public function test_length() $tf = new TableFormatter(); $tf->setBorder('|'); - $result = $tf->format([20, '*'], [$text, 'test']); + $result = $tf->format(array(20, '*'), array($text, 'test')); $this->assertEquals($expect, trim($result)); } @@ -118,7 +118,7 @@ public function test_colorlength() $tf = new TableFormatter(); $tf->setBorder('|'); - $result = $tf->format([20, '*'], [$text, 'test']); + $result = $tf->format(array(20, '*'), array($text, 'test')); $this->assertEquals($expect, trim($result)); } @@ -135,7 +135,54 @@ public function test_onewrap() $tf->setMaxWidth(11); $tf->setBorder('|'); - $result = $tf->format([5, '*'], [$col1, $col2]); + $result = $tf->format(array(5, '*'), array($col1, $col2)); $this->assertEquals($expect, $result); } + + /** + * Test that colors are correctly applied when text is wrapping across lines. + * + * @dataProvider colorwrapProvider + */ + public function test_colorwrap($text, $expect) + { + $tf = new TableFormatter(); + $tf->setMaxWidth(15); + + $this->assertEquals($expect, $tf->format(array('*'), array($text))); + } + + /** + * Data provider for test_colorwrap. + * + * @return array[] + */ + public function colorwrapProvider() + { + $color = new Colors(); + $cyan = $color->getColorCode(Colors::C_CYAN); + $reset = $color->getColorCode(Colors::C_RESET); + $wrap = function ($str) use ($color) { + return $color->wrap($str, Colors::C_CYAN); + }; + + return array( + 'color word line 1' => array( + "This is ". $wrap("cyan") . " text wrapping", + "This is {$cyan}cyan{$reset} \ntext wrapping \n", + ), + 'color word line 2' => array( + "This is text ". $wrap("cyan") . " wrapping", + "This is text \n{$cyan}cyan{$reset} wrapping \n", + ), + 'color across lines' => array( + "This is ". $wrap("cyan text") . " wrapping", + "This is {$cyan}cyan \ntext{$reset} wrapping \n", + ), + 'color across lines until end' => array( + "This is ". $wrap("cyan text wrapping"), + "This is {$cyan}cyan \n{$cyan}text wrapping{$reset} \n", + ), + ); + } }