diff --git a/src/Psy/Command/HelpCommand.php b/src/Psy/Command/HelpCommand.php index 99c47a439..cecd35d96 100644 --- a/src/Psy/Command/HelpCommand.php +++ b/src/Psy/Command/HelpCommand.php @@ -11,6 +11,7 @@ namespace Psy\Command; +use Symfony\Component\Console\Helper\TableHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -65,11 +66,10 @@ protected function execute(InputInterface $input, OutputInterface $output) // list available commands $commands = $this->getApplication()->all(); - $width = 0; - foreach ($commands as $command) { - $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; - } - $width += 2; + $table = $this->getApplication()->getHelperSet()->get('table') + ->setLayout(TableHelper::LAYOUT_BORDERLESS) + ->setHorizontalBorderChar('') + ->setCrossingChar(''); foreach ($commands as $name => $command) { if ($name !== $command->getName()) { @@ -77,15 +77,21 @@ protected function execute(InputInterface $input, OutputInterface $output) } if ($command->getAliases()) { - $aliases = sprintf(' Aliases: %s', implode(', ', $command->getAliases())); + $aliases = sprintf('Aliases: %s', implode(', ', $command->getAliases())); } else { $aliases = ''; } - $messages[] = sprintf(" %-${width}s %s%s", $name, $command->getDescription(), $aliases); + $table->addRow(array( + sprintf('%s', $name), + $command->getDescription(), + $aliases, + )); } - $output->page($messages); + $output->page(function($output) use ($table) { + $table->render($output); + }); } } } diff --git a/src/Psy/Command/HistoryCommand.php b/src/Psy/Command/HistoryCommand.php index 66e8564a2..71b4d4bd3 100644 --- a/src/Psy/Command/HistoryCommand.php +++ b/src/Psy/Command/HistoryCommand.php @@ -59,10 +59,14 @@ protected function configure() new InputOption('clear', '', InputOption::VALUE_NONE, 'Clear the history.') )) ->setDescription('Show the Psy Shell history.') - - // TODO: help! ->setHelp(<<>>> history --grep /[bB]acon/ +>>> history --show 0..10 --replay +>>> history --clear +>>> history --tail 1000 --save somefile.txt HELP ); } diff --git a/src/Psy/Command/ListCommand.php b/src/Psy/Command/ListCommand.php index 18306649a..ef9f5f521 100644 --- a/src/Psy/Command/ListCommand.php +++ b/src/Psy/Command/ListCommand.php @@ -29,6 +29,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Helper\TableHelper; /** * List available local variables, object properties, etc. @@ -119,9 +120,18 @@ protected function execute(InputInterface $input, OutputInterface $output) $reflector = null; } + // TODO: something cleaner than this :-/ + if ($input->getOption('long')) { + $output->startPaging(); + } + foreach ($this->enumerators as $enumerator) { $this->$method($output, $enumerator->enumerate($input, $reflector, $target)); } + + if ($input->getOption('long')) { + $output->stopPaging(); + } } /** @@ -175,15 +185,18 @@ protected function writeLong(OutputInterface $output, array $result = null) { if ($result === null) return; + $table = $this->getTable(); + foreach ($result as $label => $items) { $output->writeln(''); $output->writeln(sprintf('%s:', $label)); - $pad = max(array_map('strlen', array_keys($items))); + $table->setRows(array()); foreach ($items as $item) { - $itemPad = $pad + (2 * strlen($item['style'])) + 5; - $output->writeln(sprintf(" %-${itemPad}s %s", $this->formatItemName($item), $item['value'])); + $table->addRow(array($this->formatItemName($item), $item['value'])); } + + $table->render($output); } } @@ -256,4 +269,17 @@ private function validateInput(InputInterface $input) $input->setOption('methods', true); } } + + /** + * Get a TableHelper instance. + * + * @return TableHelper + */ + private function getTable() + { + return $this->getApplication()->getHelperSet()->get('table') + ->setLayout(TableHelper::LAYOUT_BORDERLESS) + ->setHorizontalBorderChar('') + ->setCrossingChar(''); + } } diff --git a/src/Psy/Command/ShowCommand.php b/src/Psy/Command/ShowCommand.php index 037805ed8..3f840077d 100644 --- a/src/Psy/Command/ShowCommand.php +++ b/src/Psy/Command/ShowCommand.php @@ -34,10 +34,12 @@ protected function configure() new InputArgument('value', InputArgument::REQUIRED, 'Function, class, instance, constant, method or property to show.'), )) ->setDescription('Show the code for an object, class, constant, method or property.') - - // TODO: help! ->setHelp(<<>>> show \$myObject +>>> show Psy\Shell::debug HELP ); } diff --git a/src/Psy/Command/TraceCommand.php b/src/Psy/Command/TraceCommand.php index 0e6cb8fe3..e209832db 100644 --- a/src/Psy/Command/TraceCommand.php +++ b/src/Psy/Command/TraceCommand.php @@ -15,6 +15,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Formatter\OutputFormatter; /** * Show the current stack trace. @@ -68,6 +69,10 @@ protected function execute(InputInterface $input, OutputInterface $output) */ protected function getBacktrace(\Exception $e, $count = null, $includePsy = true) { + if ($cwd = getcwd()) { + $cwd = rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + } + if ($count === null) { $count = PHP_INT_MAX; } @@ -96,12 +101,36 @@ protected function getBacktrace(\Exception $e, $count = null, $includePsy = true $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; $function = $trace[$i]['function']; - $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $file = isset($trace[$i]['file']) ? $this->replaceCwd($cwd, $trace[$i]['file']) : 'n/a'; $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; - $lines[] = sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line); + $lines[] = sprintf( + ' %s%s%s() at %s:%s', + OutputFormatter::escape($class), + OutputFormatter::escape($type), + OutputFormatter::escape($function), + OutputFormatter::escape($file), + OutputFormatter::escape($line) + ); } return $lines; } + + /** + * Replace the given directory from the start of a filepath. + * + * @param string $cwd + * @param string $file + * + * @return string + */ + private function replaceCwd($cwd, $file) + { + if ($cwd === false) { + return $file; + } else { + return preg_replace('/^'.preg_quote($cwd, '/').'/', '', $file); + } + } } diff --git a/src/Psy/Command/WtfCommand.php b/src/Psy/Command/WtfCommand.php index 9da2466cb..efa6135ea 100644 --- a/src/Psy/Command/WtfCommand.php +++ b/src/Psy/Command/WtfCommand.php @@ -50,7 +50,7 @@ protected function configure() ->setName('wtf') ->setAliases(array('last-exception', 'wtf?')) ->setDefinition(array( - new InputArgument('incredulity', InputArgument::OPTIONAL, 'Number of lines to show'), + new InputArgument('incredulity', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Number of lines to show'), new InputOption('verbose', 'v', InputOption::VALUE_NONE, 'Show entire backtrace.'), )) ->setDescription('Show the backtrace of the most recent exception.') @@ -89,12 +89,20 @@ protected function getHiddenOptions() */ protected function execute(InputInterface $input, OutputInterface $output) { - $incredulity = $input->getArgument('incredulity'); + $incredulity = implode('', $input->getArgument('incredulity')); if (strlen(preg_replace('/[\\?!]/', '', $incredulity))) { throw new \InvalidArgumentException('Incredulity must include only "?" and "!".'); } - $count = $input->getOption('verbose') ? PHP_INT_MAX : pow(2, max(0, (strlen($incredulity) - 1))); - $output->page($this->getBacktrace($this->context->getLastException(), $count), ShellOutput::NUMBER_LINES | ShellOutput::OUTPUT_RAW); + $exception = $this->context->getLastException(); + $count = $input->getOption('verbose') ? PHP_INT_MAX : pow(2, max(0, (strlen($incredulity) - 1))); + $trace = $this->getBacktrace($exception, $count); + + $shell = $this->getApplication(); + $output->page(function($output) use ($exception, $trace, $shell) { + $shell->renderException($exception, $output); + $output->writeln('--'); + $output->write($trace, true, ShellOutput::NUMBER_LINES); + }); } } diff --git a/src/Psy/Output/ShellOutput.php b/src/Psy/Output/ShellOutput.php index 434273882..f54b07ce3 100644 --- a/src/Psy/Output/ShellOutput.php +++ b/src/Psy/Output/ShellOutput.php @@ -72,7 +72,7 @@ public function page($messages, $type = 0) throw new \InvalidArgumentException('Paged output requires a string, array or callback.'); } - $this->paging++; + $this->startPaging(); if (is_callable($messages)) { $messages($this); @@ -80,6 +80,22 @@ public function page($messages, $type = 0) $this->write($messages, true, $type); } + $this->stopPaging(); + } + + /** + * Start sending output to the output pager. + */ + public function startPaging() + { + $this->paging++; + } + + /** + * Stop paging output and flush the output pager. + */ + public function stopPaging() + { $this->paging--; $this->closePager(); } @@ -106,7 +122,7 @@ public function write($messages, $newline = false, $type = 0) if ($type & self::NUMBER_LINES) { $pad = strlen((string) count($messages)); - $template = $this->isDecorated() ? ": %s" : "%-{$pad}s: %s"; + $template = $this->isDecorated() ? ": %s" : "%{$pad}s: %s"; if ($type & self::OUTPUT_RAW) { $messages = array_map(array('Symfony\Component\Console\Formatter\OutputFormatter', 'escape'), $messages); diff --git a/src/Psy/Shell.php b/src/Psy/Shell.php index ed8c0fdb9..64ae2c2a5 100644 --- a/src/Psy/Shell.php +++ b/src/Psy/Shell.php @@ -39,7 +39,7 @@ */ class Shell extends Application { - const VERSION = 'v0.1.5'; + const VERSION = 'v0.1.6'; const PROMPT = '>>> '; const BUFF_PROMPT = '... '; @@ -69,7 +69,7 @@ public function __construct(Configuration $config = null) $this->cleaner = $this->config->getCodeCleaner(); $this->loop = $this->config->getLoop(); $this->context = new Context; - $this->includes = $this->config->getDefaultIncludes(); + $this->includes = array(); $this->readline = $this->config->getReadline(); parent::__construct('Psy Shell', self::VERSION); @@ -369,7 +369,7 @@ public function setIncludes(array $includes = array()) */ public function getIncludes() { - return $this->includes; + return array_merge($this->config->getDefaultIncludes(), $this->includes); } /** diff --git a/test/Psy/Test/ConfigurationTest.php b/test/Psy/Test/ConfigurationTest.php index a89719820..87f5d17fb 100644 --- a/test/Psy/Test/ConfigurationTest.php +++ b/test/Psy/Test/ConfigurationTest.php @@ -108,4 +108,16 @@ private function joinPath() { return implode(DIRECTORY_SEPARATOR, func_get_args()); } + + public function testConfigIncludes() + { + $config = new Configuration(array( + 'defaultIncludes' => array('/file.php'), + 'configFile' => '(ignore user config)' + )); + + $includes = $config->getDefaultIncludes(); + $this->assertCount(1, $includes); + $this->assertEquals('/file.php', $includes[0]); + } } diff --git a/test/Psy/Test/ShellTest.php b/test/Psy/Test/ShellTest.php index 52c9fc4d8..4fc14456f 100644 --- a/test/Psy/Test/ShellTest.php +++ b/test/Psy/Test/ShellTest.php @@ -14,6 +14,7 @@ use Psy\Exception\ErrorException; use Psy\Exception\ParseErrorException; use Psy\Shell; +use Psy\Configuration; use Symfony\Component\Console\Output\StreamOutput; class ShellTest extends \PHPUnit_Framework_TestCase @@ -62,12 +63,27 @@ public function testUnknownScopeVariablesThrowExceptions() public function testIncludes() { - $shell = new Shell; + $config = new Configuration(array('configFile' => '(ignore user config)')); + + $shell = new Shell($config); $this->assertEmpty($shell->getIncludes()); $shell->setIncludes(array('foo', 'bar', 'baz')); $this->assertEquals(array('foo', 'bar', 'baz'), $shell->getIncludes()); } + public function testIncludesConfig() + { + $config = new Configuration(array( + 'defaultIncludes' => array('/file.php'), + 'configFile' => '(ignore user config)' + )); + + $shell = new Shell($config); + + $includes = $shell->getIncludes(); + $this->assertEquals('/file.php', $includes[0]); + } + public function testRenderingExceptions() { $shell = new Shell;