diff --git a/README.md b/README.md index b7bd3f3..7c87cc4 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ require 'sage.phar'; sage('Hello, 🌎!'); ``` - ## Usage ```php @@ -45,7 +44,7 @@ sage(1); // shortcut for dumping trace | `ssage` | `ss` | Simple dump | | `ssaged` | `ssd` | Simple dump & die | | `sagetrace` | `s(1)` | Debug backtrace (same as `\Sage::trace()`) | -| --- | `s(2)` | Backtrace without the arguments - just the paths | +| --- | `s(2)` | Backtrace without the arguments - just the paths | ### Simple dump: @@ -57,7 +56,7 @@ sage(1); // shortcut for dumping trace ### More cool stuff 🀯 -Sage determines the **passed variable name** and as a side effect can detect all sort of prefixes to the call. Use it +Sage determines the **passed variable name** and as a side effect can detect all sort of prefixes to the call. Use it for some common on-the-fly adjustments to the dump output. Examples: @@ -65,8 +64,9 @@ Examples: ```php ~ss($var); // outputs plain text $output = @ss($var); // returns output instead of displaying it +! sage($var); // ignores depth limit for large objects print sd($var); // saves output into "sage.html" in the current directory -+sage($var); // ignores depth limit for large objects +print ! sd($var); // saves output into "sage.html" while also ignoring the output depth limit! ``` See [Advanced section](#-advanced-tips--tricks) below for more tricks. @@ -132,9 +132,8 @@ Sage::$theme = Sage::THEME_LIGHT; Add this entry to the `autoload.files` configuration key in `composer.json`: -```json +```js "autoload": { - /* ... */ "files": [ "config/sage.php" /* <--------------- this line */ ] @@ -182,7 +181,7 @@ Make visible source file paths clickable to open your editor. Available options * `'emacs'` * `'macvim'` * `'phpstorm'` -* `'phpstorm-plugin'` - default, +* `'phpstorm-remote'` - default, requires [IDE Remote Control](https://plugins.jetbrains.com/plugin/19991-ide-remote-control) plugin. * `'idea'` * `'vscode'` @@ -296,6 +295,9 @@ Sage::dump($GLOBALS, $_SERVER); // ss() will display a more basic, javascript-free display (but with colors) ss($GLOBALS, $_SERVER); +// to recap: s() or sage() - dumps. Add "d" to die afterwards: sd(), saged() +// preppend "s" to simplify output: ss(), ssage(). +// works in combination, too: ssd() and ssagedd() will dump in "simple mode" and die! // prepending a tilde will make the output *even more basic* (rich->basic and basic->plain text) ~d($GLOBALS, $_SERVER); // more on modifiers below @@ -316,7 +318,8 @@ Sage::enabled(false); sd('Get off my lawn!'); // no effect ``` -* Sage supports keyboard shortcuts! Just press d when viewing output and the rest is self-explanatory (p.s. +* Sage supports keyboard shortcuts! Just press d when viewing output and the rest is self-explanatory, try it + out! (p.s. vim-style `hjkl` works as well); * Call `Sage::enabled(Sage::MODE_PLAIN);` to switch to a simpler, js-free output. * Call `Sage::enabled(Sage::MODE_TEXT_ONLY);` for pure-plain text output which you can save or pass around by first @@ -341,17 +344,17 @@ sd('Get off my lawn!'); // no effect | Prefix | | Example | |--------|----------------------------------------------|--------------| | print | Puts output into current DIR as sage.html | print sage() | - | + | Dump ignoring depth limits for large objects | + sage() | + | ! | Dump ignoring depth limits for large objects | ! sage() | | ~ | Simplifies sage output (rich->html->plain) | ~ sage() | | - | Clean up any output before dumping | - sage() | - | ! | Expand all nodes (in rich view) | ! sage() | + | + | Expand all nodes (in rich view) | + sage() | | @ | Return output instead of displaying it | @ sage() | * Sage also includes a naΓ―ve profiler you may find handy. It's for determining relatively which code blocks take longer than others: ```php -Sage::dump( microtime() ); // just pass microtime() +Sage::dump( microtime() ); // just pass microtime() - also works if you pass NOTHING: s(); sleep( 1 ); Sage::dump( microtime(), 'after sleep(1)' ); sleep( 2 ); @@ -395,10 +398,10 @@ sd( microtime(), 'final call, after sleep(2)' ); ### πŸ’¬ Why does Sage look so much like Kint? A.K.A. Why does this have so few stars? Because it is Kint, and I am its author, however the project was [**forcibly taken over -**](https://github.com/kint-php/kint/commit/1ea81f3add81b586756515673f8364f60feb86a3) from me by a malicious +**](https://github.com/kint-php/kint/commit/1ea81f3add81b586756515673f8364f60feb86a3) by a malicious contributor! -Instead of fighting DMCA windmills, I chose to fork and rename the last **good** version and continue under a new name! +Instead of fighting DMCA windmills, I chose to fork and rename the last good version and continue under a new name! You can use Sage as a drop-in replacement for Kint. Simple. diff --git a/Sage.php b/Sage.php index 368cf01..f814596 100644 --- a/Sage.php +++ b/Sage.php @@ -67,7 +67,7 @@ class Sage * 'emacs' => 'emacs://open?url=file://%file&line=%line', * 'macvim' => 'mvim://open/?url=file://%file&line=%line', * 'phpstorm' => 'phpstorm://open?file=%file&line=%line', - * 'phpstorm-plugin' => 'http://localhost:63342/api/file/%file:%line', + * 'phpstorm-remote' => 'http://localhost:63342/api/file/%file:%line', * 'idea' => 'idea://open?file=%file&line=%line', * 'vscode' => 'vscode://file/%file:%line', * 'vscode-insiders' => 'vscode-insiders://file/%file:%line', @@ -84,13 +84,13 @@ class Sage * * Example: * // works with for PHPStorm and IDE Remote Control Plugin - * Sage::$editor = 'phpstorm-plugin'; + * Sage::$editor = 'phpstorm-remote'; * Example: * // same result as above, but explicitly defined * Sage::$editor = 'http://localhost:63342/api/file/f:%line'; * * Default: - * ini_get('xdebug.file_link_format') ?: 'phpstorm-plugin' + * ini_get('xdebug.file_link_format') ?: 'phpstorm-remote' * */ public static $editor; @@ -214,6 +214,9 @@ class Sage */ public static $aliases = []; + /** @var bool @internal */ + public static $simplify = false; + /* * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— * β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β• @@ -311,11 +314,11 @@ public static function trace($trace = null) * |-------|----------------------------------------------| * | | Example: `+ saged('magic');` | * |-------|----------------------------------------------| - * | + | Dump ignoring depth limits for large objects | + * | ! | Dump ignoring depth limits for large objects | * | print | Puts output into current DIR as sage.html | * | ~ | Simplifies sage output (rich->html->plain) | * | - | Clean up any output before dumping | - * | ! | Expand all nodes (in rich view) | + * | + | Expand all nodes (in rich view) | * | @ | Return output instead of displaying it | * |-------|----------------------------------------------| * ``` @@ -341,34 +344,55 @@ public static function trace($trace = null) */ public static function dump($data = null) { - $enabledMode = self::enabled(); - if (! $enabledMode) { + $enabledModeOriginal = self::enabled(); + + if (! $enabledModeOriginal) { return 5463; } self::_init(); - list($names, $modifiers, $callee, $previousCaller, $miniTrace) = self::_getCalleeInfo( + [$names, $modifiers, $callee, $previousCaller, $miniTrace] = self::_getCalleeInfo( defined('DEBUG_BACKTRACE_IGNORE_ARGS') ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) : debug_backtrace() ); // auto-detect mode if not explicitly set - if (! in_array($enabledMode, array( - self::MODE_RICH, - self::MODE_TEXT_ONLY, - self::MODE_CLI, - self::MODE_PLAIN - ), true)) { - if (self::$outputFile && substr(self::$outputFile, -5) === '.html') { + if ($enabledModeOriginal === true) { + if (! empty($modifiers) && strpos($modifiers, 'print') !== false && isset($callee['file'])) { + $newMode = self::MODE_RICH; + } elseif (self::$outputFile && substr(self::$outputFile, -5) === '.html') { $newMode = self::MODE_RICH; } else { - $newMode = (PHP_SAPI === 'cli' && self::$cliDetection === true) + $newMode = PHP_SAPI === 'cli' && self::$cliDetection === true ? self::MODE_CLI : self::MODE_RICH; } + if (self::$simplify) { + switch ($newMode) { + case self::MODE_RICH: + $newMode = self::MODE_PLAIN; + break; + case self::MODE_CLI: + $newMode = self::MODE_TEXT_ONLY; + break; + } + } + + if (! empty($modifiers) && strpos($modifiers, '~') !== false) { + switch ($newMode) { + case self::MODE_RICH: + $newMode = self::MODE_PLAIN; + break; + case self::MODE_PLAIN: + case self::MODE_CLI: + $newMode = self::MODE_TEXT_ONLY; + break; + } + } + self::enabled($newMode); } @@ -386,45 +410,28 @@ public static function dump($data = null) ob_end_clean(); } } - if (! empty($modifiers) && strpos($modifiers, '!') !== false) { + if (! empty($modifiers) && strpos($modifiers, '+') !== false) { $expandedByDefaultOldValue = self::$expandedByDefault; self::$expandedByDefault = true; } - if (! empty($modifiers) && strpos($modifiers, '+') !== false) { + + if (! empty($modifiers) && strpos($modifiers, '!') !== false) { $maxLevelsOldValue = self::$maxLevels; self::$maxLevels = false; } + if (! empty($modifiers) && strpos($modifiers, '@') !== false) { $returnOldValue = self::$returnOutput; self::$returnOutput = true; } + if (self::$returnOutput) { $decorator::$firstRun = true; } - if (! empty($modifiers) && strpos($modifiers, '~') !== false) { - // restore values for whatever decorator was set previously - $decorator::$firstRun = $firstRunOldValue; - - // simplify mode - $decorator = 'SageDecoratorsPlain'; - $firstRunOldValue = $decorator::$firstRun; - if ($enabledMode !== self::MODE_TEXT_ONLY) { // if not already in plainest mode... - self::enabled( // remove cli colors in cli mode; remove rich interface in HTML mode - $enabledMode === self::MODE_PLAIN ? self::MODE_TEXT_ONLY : self::MODE_PLAIN - ); - } - - // process modifier combinations - if (strpos($modifiers, '-') !== false) { - $decorator::$firstRun = true; - } - } if (! empty($modifiers) && strpos($modifiers, 'print') !== false && isset($callee['file'])) { - self::enabled(Sage::MODE_RICH); $outputFileOldValue = self::$outputFile; self::$outputFile = dirname($callee['file']) . '/sage.html'; - $decorator = 'SageDecoratorsRich'; } if (self::$outputFile && ! isset(self::$_openedFile[self::$outputFile])) { @@ -489,7 +496,7 @@ public static function dump($data = null) // displayed names might be wrong, at least don't throw an error $output .= call_user_func( array($decorator, 'decorate'), - SageParser::process($argument, isset($names[$k]) ? $names[$k] : '') + SageParser::process($argument, empty($names[$k]) ? '???' : $names[$k]), ); } } @@ -508,7 +515,7 @@ public static function dump($data = null) fwrite(self::$_openedFile[self::$outputFile], $output); } - self::enabled($enabledMode); + self::enabled($enabledModeOriginal); $decorator::$firstRun = false; if (! empty($modifiers)) { @@ -516,26 +523,31 @@ public static function dump($data = null) $decorator::$firstRun = $firstRunOldValue; } - if (strpos($modifiers, '!') !== false) { + if (strpos($modifiers, '+') !== false) { self::$expandedByDefault = $expandedByDefaultOldValue; } - if (strpos($modifiers, '+') !== false) { + if (strpos($modifiers, '!') !== false) { self::$maxLevels = $maxLevelsOldValue; } + if (! empty($modifiers) && strpos($modifiers, 'print') !== false && isset($callee['file'])) { + $tmp = self::$outputFile; + self::$outputFile = $outputFileOldValue; + + if (strpos($modifiers, '@') === false) { + echo 'Sage -> ' . $tmp . PHP_EOL; + } + + return 5463; + } + if (strpos($modifiers, '@') !== false) { self::$returnOutput = $returnOldValue; $decorator::$firstRun = $firstRunOldValue; return $output; } - if (! empty($modifiers) && strpos($modifiers, 'print') !== false && isset($callee['file'])) { - $tmp = self::$outputFile; - self::$outputFile = $outputFileOldValue; - - return 'Sage -> ' . $tmp . PHP_EOL; - } } if (self::$returnOutput) { @@ -655,6 +667,15 @@ private static function _getCalleeInfo($trace) } // open the file and read it up to the position where the function call expression ended + // TODO since PHP 8.2 backtrace reports the lineno of the function/method name! + // https://github.com/php/php-src/pull/8818 + // $file = new SplFileObject($callee['file']); + // do { + // $file->seek($callee['line']); + // $contents = $file->current(); // $contents would hold the data from line x + // + // } while (! $file->eof()); + $file = fopen($callee['file'], 'r'); $line = 0; $source = ''; @@ -681,7 +702,7 @@ private static function _getCalleeInfo($trace) [\x07{(] # search for modifiers (group 1) - ([-+!@~]|print*)? + ([print\x07-+!@~]*)? # spaces \x07* @@ -716,7 +737,7 @@ private static function _getCalleeInfo($trace) return array(array(), $modifiers, $callee, $previousCaller, $miniTrace); } - $modifiers = $modifiers[0]; + $modifiers = str_replace("\x07", '', $modifiers[0]); $paramsString = preg_replace("[\x07+]", ' ', substr($source, $bracket[1] + 1)); // we now have a string like this: // ); @@ -773,24 +794,14 @@ private static function _getCalleeInfo($trace) } // by now we have an un-nested arguments list, lets make it to an array for processing further - $arguments = explode(',', preg_replace("[\x07+]", "\x07", $paramsString)); + $arguments = explode(',', preg_replace("[\x07+]", '...', $paramsString)); // test each argument whether it was passed literary or was it an expression or a variable name - $parameters = array(); - $blacklist = array('null', 'true', 'false', "array(\x07)", 'array()', "\"\x07\"", '[\x07]', 'b\"\x07\"',); - foreach ($arguments as $argument) { + foreach ($arguments as &$argument) { $argument = trim($argument); - - if (is_numeric($argument) - || in_array(str_replace("'", '"', strtolower($argument)), $blacklist, true) - ) { - $parameters[] = null; - } else { - $parameters[] = $argument; - } } - return array($parameters, $modifiers, $callee, $previousCaller, $miniTrace); + return array($arguments, $modifiers, $callee, $previousCaller, $miniTrace); } /** @@ -999,7 +1010,7 @@ private static function _init() // 4. Load default from Sage self::_initSetting( 'editor', - ini_get('xdebug.file_link_format') ? ini_get('xdebug.file_link_format') : 'phpstorm-plugin' + ini_get('xdebug.file_link_format') ? ini_get('xdebug.file_link_format') : 'phpstorm-remote' ); self::_initSetting('fileLinkServerPath', null); self::_initSetting('fileLinkLocalPath', null); diff --git a/composer.json b/composer.json index a8b4032..49ce359 100644 --- a/composer.json +++ b/composer.json @@ -19,9 +19,9 @@ "php": ">=5.1.0" }, "require-dev": { - "pestphp/pest": "^1.21", - "seld/phar-utils": "^1.2", - "spatie/pest-plugin-snapshots": "^1.1", + "pestphp/pest": "^1.0", + "seld/phar-utils": "^1.0", + "spatie/pest-plugin-snapshots": "^1.0", "symfony/finder": "^3.0 || ^4.0 || ^5.0", "symfony/var-dumper": "^6.1" }, diff --git a/decorators/SageDecoratorsPlain.php b/decorators/SageDecoratorsPlain.php index e6ee256..16b69da 100644 --- a/decorators/SageDecoratorsPlain.php +++ b/decorators/SageDecoratorsPlain.php @@ -8,6 +8,7 @@ class SageDecoratorsPlain { public static $firstRun = true; private static $_enableColors; + private static $levelColors = []; public static function decorate(SageVariableData $varData, $level = 0) { @@ -19,7 +20,22 @@ public static function decorate(SageVariableData $varData, $level = 0) $output .= self::_title($name); } - $space = str_repeat($s = ' ', $level); + // make each level different-color + self::$levelColors = array_slice(self::$levelColors, 0, $level); + $s = ' '; + $space = ''; + if (Sage::enabled() === Sage::MODE_CLI) { + for ($i = 0; $i < $level; $i++) { + if (! array_key_exists($i, self::$levelColors)) { + self::$levelColors[$i] = rand(1, 231); + } + $color = self::$levelColors[$i]; + $space .= "\x1b[38;5;{$color}m┆\x1b[0m "; + } + } else { + $space = str_repeat($s, $level); + } + $output .= $space . self::_drawHeader($varData); if (isset($varData->extendedValue)) { @@ -41,11 +57,11 @@ public static function decorate(SageVariableData $varData, $level = 0) // throw new RuntimeException(); // $output .= self::decorate($varData->extendedValue, $level + 1); // it's SageVariableData } - $output .= $space . ($varData->type === 'array' ? ']' : ')') . PHP_EOL; - } else { - $output .= PHP_EOL; + $output .= $space . ($varData->type === 'array' ? ']' : ')'); } + $output .= PHP_EOL; + return $output; } @@ -158,17 +174,20 @@ private static function _colorize($text, $type, $nlAfter = true) } switch ($type) { - case 'value': + case 'key': + $text = "{$text}"; + break; + case 'access': $text = "{$text}"; break; - case 'key': - // $text = $text; + case 'value': + $text = "{$text}"; break; case 'type': $text = "{$text}"; break; case 'header': - $text = "
{$text}
"; + $text = "

{$text}

"; break; } @@ -179,11 +198,31 @@ private static function _colorize($text, $type, $nlAfter = true) return $text . $nl; } + /* + * Black 0;30 Dark Gray 1;30 + * Red 0;31 Light Red 1;31 + * Green 0;32 Light Green 1;32 + * Brown 0;33 Yellow 1;33 + * Blue 0;34 Light Blue 1;34 + * Purple 0;35 Light Purple 1;35 + * Cyan 0;36 Light Cyan 1;36 + * Light Gray 0;37 White 1;37 + * + * Format: + * \x1b[[light];[color];[font]m + * light: 1/0 + * color: 30-37 + * font: 1 - bold, 3 - italic, 4 - underline, 7 - invert, 9 - strikethrough + * + * https://misc.flogisoft.com/bash/tip_colors_and_formatting + */ + $optionsMap = array( - 'key' => "\x1b[33m", // yellow - 'header' => "\x1b[36m", // cyan - 'type' => "\x1b[35;1m", // magenta bold - 'value' => "\x1b[32m", // green + 'key' => "\x1b[32m", + 'access' => "\x1b[3m", + 'header' => "\x1b[38;5;75m", + 'type' => "\x1b[1m", + 'value' => "\x1b[31m", ); return $optionsMap[$type] . $text . "\x1b[0m" . $nl; @@ -220,33 +259,33 @@ public static function wrapStart() public static function wrapEnd($callee, $miniTrace, $prevCaller) { - $lastLine = self::_colorize(str_repeat('═', 80), 'header'); - $isHtml = Sage::enabled() === Sage::MODE_PLAIN; - $lastChar = $isHtml ? '' : ''; + $lastLine = str_repeat('═', 80); + $lastChar = Sage::enabled() === Sage::MODE_PLAIN ? '' : ''; $traceDisplay = ''; if (! Sage::$displayCalledFrom) { - return $lastLine . $lastChar; + return self::_colorize($lastLine . $lastChar, 'header'); } if (! empty($miniTrace)) { - $traceDisplay = $isHtml ? '
    ' : PHP_EOL; + $traceDisplay = PHP_EOL; $i = 0; foreach ($miniTrace as $step) { - $traceDisplay .= $isHtml ? '
  1. ' : ' '; + $traceDisplay .= ' ' . $i + 2 . '. '; $traceDisplay .= SageHelper::ideLink($step['file'], $step['line']); - $traceDisplay .= $isHtml ? '' : PHP_EOL; + $traceDisplay .= PHP_EOL; if ($i++ > 2) { break; } } - $traceDisplay .= $isHtml ? '
' : ''; + $traceDisplay .= ''; } - return $lastLine - . self::_colorize( - 'Call stack ' . SageHelper::ideLink($callee['file'], $callee['line']) . $traceDisplay, - 'header' + return self::_colorize( + $lastLine . PHP_EOL + . 'Call stack ' . SageHelper::ideLink($callee['file'], $callee['line']) + . $traceDisplay, + 'header', ) . $lastChar; } @@ -256,7 +295,7 @@ private static function _drawHeader(SageVariableData $varData) $output = ''; if ($varData->access) { - $output .= ' ' . $varData->access; + $output .= ' ' . self::_colorize(SageHelper::esc($varData->access), 'access', false); } if ($varData->name !== null && $varData->name !== '') { @@ -267,12 +306,13 @@ private static function _drawHeader(SageVariableData $varData) $output .= ' ' . $varData->operator; } - $output .= ' ' . self::_colorize($varData->type, 'type', false); - + $type = $varData->type; if ($varData->size !== null) { - $output .= ' (' . $varData->size . ')'; + $type .= ' (' . $varData->size . ')'; } + $output .= ' ' . self::_colorize($type, 'type', false); + if ($varData->value !== null && $varData->value !== '') { $output .= ' ' . self::_colorize($varData->value, 'value', false); } @@ -303,7 +343,11 @@ function_exists('sapi_windows_vt100_support') } return <<<'HTML' - + +HTML + . <<window.onload=function(){document.querySelectorAll('._sage_plain a').forEach(el=>el.addEventListener('click',e=>{e.preventDefault();let X=new XMLHttpRequest;X.open('GET',e.target.href);X.send()}))} + HTML; } } diff --git a/decorators/SageDecoratorsRich.php b/decorators/SageDecoratorsRich.php index de23764..cb51774 100644 --- a/decorators/SageDecoratorsRich.php +++ b/decorators/SageDecoratorsRich.php @@ -233,6 +233,8 @@ public static function wrapEnd($callee, $miniTrace, $prevCaller) $calleeInfo = '' . $calleeInfo; } + $callingFunction .= ' @ ' . date('Y-m-d H:i:s'); + return '