├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json └── src └── Highlighter.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 6 | 7 | ## [Unreleased] 8 | 9 | _Nothing yet._ 10 | 11 | 12 | ## [1.0.0] - 2022-02-18 13 | 14 | ### Fixed 15 | 16 | - Bug fix: if the highlighted code snippet was at the start of the file, too many lines were retrieved, [#35] from [@jrfnl]. 17 | - Bug fix: code snippets highlighted with line numbers had a stray blank line at the end of the snippet, [#35] from [@jrfnl]. 18 | - PHP 8.0: handle changed tokenization of namespaced names [#19] from [@jrfnl]. 19 | 20 | ### Changed 21 | 22 | - BC-Break: The top-level namespace for all classes has changed from `JakubOnderka` to `PHP_Parallel_Lint`. [#28] from [@jrfnl], fixes [#4]. 23 | - Support for PHP 5.3 has been restored, [#33] from [@jrfnl]. 24 | - Update [PHP Console Color] dependency to version `^1.0.1` [#17] from [@jrfnl]. 25 | 26 | ### Internal 27 | 28 | - Welcome [@jrfnl] as new co-maintainer. 29 | - Improvements to the test suite, [#10], [#15], [#21], [#25], [#34], [#35], [#37], [#38], [#39] from [@peter279k] and [@jrfnl]. 30 | - Improvements to the code consistency, [#10], [#20], [#29], [#30] from [@peter279k] and [@jrfnl], fixes [#11]. 31 | - Improvements to the CI/QA setup, [#12], [#14], [#16], [#18], [#23], [#24], [#26], [#31], [#36] from [@jrfnl], fixes [#13], [#22]. 32 | - Improvements to the changelog, [#27] from [@jrfnl]. 33 | 34 | [PHP Console Color]: https://github.com/php-parallel-lint/PHP-Console-Color 35 | 36 | [#4]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues/4 37 | [#10]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/10 38 | [#11]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues/11 39 | [#12]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/12 40 | [#13]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues/13 41 | [#14]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/14 42 | [#15]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/15 43 | [#16]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/16 44 | [#17]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/17 45 | [#18]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/18 46 | [#19]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/19 47 | [#20]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/20 48 | [#21]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/21 49 | [#22]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues/22 50 | [#23]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/23 51 | [#24]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/24 52 | [#25]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/25 53 | [#26]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/26 54 | [#27]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/27 55 | [#28]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/28 56 | [#29]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/29 57 | [#30]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/30 58 | [#31]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/31 59 | [#33]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/33 60 | [#34]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/34 61 | [#35]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/35 62 | [#36]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/36 63 | [#37]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/37 64 | [#38]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/38 65 | [#39]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/pull/39 66 | 67 | 68 | 69 | ## [0.5] - 2020-05-13 70 | 71 | ### Added 72 | 73 | - Added changelog from [@reedy]. 74 | 75 | ### Internal 76 | 77 | - Cleaned readme - new organization from previous package from [@grogy]. 78 | - Composer: marked package as replacing jakub-onderka/php-console-highlighter from [@grogy]. 79 | - Composer: updated dependencies to use new php-parallel-lint organisation from [@grogy]. 80 | - Travis: test against PHP 7.4 and nightly from [@jrfnl]. 81 | - Fixed build script from [@jrfnl]. 82 | - Added a .gitattributes file from [@reedy]. 83 | - Updated installation command from [@cafferata]. 84 | 85 | 86 | [Unreleased]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/compare/v1.0.0...HEAD 87 | [1.0.0]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/compare/v0.5...v1.0.0 88 | [0.5]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/compare/v0.4...v0.5 89 | 90 | [@cafferata]: https://github.com/cafferata 91 | [@grogy]: https://github.com/grogy 92 | [@jrfnl]: https://github.com/jrfnl 93 | [@peter279k]: https://github.com/peter279k 94 | [@reedy]: https://github.com/reedy 95 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Jakub Onderka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP Console Highlighter 2 | ======================= 3 | 4 | Highlight PHP code in console (terminal). 5 | 6 | Example 7 | ------- 8 | ![Example](http://jakubonderka.github.io/php-console-highlight-example.png) 9 | 10 | Install 11 | ------- 12 | 13 | Just run the following command to install it: 14 | 15 | composer require --dev php-parallel-lint/php-console-highlighter:"0.*" 16 | 17 | Usage 18 | ------- 19 | ```php 20 | getWholeFile($fileContent); 30 | ``` 31 | 32 | ------ 33 | 34 | [![Downloads this Month](https://img.shields.io/packagist/dm/php-parallel-lint/php-console-highlighter.svg)](https://packagist.org/packages/php-parallel-lint/php-console-highlighter) 35 | [![CS](https://github.com/php-parallel-lint/PHP-Console-Highlighter/actions/workflows/cs.yml/badge.svg)](https://github.com/php-parallel-lint/PHP-Console-Highlighter/actions/workflows/cs.yml) 36 | [![Test](https://github.com/php-parallel-lint/PHP-Console-Highlighter/actions/workflows/test.yml/badge.svg)](https://github.com/php-parallel-lint/PHP-Console-Highlighter/actions/workflows/test.yml) 37 | [![License](https://poser.pugx.org/php-parallel-lint/php-console-highlighter/license.svg)](https://packagist.org/packages/php-parallel-lint/php-console-highlighter) 38 | [![Coverage Status](https://coveralls.io/repos/github/php-parallel-lint/PHP-Console-Highlighter/badge.svg?branch=master)](https://coveralls.io/github/php-parallel-lint/PHP-Console-Highlighter?branch=master) 39 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-parallel-lint/php-console-highlighter", 3 | "description": "Highlight PHP code in terminal", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Jakub Onderka", 9 | "email": "acci@acci.cz", 10 | "homepage": "http://www.acci.cz/" 11 | }, 12 | { 13 | "name" : "Contributors", 14 | "homepage" : "https://github.com/php-parallel-lint/PHP-Console-Highlighter/graphs/contributors" 15 | } 16 | ], 17 | "autoload": { 18 | "psr-4": {"PHP_Parallel_Lint\\PhpConsoleHighlighter\\": "src/"} 19 | }, 20 | "autoload-dev": { 21 | "psr-4": {"PHP_Parallel_Lint\\PhpConsoleHighlighter\\Test\\": "tests/"} 22 | }, 23 | "require": { 24 | "php": ">=5.3.2", 25 | "ext-tokenizer": "*", 26 | "php-parallel-lint/php-console-color": "^1.0.1" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.1", 30 | "php-parallel-lint/php-parallel-lint": "^1.0", 31 | "php-parallel-lint/php-var-dump-check": "0.*", 32 | "php-parallel-lint/php-code-style": "^2.0" 33 | }, 34 | "replace": { 35 | "jakub-onderka/php-console-highlighter": "*" 36 | }, 37 | "config": { 38 | "allow-plugins": { 39 | "dealerdirect/phpcodesniffer-composer-installer": true 40 | }, 41 | "lock": false 42 | }, 43 | "scripts" : { 44 | "phplint": [ 45 | "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git" 46 | ], 47 | "vardumpcheck": [ 48 | "@php ./vendor/php-parallel-lint/php-var-dump-check/var-dump-check . --exclude vendor --exclude .git" 49 | ], 50 | "phpcs": [ 51 | "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --report-full --report-checkstyle=./build/logs/checkstyle.xml" 52 | ], 53 | "fixcs": [ 54 | "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" 55 | ], 56 | "phpunit": [ 57 | "@php ./vendor/phpunit/phpunit/phpunit --no-coverage" 58 | ], 59 | "phpunit10": [ 60 | "@php ./vendor/phpunit/phpunit/phpunit -c phpunit10.xml.dist --no-coverage" 61 | ], 62 | "coverage": [ 63 | "@php ./vendor/phpunit/phpunit/phpunit" 64 | ], 65 | "coverage10": [ 66 | "@php ./vendor/phpunit/phpunit/phpunit -c phpunit10.xml.dist" 67 | ], 68 | "build": [ 69 | "@phplint", 70 | "@vardumpcheck", 71 | "@phpcs", 72 | "@phpunit" 73 | ] 74 | }, 75 | "scripts-descriptions": { 76 | "phplint": "Check syntax errors in PHP files", 77 | "vardumpcheck": "Check PHP files for forgotten variable dumps", 78 | "phpcs": "Check PHP code style", 79 | "fixcs": "Auto-fix PHP code style", 80 | "phpunit": "Run the unit tests on PHPUnit 4.x - 9.x without code coverage.", 81 | "phpunit10": "Run the unit tests on PHPUnit 10.x without code coverage.", 82 | "coverage": "Run the unit tests on PHPUnit 4.x - 9.x with code coverage.", 83 | "coverage10": "Run the unit tests on PHPUnit 10.x with code coverage.", 84 | "build": "Run all checks" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Highlighter.php: -------------------------------------------------------------------------------- 1 | 'red', 24 | self::TOKEN_COMMENT => 'yellow', 25 | self::TOKEN_KEYWORD => 'green', 26 | self::TOKEN_DEFAULT => 'default', 27 | self::TOKEN_HTML => 'cyan', 28 | 29 | self::ACTUAL_LINE_MARK => 'red', 30 | self::LINE_NUMBER => 'dark_gray', 31 | ); 32 | 33 | /** @var array */ 34 | private $phpTagTokens = array( 35 | T_OPEN_TAG => T_OPEN_TAG, 36 | T_OPEN_TAG_WITH_ECHO => T_OPEN_TAG_WITH_ECHO, 37 | T_CLOSE_TAG => T_CLOSE_TAG, 38 | ); 39 | 40 | /** @var array */ 41 | private $magicConstantTokens = array( 42 | T_DIR => T_DIR, 43 | T_FILE => T_FILE, 44 | T_LINE => T_LINE, 45 | T_CLASS_C => T_CLASS_C, 46 | T_FUNC_C => T_FUNC_C, 47 | T_METHOD_C => T_METHOD_C, 48 | T_NS_C => T_NS_C, 49 | ); 50 | 51 | /** @var array */ 52 | private $miscTokens = array( 53 | T_STRING => T_STRING, // Labels. 54 | T_VARIABLE => T_VARIABLE, 55 | T_DNUMBER => T_DNUMBER, // Floats. 56 | T_LNUMBER => T_LNUMBER, // Integers. 57 | ); 58 | 59 | /** @var array */ 60 | private $commentTokens = array( 61 | T_COMMENT => T_COMMENT, 62 | T_DOC_COMMENT => T_DOC_COMMENT, 63 | ); 64 | 65 | /** @var array */ 66 | private $textStringTokens = array( 67 | T_ENCAPSED_AND_WHITESPACE => T_ENCAPSED_AND_WHITESPACE, 68 | T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING, 69 | ); 70 | 71 | /** @var array */ 72 | private $htmlTokens = array( 73 | T_INLINE_HTML => T_INLINE_HTML, 74 | ); 75 | 76 | /** 77 | * @param ConsoleColor $color 78 | * @throws \PHP_Parallel_Lint\PhpConsoleColor\InvalidStyleException 79 | */ 80 | public function __construct(ConsoleColor $color) 81 | { 82 | $this->color = $color; 83 | 84 | foreach ($this->defaultTheme as $name => $styles) { 85 | if (!$this->color->hasTheme($name)) { 86 | $this->color->addTheme($name, $styles); 87 | } 88 | } 89 | } 90 | 91 | /** 92 | * @param string $source 93 | * @param int $lineNumber 94 | * @param int $linesBefore 95 | * @param int $linesAfter 96 | * @return string 97 | * @throws \PHP_Parallel_Lint\PhpConsoleColor\InvalidStyleException 98 | * @throws \InvalidArgumentException 99 | */ 100 | public function getCodeSnippet($source, $lineNumber, $linesBefore = 2, $linesAfter = 2) 101 | { 102 | $tokenLines = $this->getHighlightedLines($source); 103 | 104 | $offset = $lineNumber - $linesBefore - 1; 105 | $offset = max($offset, 0); 106 | 107 | if ($lineNumber <= $linesBefore) { 108 | $length = $lineNumber + $linesAfter; 109 | } else { 110 | $length = $linesAfter + $linesBefore + 1; 111 | } 112 | 113 | $tokenLines = array_slice($tokenLines, $offset, $length, $preserveKeys = true); 114 | 115 | $lines = $this->colorLines($tokenLines); 116 | 117 | return $this->lineNumbers($lines, $lineNumber); 118 | } 119 | 120 | /** 121 | * @param string $source 122 | * @return string 123 | * @throws \PHP_Parallel_Lint\PhpConsoleColor\InvalidStyleException 124 | * @throws \InvalidArgumentException 125 | */ 126 | public function getWholeFile($source) 127 | { 128 | $tokenLines = $this->getHighlightedLines($source); 129 | $lines = $this->colorLines($tokenLines); 130 | return implode(PHP_EOL, $lines); 131 | } 132 | 133 | /** 134 | * @param string $source 135 | * @return string 136 | * @throws \PHP_Parallel_Lint\PhpConsoleColor\InvalidStyleException 137 | * @throws \InvalidArgumentException 138 | */ 139 | public function getWholeFileWithLineNumbers($source) 140 | { 141 | $tokenLines = $this->getHighlightedLines($source); 142 | $lines = $this->colorLines($tokenLines); 143 | return $this->lineNumbers($lines); 144 | } 145 | 146 | /** 147 | * @param string $source 148 | * @return array 149 | */ 150 | private function getHighlightedLines($source) 151 | { 152 | $source = str_replace(array("\r\n", "\r"), "\n", $source); 153 | $tokens = $this->tokenize($source); 154 | return $this->splitToLines($tokens); 155 | } 156 | 157 | /** 158 | * @param string $source 159 | * @return array 160 | */ 161 | private function tokenize($source) 162 | { 163 | $tokens = token_get_all($source); 164 | 165 | $output = array(); 166 | $currentType = null; 167 | $buffer = ''; 168 | 169 | foreach ($tokens as $token) { 170 | if (is_array($token)) { 171 | if ($token[0] !== T_WHITESPACE) { 172 | $newType = $this->getTokenType($token); 173 | } 174 | } else { 175 | $newType = $token === '"' ? self::TOKEN_STRING : self::TOKEN_KEYWORD; 176 | } 177 | 178 | if ($currentType === null) { 179 | $currentType = $newType; 180 | } 181 | 182 | if ($currentType !== $newType) { 183 | $output[] = array($currentType, $buffer); 184 | $buffer = ''; 185 | $currentType = $newType; 186 | } 187 | 188 | $buffer .= is_array($token) ? $token[1] : $token; 189 | } 190 | 191 | if (isset($newType)) { 192 | $output[] = array($newType, $buffer); 193 | } 194 | 195 | return $output; 196 | } 197 | 198 | /** 199 | * @param array $arrayToken 200 | * @return string 201 | */ 202 | private function getTokenType($arrayToken) 203 | { 204 | switch (true) { 205 | case isset($this->phpTagTokens[$arrayToken[0]]): 206 | case isset($this->magicConstantTokens[$arrayToken[0]]): 207 | case isset($this->miscTokens[$arrayToken[0]]): 208 | return self::TOKEN_DEFAULT; 209 | 210 | case isset($this->commentTokens[$arrayToken[0]]): 211 | return self::TOKEN_COMMENT; 212 | 213 | case isset($this->textStringTokens[$arrayToken[0]]): 214 | return self::TOKEN_STRING; 215 | 216 | case isset($this->htmlTokens[$arrayToken[0]]): 217 | return self::TOKEN_HTML; 218 | } 219 | 220 | // phpcs:disable PHPCompatibility.Constants.NewConstants -- The new token constants are only used when defined. 221 | 222 | // Traits didn't exist in PHP 5.3 yet, so the trait magic constant needs special casing for PHP >= 5.4. 223 | // __TRAIT__ will tokenize as T_STRING in PHP 5.3, so, the end result will be the same cross-version. 224 | if (defined('T_TRAIT_C') && $arrayToken[0] === T_TRAIT_C) { 225 | return self::TOKEN_DEFAULT; 226 | } 227 | 228 | // Handle PHP >= 8.0 namespaced name tokens. 229 | // https://www.php.net/manual/en/migration80.incompatible.php#migration80.incompatible.tokenizer 230 | if ( 231 | (defined('T_NAME_QUALIFIED') && $arrayToken[0] === T_NAME_QUALIFIED) 232 | || (defined('T_NAME_FULLY_QUALIFIED') && $arrayToken[0] === T_NAME_FULLY_QUALIFIED) 233 | || (defined('T_NAME_RELATIVE') && $arrayToken[0] === T_NAME_RELATIVE) 234 | ) { 235 | return self::TOKEN_DEFAULT; 236 | } 237 | 238 | // phpcs:enable 239 | 240 | return self::TOKEN_KEYWORD; 241 | } 242 | 243 | /** 244 | * @param array $tokens 245 | * @return array 246 | */ 247 | private function splitToLines(array $tokens) 248 | { 249 | $lines = array(); 250 | 251 | $line = array(); 252 | foreach ($tokens as $token) { 253 | foreach (explode("\n", $token[1]) as $count => $tokenLine) { 254 | if ($count > 0) { 255 | $lines[] = $line; 256 | $line = array(); 257 | } 258 | 259 | if ($tokenLine === '') { 260 | continue; 261 | } 262 | 263 | $line[] = array($token[0], $tokenLine); 264 | } 265 | } 266 | 267 | $lines[] = $line; 268 | 269 | return $lines; 270 | } 271 | 272 | /** 273 | * @param array $tokenLines 274 | * @return array 275 | * @throws \PHP_Parallel_Lint\PhpConsoleColor\InvalidStyleException 276 | * @throws \InvalidArgumentException 277 | */ 278 | private function colorLines(array $tokenLines) 279 | { 280 | $lines = array(); 281 | foreach ($tokenLines as $lineCount => $tokenLine) { 282 | $line = ''; 283 | foreach ($tokenLine as $token) { 284 | list($tokenType, $tokenValue) = $token; 285 | if ($this->color->hasTheme($tokenType)) { 286 | $line .= $this->color->apply($tokenType, $tokenValue); 287 | } else { 288 | $line .= $tokenValue; 289 | } 290 | } 291 | $lines[$lineCount] = $line; 292 | } 293 | 294 | return $lines; 295 | } 296 | 297 | /** 298 | * @param array $lines 299 | * @param null|int $markLine 300 | * @return string 301 | * @throws \PHP_Parallel_Lint\PhpConsoleColor\InvalidStyleException 302 | */ 303 | private function lineNumbers(array $lines, $markLine = null) 304 | { 305 | end($lines); 306 | $lineStrlen = strlen(key($lines) + 1); 307 | 308 | $snippet = ''; 309 | foreach ($lines as $i => $line) { 310 | if ($markLine !== null) { 311 | $snippet .= ($markLine === $i + 1 ? $this->color->apply(self::ACTUAL_LINE_MARK, ' > ') : ' '); 312 | } 313 | 314 | $snippet .= $this->color->apply(self::LINE_NUMBER, str_pad($i + 1, $lineStrlen, ' ', STR_PAD_LEFT) . '| '); 315 | $snippet .= $line . PHP_EOL; 316 | } 317 | 318 | return rtrim($snippet, PHP_EOL); 319 | } 320 | } 321 | --------------------------------------------------------------------------------